diff options
Diffstat (limited to 'netwerk/base')
305 files changed, 79487 insertions, 0 deletions
diff --git a/netwerk/base/ARefBase.h b/netwerk/base/ARefBase.h new file mode 100644 index 0000000000..207b5fe03c --- /dev/null +++ b/netwerk/base/ARefBase.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_ARefBase_h +#define mozilla_net_ARefBase_h + +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace net { + +// This is an abstract class that can be pointed to by either +// nsCOMPtr or nsRefPtr. nsHttpConnectionMgr uses it for generic +// objects that need to be reference counted - similiar to nsISupports +// but it may or may not be xpcom. + +class ARefBase { + public: + ARefBase() = default; + virtual ~ARefBase() = default; + + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/ArrayBufferInputStream.cpp b/netwerk/base/ArrayBufferInputStream.cpp new file mode 100644 index 0000000000..b8c26d03ef --- /dev/null +++ b/netwerk/base/ArrayBufferInputStream.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> +#include "ArrayBufferInputStream.h" +#include "nsStreamUtils.h" +#include "js/ArrayBuffer.h" // JS::{GetArrayBuffer{ByteLength,Data},IsArrayBufferObject} +#include "js/RootingAPI.h" // JS::{Handle,Rooted} +#include "js/Value.h" // JS::Value +#include "mozilla/Span.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/dom/ScriptSettings.h" + +using mozilla::dom::RootingCx; + +NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream, + nsIInputStream); + +NS_IMETHODIMP +ArrayBufferInputStream::SetDataFromJS(JS::Handle<JS::Value> aBuffer, + uint64_t aByteOffset, uint64_t aLength) { + NS_ASSERT_OWNINGTHREAD(ArrayBufferInputStream); + + if (!aBuffer.isObject()) { + return NS_ERROR_FAILURE; + } + JS::Rooted<JSObject*> arrayBuffer(RootingCx(), &aBuffer.toObject()); + if (!JS::IsArrayBufferObject(arrayBuffer)) { + return NS_ERROR_FAILURE; + } + + uint64_t buflen = JS::GetArrayBufferByteLength(arrayBuffer); + uint64_t offset = std::min(buflen, aByteOffset); + uint64_t bufferLength = std::min(buflen - offset, aLength); + + // Prevent truncation. + if (bufferLength > UINT32_MAX) { + return NS_ERROR_INVALID_ARG; + } + + mArrayBuffer = mozilla::MakeUniqueFallible<uint8_t[]>(bufferLength); + if (!mArrayBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mBufferLength = bufferLength; + + JS::AutoCheckCannotGC nogc; + bool isShared; + uint8_t* src = JS::GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset; + memcpy(&mArrayBuffer[0], src, mBufferLength); + return NS_OK; +} + +nsresult ArrayBufferInputStream::SetData(mozilla::UniquePtr<uint8_t[]> aBytes, + uint64_t aByteLen) { + mArrayBuffer = std::move(aBytes); + mBufferLength = aByteLen; + return NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::Close() { + mClosed = true; + return NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::Available(uint64_t* aCount) { + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + if (mArrayBuffer) { + *aCount = mBufferLength ? mBufferLength - mPos : 0; + } else { + *aCount = 0; + } + return NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::StreamStatus() { + return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount, + uint32_t* aReadCount) { + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount); +} + +NS_IMETHODIMP +ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t aCount, uint32_t* result) { + NS_ASSERTION(result, "null ptr"); + NS_ASSERTION(mBufferLength >= mPos, "bad stream state"); + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength), + "stream inited incorrectly"); + + *result = 0; + while (mPos < mBufferLength) { + uint32_t remaining = mBufferLength - mPos; + MOZ_ASSERT(mArrayBuffer); + + uint32_t count = std::min(aCount, remaining); + if (count == 0) { + break; + } + + uint32_t written; + nsresult rv = writer(this, closure, (char*)&mArrayBuffer[0] + mPos, *result, + count, &written); + if (NS_FAILED(rv)) { + // InputStreams do not propagate errors to caller. + return NS_OK; + } + + NS_ASSERTION(written <= count, + "writer should not write more than we asked it to write"); + mPos += written; + *result += written; + aCount -= written; + } + + return NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = true; + return NS_OK; +} diff --git a/netwerk/base/ArrayBufferInputStream.h b/netwerk/base/ArrayBufferInputStream.h new file mode 100644 index 0000000000..e3d4ea62f0 --- /dev/null +++ b/netwerk/base/ArrayBufferInputStream.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ArrayBufferInputStream_h +#define ArrayBufferInputStream_h + +#include "nsIArrayBufferInputStream.h" +#include "js/Value.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "nsISupportsImpl.h" + +#define NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID \ + "@mozilla.org/io/arraybuffer-input-stream;1" +#define NS_ARRAYBUFFERINPUTSTREAM_CID \ + { /* 3014dde6-aa1c-41db-87d0-48764a3710f6 */ \ + 0x3014dde6, 0xaa1c, 0x41db, { \ + 0x87, 0xd0, 0x48, 0x76, 0x4a, 0x37, 0x10, 0xf6 \ + } \ + } + +class ArrayBufferInputStream : public nsIArrayBufferInputStream { + public: + ArrayBufferInputStream() = default; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIARRAYBUFFERINPUTSTREAM + NS_DECL_NSIINPUTSTREAM + + private: + virtual ~ArrayBufferInputStream() = default; + mozilla::UniquePtr<uint8_t[]> mArrayBuffer; + uint32_t mBufferLength{0}; + uint32_t mPos{0}; + bool mClosed{false}; +}; + +#endif // ArrayBufferInputStream_h diff --git a/netwerk/base/AutoClose.h b/netwerk/base/AutoClose.h new file mode 100644 index 0000000000..a0e3a48e17 --- /dev/null +++ b/netwerk/base/AutoClose.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_AutoClose_h +#define mozilla_net_AutoClose_h + +#include "nsCOMPtr.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +// A container for XPCOM streams (e.g. nsIAsyncInputStream) and other +// refcounted classes that need to have the Close() method called explicitly +// before they are destroyed. +template <typename T> +class AutoClose { + public: + AutoClose() : mMutex("net::AutoClose.mMutex") {} + ~AutoClose() { CloseAndRelease(); } + + explicit operator bool() { + MutexAutoLock lock(mMutex); + return mPtr; + } + + already_AddRefed<T> forget() { + MutexAutoLock lock(mMutex); + return mPtr.forget(); + } + + void takeOver(nsCOMPtr<T>& rhs) { TakeOverInternal(rhs.forget()); } + + void CloseAndRelease() { TakeOverInternal(nullptr); } + + private: + void TakeOverInternal(already_AddRefed<T>&& aOther) { + nsCOMPtr<T> ptr(std::move(aOther)); + { + MutexAutoLock lock(mMutex); + ptr.swap(mPtr); + } + + if (ptr) { + ptr->Close(); + } + } + + void operator=(const AutoClose<T>&) = delete; + AutoClose(const AutoClose<T>&) = delete; + + nsCOMPtr<T> mPtr; + Mutex mMutex MOZ_UNANNOTATED; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_AutoClose_h diff --git a/netwerk/base/BackgroundFileSaver.cpp b/netwerk/base/BackgroundFileSaver.cpp new file mode 100644 index 0000000000..3af6b8eb26 --- /dev/null +++ b/netwerk/base/BackgroundFileSaver.cpp @@ -0,0 +1,1121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BackgroundFileSaver.h" + +#include "ScopedNSSTypes.h" +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/Casting.h" +#include "mozilla/Logging.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Telemetry.h" +#include "nsCOMArray.h" +#include "nsComponentManagerUtils.h" +#include "nsDependentSubstring.h" +#include "nsIAsyncInputStream.h" +#include "nsIFile.h" +#include "nsIMutableArray.h" +#include "nsIPipe.h" +#include "nsNetUtil.h" +#include "nsThreadUtils.h" +#include "pk11pub.h" +#include "secoidt.h" + +#ifdef XP_WIN +# include <windows.h> +# include <softpub.h> +# include <wintrust.h> +#endif // XP_WIN + +namespace mozilla { +namespace net { + +// MOZ_LOG=BackgroundFileSaver:5 +static LazyLogModule prlog("BackgroundFileSaver"); +#define LOG(args) MOZ_LOG(prlog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(prlog, mozilla::LogLevel::Debug) + +//////////////////////////////////////////////////////////////////////////////// +//// Globals + +/** + * Buffer size for writing to the output file or reading from the input file. + */ +#define BUFFERED_IO_SIZE (1024 * 32) + +/** + * When this upper limit is reached, the original request is suspended. + */ +#define REQUEST_SUSPEND_AT (1024 * 1024 * 4) + +/** + * When this lower limit is reached, the original request is resumed. + */ +#define REQUEST_RESUME_AT (1024 * 1024 * 2) + +//////////////////////////////////////////////////////////////////////////////// +//// NotifyTargetChangeRunnable + +/** + * Runnable object used to notify the control thread that file contents will now + * be saved to the specified file. + */ +class NotifyTargetChangeRunnable final : public Runnable { + public: + NotifyTargetChangeRunnable(BackgroundFileSaver* aSaver, nsIFile* aTarget) + : Runnable("net::NotifyTargetChangeRunnable"), + mSaver(aSaver), + mTarget(aTarget) {} + + NS_IMETHOD Run() override { return mSaver->NotifyTargetChange(mTarget); } + + private: + RefPtr<BackgroundFileSaver> mSaver; + nsCOMPtr<nsIFile> mTarget; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaver + +uint32_t BackgroundFileSaver::sThreadCount = 0; +uint32_t BackgroundFileSaver::sTelemetryMaxThreadCount = 0; + +BackgroundFileSaver::BackgroundFileSaver() { + LOG(("Created BackgroundFileSaver [this = %p]", this)); +} + +BackgroundFileSaver::~BackgroundFileSaver() { + LOG(("Destroying BackgroundFileSaver [this = %p]", this)); +} + +// Called on the control thread. +nsresult BackgroundFileSaver::Init() { + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); + + NS_NewPipe2(getter_AddRefs(mPipeInputStream), + getter_AddRefs(mPipeOutputStream), true, true, 0, + HasInfiniteBuffer() ? UINT32_MAX : 0); + + mControlEventTarget = GetCurrentSerialEventTarget(); + NS_ENSURE_TRUE(mControlEventTarget, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = NS_CreateBackgroundTaskQueue("BgFileSaver", + getter_AddRefs(mBackgroundET)); + NS_ENSURE_SUCCESS(rv, rv); + + sThreadCount++; + if (sThreadCount > sTelemetryMaxThreadCount) { + sTelemetryMaxThreadCount = sThreadCount; + } + + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver** aObserver) { + NS_ENSURE_ARG_POINTER(aObserver); + *aObserver = do_AddRef(mObserver).take(); + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver* aObserver) { + mObserver = aObserver; + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::EnableAppend() { + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); + + MutexAutoLock lock(mLock); + mAppend = true; + + return NS_OK; +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::SetTarget(nsIFile* aTarget, bool aKeepPartial) { + NS_ENSURE_ARG(aTarget); + { + MutexAutoLock lock(mLock); + if (!mInitialTarget) { + aTarget->Clone(getter_AddRefs(mInitialTarget)); + mInitialTargetKeepPartial = aKeepPartial; + } else { + aTarget->Clone(getter_AddRefs(mRenamedTarget)); + mRenamedTargetKeepPartial = aKeepPartial; + } + } + + // After the worker thread wakes up because attention is requested, it will + // rename or create the target file as requested, and start copying data. + return GetWorkerThreadAttention(true); +} + +// Called on the control thread. +NS_IMETHODIMP +BackgroundFileSaver::Finish(nsresult aStatus) { + nsresult rv; + + // This will cause the NS_AsyncCopy operation, if it's in progress, to consume + // all the data that is still in the pipe, and then finish. + rv = mPipeOutputStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + // Ensure that, when we get attention from the worker thread, if no pending + // rename operation is waiting, the operation will complete. + { + MutexAutoLock lock(mLock); + mFinishRequested = true; + if (NS_SUCCEEDED(mStatus)) { + mStatus = aStatus; + } + } + + // After the worker thread wakes up because attention is requested, it will + // process the completion conditions, detect that completion is requested, and + // notify the main thread of the completion. If this function was called with + // a success code, we wait for the copy to finish before processing the + // completion conditions, otherwise we interrupt the copy immediately. + return GetWorkerThreadAttention(NS_FAILED(aStatus)); +} + +NS_IMETHODIMP +BackgroundFileSaver::EnableSha256() { + MOZ_ASSERT(NS_IsMainThread(), + "Can't enable sha256 or initialize NSS off the main thread"); + // Ensure Personal Security Manager is initialized. This is required for + // PK11_* operations to work. + nsresult rv; + nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + MutexAutoLock lock(mLock); + mSha256Enabled = true; // this will be read by the worker thread + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaver::GetSha256Hash(nsACString& aHash) { + MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread"); + // We acquire a lock because mSha256 is written on the worker thread. + MutexAutoLock lock(mLock); + if (mSha256.IsEmpty()) { + return NS_ERROR_NOT_AVAILABLE; + } + aHash = mSha256; + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaver::EnableSignatureInfo() { + MOZ_ASSERT(NS_IsMainThread(), + "Can't enable signature extraction off the main thread"); + // Ensure Personal Security Manager is initialized. + nsresult rv; + nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + MutexAutoLock lock(mLock); + mSignatureInfoEnabled = true; + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaver::GetSignatureInfo( + nsTArray<nsTArray<nsTArray<uint8_t>>>& aSignatureInfo) { + MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread"); + // We acquire a lock because mSignatureInfo is written on the worker thread. + MutexAutoLock lock(mLock); + if (!mComplete || !mSignatureInfoEnabled) { + return NS_ERROR_NOT_AVAILABLE; + } + for (const auto& signatureChain : mSignatureInfo) { + aSignatureInfo.AppendElement(TransformIntoNewArray( + signatureChain, [](const auto& element) { return element.Clone(); })); + } + return NS_OK; +} + +// Called on the control thread. +nsresult BackgroundFileSaver::GetWorkerThreadAttention( + bool aShouldInterruptCopy) { + nsresult rv; + + MutexAutoLock lock(mLock); + + // We only require attention one time. If this function is called two times + // before the worker thread wakes up, and the first has aShouldInterruptCopy + // false and the second true, we won't forcibly interrupt the copy from the + // control thread. However, that never happens, because calling Finish with a + // success code is the only case that may result in aShouldInterruptCopy being + // false. In that case, we won't call this function again, because consumers + // should not invoke other methods on the control thread after calling Finish. + // And in any case, Finish already closes one end of the pipe, causing the + // copy to finish properly on its own. + if (mWorkerThreadAttentionRequested) { + return NS_OK; + } + + if (!mAsyncCopyContext) { + // Background event queues are not shutdown and could be called after + // the queue is reset to null. To match the behavior of nsIThread + // return NS_ERROR_UNEXPECTED + if (!mBackgroundET) { + return NS_ERROR_UNEXPECTED; + } + + // Copy is not in progress, post an event to handle the change manually. + rv = mBackgroundET->Dispatch( + NewRunnableMethod("net::BackgroundFileSaver::ProcessAttention", this, + &BackgroundFileSaver::ProcessAttention), + NS_DISPATCH_EVENT_MAY_BLOCK); + NS_ENSURE_SUCCESS(rv, rv); + + } else if (aShouldInterruptCopy) { + // Interrupt the copy. The copy will be resumed, if needed, by the + // ProcessAttention function, invoked by the AsyncCopyCallback function. + NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT); + } + + // Indicate that attention has been requested successfully, there is no need + // to post another event until the worker thread processes the current one. + mWorkerThreadAttentionRequested = true; + + return NS_OK; +} + +// Called on the worker thread. +// static +void BackgroundFileSaver::AsyncCopyCallback(void* aClosure, nsresult aStatus) { + // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object + // alive even if other references disappeared. At the end of this method, + // we've finished using the object and can safely release our reference. + RefPtr<BackgroundFileSaver> self = + dont_AddRef((BackgroundFileSaver*)aClosure); + { + MutexAutoLock lock(self->mLock); + + // Now that the copy was interrupted or terminated, any notification from + // the control thread requires an event to be posted to the worker thread. + self->mAsyncCopyContext = nullptr; + + // When detecting failures, ignore the status code we use to interrupt. + if (NS_FAILED(aStatus) && aStatus != NS_ERROR_ABORT && + NS_SUCCEEDED(self->mStatus)) { + self->mStatus = aStatus; + } + } + + (void)self->ProcessAttention(); +} + +// Called on the worker thread. +nsresult BackgroundFileSaver::ProcessAttention() { + nsresult rv; + + // This function is called whenever the attention of the worker thread has + // been requested. This may happen in these cases: + // * We are about to start the copy for the first time. In this case, we are + // called from an event posted on the worker thread from the control thread + // by GetWorkerThreadAttention, and mAsyncCopyContext is null. + // * We have interrupted the copy for some reason. In this case, we are + // called by AsyncCopyCallback, and mAsyncCopyContext is null. + // * We are currently executing ProcessStateChange, and attention is requested + // by the control thread, for example because SetTarget or Finish have been + // called. In this case, we are called from from an event posted through + // GetWorkerThreadAttention. While mAsyncCopyContext was always null when + // the event was posted, at this point mAsyncCopyContext may not be null + // anymore, because ProcessStateChange may have started the copy before the + // event that called this function was processed on the worker thread. + // If mAsyncCopyContext is not null, we interrupt the copy and re-enter + // through AsyncCopyCallback. This allows us to check if, for instance, we + // should rename the target file. We will then restart the copy if needed. + + // mAsyncCopyContext is only written on the worker thread (which we are on) + MOZ_ASSERT(!NS_IsMainThread()); + { + // Even though we're the only thread that writes this, we have to take the + // lock + MutexAutoLock lock(mLock); + if (mAsyncCopyContext) { + NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT); + return NS_OK; + } + } + // Use the current shared state to determine the next operation to execute. + rv = ProcessStateChange(); + if (NS_FAILED(rv)) { + // If something failed while processing, terminate the operation now. + { + MutexAutoLock lock(mLock); + + if (NS_SUCCEEDED(mStatus)) { + mStatus = rv; + } + } + // Ensure we notify completion now that the operation failed. + CheckCompletion(); + } + + return NS_OK; +} + +// Called on the worker thread. +nsresult BackgroundFileSaver::ProcessStateChange() { + nsresult rv; + + // We might have been notified because the operation is complete, verify. + if (CheckCompletion()) { + return NS_OK; + } + + // Get a copy of the current shared state for the worker thread. + nsCOMPtr<nsIFile> initialTarget; + bool initialTargetKeepPartial; + nsCOMPtr<nsIFile> renamedTarget; + bool renamedTargetKeepPartial; + bool sha256Enabled; + bool append; + { + MutexAutoLock lock(mLock); + + initialTarget = mInitialTarget; + initialTargetKeepPartial = mInitialTargetKeepPartial; + renamedTarget = mRenamedTarget; + renamedTargetKeepPartial = mRenamedTargetKeepPartial; + sha256Enabled = mSha256Enabled; + append = mAppend; + + // From now on, another attention event needs to be posted if state changes. + mWorkerThreadAttentionRequested = false; + } + + // The initial target can only be null if it has never been assigned. In this + // case, there is nothing to do since we never created any output file. + if (!initialTarget) { + return NS_OK; + } + + // Determine if we are processing the attention request for the first time. + bool isContinuation = !!mActualTarget; + if (!isContinuation) { + // Assign the target file for the first time. + mActualTarget = initialTarget; + mActualTargetKeepPartial = initialTargetKeepPartial; + } + + // Verify whether we have actually been instructed to use a different file. + // This may happen the first time this function is executed, if SetTarget was + // called two times before the worker thread processed the attention request. + bool equalToCurrent = false; + if (renamedTarget) { + rv = mActualTarget->Equals(renamedTarget, &equalToCurrent); + NS_ENSURE_SUCCESS(rv, rv); + if (!equalToCurrent) { + // If we were asked to rename the file but the initial file did not exist, + // we simply create the file in the renamed location. We avoid this check + // if we have already started writing the output file ourselves. + bool exists = true; + if (!isContinuation) { + rv = mActualTarget->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + } + if (exists) { + // We are moving the previous target file to a different location. + nsCOMPtr<nsIFile> renamedTargetParentDir; + rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString renamedTargetName; + rv = renamedTarget->GetLeafName(renamedTargetName); + NS_ENSURE_SUCCESS(rv, rv); + + // We must delete any existing target file before moving the current + // one. + rv = renamedTarget->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (exists) { + rv = renamedTarget->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Move the file. If this fails, we still reference the original file + // in mActualTarget, so that it is deleted if requested. If this + // succeeds, the nsIFile instance referenced by mActualTarget mutates + // and starts pointing to the new file, but we'll discard the reference. + rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName); + NS_ENSURE_SUCCESS(rv, rv); + } + + // We should not only update the mActualTarget with renameTarget when + // they point to the different files. + // In this way, if mActualTarget and renamedTarget point to the same file + // with different addresses, "CheckCompletion()" will return false + // forever. + } + + // Update mActualTarget with renameTarget, + // even if they point to the same file. + mActualTarget = renamedTarget; + mActualTargetKeepPartial = renamedTargetKeepPartial; + } + + // Notify if the target file name actually changed. + if (!equalToCurrent) { + // We must clone the nsIFile instance because mActualTarget is not + // immutable, it may change if the target is renamed later. + nsCOMPtr<nsIFile> actualTargetToNotify; + rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<NotifyTargetChangeRunnable> event = + new NotifyTargetChangeRunnable(this, actualTargetToNotify); + NS_ENSURE_TRUE(event, NS_ERROR_FAILURE); + + rv = mControlEventTarget->Dispatch(event, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (isContinuation) { + // The pending rename operation might be the last task before finishing. We + // may return here only if we have already created the target file. + if (CheckCompletion()) { + return NS_OK; + } + + // Even if the operation did not complete, the pipe input stream may be + // empty and may have been closed already. We detect this case using the + // Available property, because it never returns an error if there is more + // data to be consumed. If the pipe input stream is closed, we just exit + // and wait for more calls like SetTarget or Finish to be invoked on the + // control thread. However, we still truncate the file or create the + // initial digest context if we are expected to do that. + uint64_t available; + rv = mPipeInputStream->Available(&available); + if (NS_FAILED(rv)) { + return NS_OK; + } + } + + // Create the digest if requested and NSS hasn't been shut down. + if (sha256Enabled && mDigest.isNothing()) { + mDigest.emplace(Digest()); + mDigest->Begin(SEC_OID_SHA256); + } + + // When we are requested to append to an existing file, we should read the + // existing data and ensure we include it as part of the final hash. + if (mDigest.isSome() && append && !isContinuation) { + nsCOMPtr<nsIInputStream> inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mActualTarget, + PR_RDONLY | nsIFile::OS_READAHEAD); + if (rv != NS_ERROR_FILE_NOT_FOUND) { + NS_ENSURE_SUCCESS(rv, rv); + + // Try to clean up the inputStream if an error occurs. + auto closeGuard = + mozilla::MakeScopeExit([&] { Unused << inputStream->Close(); }); + + char buffer[BUFFERED_IO_SIZE]; + while (true) { + uint32_t count; + rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count); + NS_ENSURE_SUCCESS(rv, rv); + + if (count == 0) { + // We reached the end of the file. + break; + } + + rv = mDigest->Update(BitwiseCast<unsigned char*, char*>(buffer), count); + NS_ENSURE_SUCCESS(rv, rv); + + // The pending resume operation may have been cancelled by the control + // thread while the worker thread was reading in the existing file. + // Abort reading in the original file in that case, as the digest will + // be discarded anyway. + MutexAutoLock lock(mLock); + if (NS_FAILED(mStatus)) { + return NS_ERROR_ABORT; + } + } + + // Close explicitly to handle any errors. + closeGuard.release(); + rv = inputStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // We will append to the initial target file only if it was requested by the + // caller, but we'll always append on subsequent accesses to the target file. + int32_t creationIoFlags; + if (isContinuation) { + creationIoFlags = PR_APPEND; + } else { + creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE; + } + + // Create the target file, or append to it if we already started writing it. + // The 0600 permissions are used while the file is being downloaded, and for + // interrupted downloads. Those may be located in the system temporary + // directory, as well as the target directory, and generally have a ".part" + // extension. Those part files should never be group or world-writable even + // if the umask allows it. + nsCOMPtr<nsIOutputStream> outputStream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mActualTarget, + PR_WRONLY | creationIoFlags, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIOutputStream> bufferedStream; + rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedStream), + outputStream.forget(), BUFFERED_IO_SIZE); + NS_ENSURE_SUCCESS(rv, rv); + outputStream = bufferedStream; + + // Wrap the output stream so that it feeds the digest if needed. + if (mDigest.isSome()) { + // Constructing the DigestOutputStream cannot fail. Passing mDigest + // to DigestOutputStream is safe, because BackgroundFileSaver always + // outlives the outputStream. BackgroundFileSaver is reference-counted + // before the call to AsyncCopy, and mDigest is never destroyed + // before AsyncCopyCallback. + outputStream = new DigestOutputStream(outputStream, mDigest.ref()); + } + + // Start copying our input to the target file. No errors can be raised past + // this point if the copy starts, since they should be handled by the thread. + { + MutexAutoLock lock(mLock); + + rv = NS_AsyncCopy(mPipeInputStream, outputStream, mBackgroundET, + NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback, + this, false, true, getter_AddRefs(mAsyncCopyContext), + GetProgressCallback()); + if (NS_FAILED(rv)) { + NS_WARNING("NS_AsyncCopy failed."); + mAsyncCopyContext = nullptr; + return rv; + } + } + + // If the operation succeeded, we must ensure that we keep this object alive + // for the entire duration of the copy, since only the raw pointer will be + // provided as the argument of the AsyncCopyCallback function. We can add the + // reference now, after NS_AsyncCopy returned, because it always starts + // processing asynchronously, and there is no risk that the callback is + // invoked before we reach this point. If the operation failed instead, then + // AsyncCopyCallback will never be called. + NS_ADDREF_THIS(); + + return NS_OK; +} + +// Called on the worker thread. +bool BackgroundFileSaver::CheckCompletion() { + nsresult rv; + + bool failed = true; + { + MutexAutoLock lock(mLock); + MOZ_ASSERT(!mAsyncCopyContext, + "Should not be copying when checking completion conditions."); + + if (mComplete) { + return true; + } + + // If an error occurred, we don't need to do the checks in this code block, + // and the operation can be completed immediately with a failure code. + if (NS_SUCCEEDED(mStatus)) { + failed = false; + + // We did not incur in an error, so we must determine if we can stop now. + // If the Finish method has not been called, we can just continue now. + if (!mFinishRequested) { + return false; + } + + // We can only stop when all the operations requested by the control + // thread have been processed. First, we check whether we have processed + // the first SetTarget call, if any. Then, we check whether we have + // processed any rename requested by subsequent SetTarget calls. + if ((mInitialTarget && !mActualTarget) || + (mRenamedTarget && mRenamedTarget != mActualTarget)) { + return false; + } + + // If we still have data to write to the output file, allow the copy + // operation to resume. The Available getter may return an error if one + // of the pipe's streams has been already closed. + uint64_t available; + rv = mPipeInputStream->Available(&available); + if (NS_SUCCEEDED(rv) && available != 0) { + return false; + } + } + + mComplete = true; + } + + // Ensure we notify completion now that the operation finished. + // Do a best-effort attempt to remove the file if required. + if (failed && mActualTarget && !mActualTargetKeepPartial) { + (void)mActualTarget->Remove(false); + } + + // Finish computing the hash + if (!failed && mDigest.isSome()) { + nsTArray<uint8_t> outArray; + rv = mDigest->End(outArray); + if (NS_SUCCEEDED(rv)) { + MutexAutoLock lock(mLock); + mSha256 = nsDependentCSubstring( + BitwiseCast<char*, uint8_t*>(outArray.Elements()), outArray.Length()); + } + } + + // Compute the signature of the binary. ExtractSignatureInfo doesn't do + // anything on non-Windows platforms except return an empty nsIArray. + if (!failed && mActualTarget) { + nsString filePath; + mActualTarget->GetTarget(filePath); + nsresult rv = ExtractSignatureInfo(filePath); + if (NS_FAILED(rv)) { + LOG(("Unable to extract signature information [this = %p].", this)); + } else { + LOG(("Signature extraction success! [this = %p]", this)); + } + } + + // Post an event to notify that the operation completed. + if (NS_FAILED(mControlEventTarget->Dispatch( + NewRunnableMethod("BackgroundFileSaver::NotifySaveComplete", this, + &BackgroundFileSaver::NotifySaveComplete), + NS_DISPATCH_NORMAL))) { + NS_WARNING("Unable to post completion event to the control thread."); + } + + return true; +} + +// Called on the control thread. +nsresult BackgroundFileSaver::NotifyTargetChange(nsIFile* aTarget) { + if (mObserver) { + (void)mObserver->OnTargetChange(this, aTarget); + } + + return NS_OK; +} + +// Called on the control thread. +nsresult BackgroundFileSaver::NotifySaveComplete() { + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); + + nsresult status; + { + MutexAutoLock lock(mLock); + status = mStatus; + } + + if (mObserver) { + (void)mObserver->OnSaveComplete(this, status); + // If mObserver keeps alive an enclosure that captures `this`, we'll have a + // cycle that won't be caught by the cycle-collector, so we need to break it + // when we're done here (see bug 1444265). + mObserver = nullptr; + } + + // At this point, the worker thread will not process any more events, and we + // can shut it down. Shutting down a thread may re-enter the event loop on + // this thread. This is not a problem in this case, since this function is + // called by a top-level event itself, and we have already invoked the + // completion observer callback. Re-entering the loop can only delay the + // final release and destruction of this saver object, since we are keeping a + // reference to it through the event object. + mBackgroundET = nullptr; + + sThreadCount--; + + // When there are no more active downloads, we consider the download session + // finished. We record the maximum number of concurrent downloads reached + // during the session in a telemetry histogram, and we reset the maximum + // thread counter for the next download session + if (sThreadCount == 0) { + Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT, + sTelemetryMaxThreadCount); + sTelemetryMaxThreadCount = 0; + } + + return NS_OK; +} + +nsresult BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath) { + MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread"); + { + MutexAutoLock lock(mLock); + if (!mSignatureInfoEnabled) { + return NS_OK; + } + } +#ifdef XP_WIN + // Setup the file to check. + WINTRUST_FILE_INFO fileToCheck = {0}; + fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO); + fileToCheck.pcwszFilePath = filePath.Data(); + fileToCheck.hFile = nullptr; + fileToCheck.pgKnownSubject = nullptr; + + // We want to check it is signed and trusted. + WINTRUST_DATA trustData = {0}; + trustData.cbStruct = sizeof(trustData); + trustData.pPolicyCallbackData = nullptr; + trustData.pSIPClientData = nullptr; + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_NONE; + trustData.dwUnionChoice = WTD_CHOICE_FILE; + trustData.dwStateAction = WTD_STATEACTION_VERIFY; + trustData.hWVTStateData = nullptr; + trustData.pwszURLReference = nullptr; + // Disallow revocation checks over the network + trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + // no UI + trustData.dwUIContext = 0; + trustData.pFile = &fileToCheck; + + // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate + // chains up to a trusted root CA and has appropriate permissions to sign + // code. + GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; + // Check if the file is signed by something that is trusted. If the file is + // not signed, this is a no-op. + LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData); + CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr; + // According to the Windows documentation, we should check against 0 instead + // of ERROR_SUCCESS, which is an HRESULT. + if (ret == 0) { + cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData); + } + if (cryptoProviderData) { + // Lock because signature information is read on the main thread. + MutexAutoLock lock(mLock); + LOG(("Downloaded trusted and signed file [this = %p].", this)); + // A binary may have multiple signers. Each signer may have multiple certs + // in the chain. + for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) { + const CERT_CHAIN_CONTEXT* certChainContext = + cryptoProviderData->pasSigners[i].pChainContext; + if (!certChainContext) { + break; + } + for (DWORD j = 0; j < certChainContext->cChain; ++j) { + const CERT_SIMPLE_CHAIN* certSimpleChain = + certChainContext->rgpChain[j]; + if (!certSimpleChain) { + break; + } + + nsTArray<nsTArray<uint8_t>> certList; + bool extractionSuccess = true; + for (DWORD k = 0; k < certSimpleChain->cElement; ++k) { + CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k]; + if (certChainElement->pCertContext->dwCertEncodingType != + X509_ASN_ENCODING) { + continue; + } + nsTArray<uint8_t> cert; + cert.AppendElements(certChainElement->pCertContext->pbCertEncoded, + certChainElement->pCertContext->cbCertEncoded); + certList.AppendElement(std::move(cert)); + } + if (extractionSuccess) { + mSignatureInfo.AppendElement(std::move(certList)); + } + } + } + // Free the provider data if cryptoProviderData is not null. + trustData.dwStateAction = WTD_STATEACTION_CLOSE; + WinVerifyTrust(nullptr, &policyGUID, &trustData); + } else { + LOG(("Downloaded unsigned or untrusted file [this = %p].", this)); + } +#endif + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverOutputStream + +NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream, nsIBackgroundFileSaver, + nsIOutputStream, nsIAsyncOutputStream, + nsIOutputStreamCallback) + +BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream() + : mAsyncWaitCallback(nullptr) {} + +bool BackgroundFileSaverOutputStream::HasInfiniteBuffer() { return false; } + +nsAsyncCopyProgressFun BackgroundFileSaverOutputStream::GetProgressCallback() { + return nullptr; +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::Close() { return mPipeOutputStream->Close(); } + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::Flush() { return mPipeOutputStream->Flush(); } + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::StreamStatus() { + return mPipeOutputStream->StreamStatus(); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* _retval) { + return mPipeOutputStream->Write(aBuf, aCount, _retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream* aFromStream, + uint32_t aCount, uint32_t* _retval) { + return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, uint32_t aCount, + uint32_t* _retval) { + return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::IsNonBlocking(bool* _retval) { + return mPipeOutputStream->IsNonBlocking(_retval); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason) { + return mPipeOutputStream->CloseWithStatus(reason); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + NS_ENSURE_STATE(!mAsyncWaitCallback); + + mAsyncWaitCallback = aCallback; + + return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount, + aEventTarget); +} + +NS_IMETHODIMP +BackgroundFileSaverOutputStream::OnOutputStreamReady( + nsIAsyncOutputStream* aStream) { + NS_ENSURE_STATE(mAsyncWaitCallback); + + nsCOMPtr<nsIOutputStreamCallback> asyncWaitCallback = nullptr; + asyncWaitCallback.swap(mAsyncWaitCallback); + + return asyncWaitCallback->OnOutputStreamReady(this); +} + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverStreamListener + +NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener, nsIBackgroundFileSaver, + nsIRequestObserver, nsIStreamListener) + +bool BackgroundFileSaverStreamListener::HasInfiniteBuffer() { return true; } + +nsAsyncCopyProgressFun +BackgroundFileSaverStreamListener::GetProgressCallback() { + return AsyncCopyProgressCallback; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest* aRequest) { + NS_ENSURE_ARG(aRequest); + + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + // If an error occurred, cancel the operation immediately. On success, wait + // until the caller has determined whether the file should be renamed. + if (NS_FAILED(aStatusCode)) { + Finish(aStatusCode); + } + + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, + uint32_t aCount) { + nsresult rv; + + NS_ENSURE_ARG(aRequest); + + // Read the requested data. Since the pipe has an infinite buffer, we don't + // expect any write error to occur here. + uint32_t writeCount; + rv = mPipeOutputStream->WriteFrom(aInputStream, aCount, &writeCount); + NS_ENSURE_SUCCESS(rv, rv); + + // If reading from the input stream fails for any reason, the pipe will return + // a success code, but without reading all the data. Since we should be able + // to read the requested data when OnDataAvailable is called, raise an error. + if (writeCount < aCount) { + NS_WARNING("Reading from the input stream should not have failed."); + return NS_ERROR_UNEXPECTED; + } + + bool stateChanged = false; + { + MutexAutoLock lock(mSuspensionLock); + + if (!mReceivedTooMuchData) { + uint64_t available; + nsresult rv = mPipeInputStream->Available(&available); + if (NS_SUCCEEDED(rv) && available > REQUEST_SUSPEND_AT) { + mReceivedTooMuchData = true; + mRequest = aRequest; + stateChanged = true; + } + } + } + + if (stateChanged) { + NotifySuspendOrResume(); + } + + return NS_OK; +} + +// Called on the worker thread. +// static +void BackgroundFileSaverStreamListener::AsyncCopyProgressCallback( + void* aClosure, uint32_t aCount) { + BackgroundFileSaverStreamListener* self = + (BackgroundFileSaverStreamListener*)aClosure; + + // Wait if the control thread is in the process of suspending or resuming. + MutexAutoLock lock(self->mSuspensionLock); + + // This function is called when some bytes are consumed by NS_AsyncCopy. Each + // time this happens, verify if a suspended request should be resumed, because + // we have now consumed enough data. + if (self->mReceivedTooMuchData) { + uint64_t available; + nsresult rv = self->mPipeInputStream->Available(&available); + if (NS_FAILED(rv) || available < REQUEST_RESUME_AT) { + self->mReceivedTooMuchData = false; + + // Post an event to verify if the request should be resumed. + if (NS_FAILED(self->mControlEventTarget->Dispatch( + NewRunnableMethod( + "BackgroundFileSaverStreamListener::NotifySuspendOrResume", + self, + &BackgroundFileSaverStreamListener::NotifySuspendOrResume), + NS_DISPATCH_NORMAL))) { + NS_WARNING("Unable to post resume event to the control thread."); + } + } + } +} + +// Called on the control thread. +nsresult BackgroundFileSaverStreamListener::NotifySuspendOrResume() { + // Prevent the worker thread from changing state while processing. + MutexAutoLock lock(mSuspensionLock); + + if (mReceivedTooMuchData) { + if (!mRequestSuspended) { + // Try to suspend the request. If this fails, don't try to resume later. + if (NS_SUCCEEDED(mRequest->Suspend())) { + mRequestSuspended = true; + } else { + NS_WARNING("Unable to suspend the request."); + } + } + } else { + if (mRequestSuspended) { + // Resume the request only if we succeeded in suspending it. + if (NS_SUCCEEDED(mRequest->Resume())) { + mRequestSuspended = false; + } else { + NS_WARNING("Unable to resume the request."); + } + } + } + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// DigestOutputStream +NS_IMPL_ISUPPORTS(DigestOutputStream, nsIOutputStream) + +DigestOutputStream::DigestOutputStream(nsIOutputStream* aStream, + Digest& aDigest) + : mOutputStream(aStream), mDigest(aDigest) { + MOZ_ASSERT(mOutputStream, "Can't have null output stream"); +} + +NS_IMETHODIMP +DigestOutputStream::Close() { return mOutputStream->Close(); } + +NS_IMETHODIMP +DigestOutputStream::Flush() { return mOutputStream->Flush(); } + +NS_IMETHODIMP +DigestOutputStream::StreamStatus() { return mOutputStream->StreamStatus(); } + +NS_IMETHODIMP +DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval) { + nsresult rv = mDigest.Update( + BitwiseCast<const unsigned char*, const char*>(aBuf), aCount); + NS_ENSURE_SUCCESS(rv, rv); + + return mOutputStream->Write(aBuf, aCount, retval); +} + +NS_IMETHODIMP +DigestOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* retval) { + // Not supported. We could read the stream to a buf, call DigestOp on the + // result, seek back and pass the stream on, but it's not worth it since our + // application (NS_AsyncCopy) doesn't invoke this on the sink. + MOZ_CRASH("DigestOutputStream::WriteFrom not implemented"); +} + +NS_IMETHODIMP +DigestOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* retval) { + MOZ_CRASH("DigestOutputStream::WriteSegments not implemented"); +} + +NS_IMETHODIMP +DigestOutputStream::IsNonBlocking(bool* retval) { + return mOutputStream->IsNonBlocking(retval); +} + +#undef LOG_ENABLED + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/BackgroundFileSaver.h b/netwerk/base/BackgroundFileSaver.h new file mode 100644 index 0000000000..214aa31d10 --- /dev/null +++ b/netwerk/base/BackgroundFileSaver.h @@ -0,0 +1,397 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file defines two implementations of the nsIBackgroundFileSaver + * interface. See the "test_backgroundfilesaver.js" file for usage examples. + */ + +#ifndef BackgroundFileSaver_h__ +#define BackgroundFileSaver_h__ + +#include "ScopedNSSTypes.h" +#include "mozilla/Mutex.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBackgroundFileSaver.h" +#include "nsIStreamListener.h" +#include "nsStreamUtils.h" +#include "nsString.h" + +class nsIAsyncInputStream; +class nsISerialEventTarget; + +namespace mozilla { +namespace net { + +class DigestOutputStream; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaver + +class BackgroundFileSaver : public nsIBackgroundFileSaver { + public: + NS_DECL_NSIBACKGROUNDFILESAVER + + BackgroundFileSaver(); + + /** + * Initializes the pipe and the worker thread on XPCOM construction. + * + * This is called automatically by the XPCOM infrastructure, and if this + * fails, the factory will delete this object without returning a reference. + */ + nsresult Init(); + + /** + * Number of worker threads that are currently running. + */ + static uint32_t sThreadCount; + + /** + * Maximum number of worker threads reached during the current download + * session, used for telemetry. + * + * When there are no more worker threads running, we consider the download + * session finished, and this counter is reset. + */ + static uint32_t sTelemetryMaxThreadCount; + + protected: + virtual ~BackgroundFileSaver(); + + /** + * Thread that constructed this object. + */ + nsCOMPtr<nsIEventTarget> mControlEventTarget; + + /** + * Thread to which the actual input/output is delegated. + */ + nsCOMPtr<nsISerialEventTarget> mBackgroundET; + + /** + * Stream that receives data from derived classes. The received data will be + * available to the worker thread through mPipeInputStream. This is an + * instance of nsPipeOutputStream, not BackgroundFileSaverOutputStream. + */ + nsCOMPtr<nsIAsyncOutputStream> mPipeOutputStream; + + /** + * Used during initialization, determines if the pipe is created with an + * infinite buffer. An infinite buffer is required if the derived class + * implements nsIStreamListener, because this interface requires all the + * provided data to be consumed synchronously. + */ + virtual bool HasInfiniteBuffer() = 0; + + /** + * Used by derived classes if they need to be called back while copying. + */ + virtual nsAsyncCopyProgressFun GetProgressCallback() = 0; + + /** + * Stream used by the worker thread to read the data to be saved. + */ + nsCOMPtr<nsIAsyncInputStream> mPipeInputStream; + + private: + friend class NotifyTargetChangeRunnable; + + /** + * Matches the nsIBackgroundFileSaver::observer property. + * + * @remarks This is a strong reference so that JavaScript callers don't need + * to worry about keeping another reference to the observer. + */ + nsCOMPtr<nsIBackgroundFileSaverObserver> mObserver; + + ////////////////////////////////////////////////////////////////////////////// + //// Shared state between control and worker threads + + /** + * Protects the shared state between control and worker threads. This mutex + * is always locked for a very short time, never during input/output. + */ + mozilla::Mutex mLock{"BackgroundFileSaver.mLock"}; + + /** + * True if the worker thread is already waiting to process a change in state. + */ + bool mWorkerThreadAttentionRequested MOZ_GUARDED_BY(mLock){false}; + + /** + * True if the operation should finish as soon as possibile. + */ + bool mFinishRequested MOZ_GUARDED_BY(mLock){false}; + + /** + * True if the operation completed, with either success or failure. + */ + bool mComplete MOZ_GUARDED_BY(mLock){false}; + + /** + * Holds the current file saver status. This is a success status while the + * object is working correctly, and remains such if the operation completes + * successfully. This becomes an error status when an error occurs on the + * worker thread, or when the operation is canceled. + */ + nsresult mStatus MOZ_GUARDED_BY(mLock){NS_OK}; + + /** + * True if we should append data to the initial target file, instead of + * overwriting it. + */ + bool mAppend MOZ_GUARDED_BY(mLock){false}; + + /** + * This is set by the first SetTarget call on the control thread, and contains + * the target file name that will be used by the worker thread, as soon as it + * is possible to update mActualTarget and open the file. This is null if no + * target was ever assigned to this object. + */ + nsCOMPtr<nsIFile> mInitialTarget MOZ_GUARDED_BY(mLock); + + /** + * This is set by the first SetTarget call on the control thread, and + * indicates whether mInitialTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mInitialTargetKeepPartial MOZ_GUARDED_BY(mLock){false}; + + /** + * This is set by subsequent SetTarget calls on the control thread, and + * contains the new target file name to which the worker thread will move the + * target file, as soon as it can be done. This is null if SetTarget was + * called only once, or no target was ever assigned to this object. + * + * The target file can be renamed multiple times, though only the most recent + * rename is guaranteed to be processed by the worker thread. + */ + nsCOMPtr<nsIFile> mRenamedTarget MOZ_GUARDED_BY(mLock); + + /** + * This is set by subsequent SetTarget calls on the control thread, and + * indicates whether mRenamedTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mRenamedTargetKeepPartial MOZ_GUARDED_BY(mLock){false}; + + /** + * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise. + * This is read by both threads but only written by the worker thread. + */ + nsCOMPtr<nsISupports> mAsyncCopyContext MOZ_GUARDED_BY(mLock); + + /** + * The SHA 256 hash in raw bytes of the downloaded file. This is written + * by the worker thread but can be read on the main thread. + */ + nsCString mSha256 MOZ_GUARDED_BY(mLock); + + /** + * Whether or not to compute the hash. Must be set on the main thread before + * setTarget is called. + */ + bool mSha256Enabled MOZ_GUARDED_BY(mLock){false}; + + /** + * Store the signature info. + */ + nsTArray<nsTArray<nsTArray<uint8_t>>> mSignatureInfo MOZ_GUARDED_BY(mLock); + + /** + * Whether or not to extract the signature. Must be set on the main thread + * before setTarget is called. + */ + bool mSignatureInfoEnabled MOZ_GUARDED_BY(mLock){false}; + + ////////////////////////////////////////////////////////////////////////////// + //// State handled exclusively by the worker thread + + /** + * Current target file associated to the input and output streams. + */ + nsCOMPtr<nsIFile> mActualTarget; + + /** + * Indicates whether mActualTarget should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. + */ + bool mActualTargetKeepPartial{false}; + + /** + * Used to calculate the file hash. This keeps state across file renames and + * is lazily initialized in ProcessStateChange. + */ + Maybe<Digest> mDigest; + + ////////////////////////////////////////////////////////////////////////////// + //// Private methods + + /** + * Called when NS_AsyncCopy completes. + * + * @param aClosure + * Populated with a raw pointer to the BackgroundFileSaver object. + * @param aStatus + * Success or failure status specified when the copy was interrupted. + */ + static void AsyncCopyCallback(void* aClosure, nsresult aStatus); + + /** + * Called on the control thread after state changes, to ensure that the worker + * thread will process the state change appropriately. + * + * @param aShouldInterruptCopy + * If true, the current NS_AsyncCopy, if any, is canceled. + */ + nsresult GetWorkerThreadAttention(bool aShouldInterruptCopy); + + /** + * Event called on the worker thread to begin processing a state change. + */ + nsresult ProcessAttention(); + + /** + * Called by ProcessAttention to execute the operations corresponding to the + * state change. If this results in an error, ProcessAttention will force the + * entire operation to be aborted. + */ + nsresult ProcessStateChange(); + + /** + * Returns true if completion conditions are met on the worker thread. The + * first time this happens, posts the completion event to the control thread. + */ + bool CheckCompletion(); + + /** + * Event called on the control thread to indicate that file contents will now + * be saved to the specified file. + */ + nsresult NotifyTargetChange(nsIFile* aTarget); + + /** + * Event called on the control thread to send the final notification. + */ + nsresult NotifySaveComplete(); + + /** + * Verifies the signature of the binary at the specified file path and stores + * the signature data in mSignatureInfo. We extract only X.509 certificates, + * since that is what Google's Safebrowsing protocol specifies. + */ + nsresult ExtractSignatureInfo(const nsAString& filePath); +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverOutputStream + +class BackgroundFileSaverOutputStream : public BackgroundFileSaver, + public nsIAsyncOutputStream, + public nsIOutputStreamCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + NS_DECL_NSIOUTPUTSTREAMCALLBACK + + BackgroundFileSaverOutputStream(); + + protected: + virtual bool HasInfiniteBuffer() override; + virtual nsAsyncCopyProgressFun GetProgressCallback() override; + + private: + ~BackgroundFileSaverOutputStream() = default; + + /** + * Original callback provided to our AsyncWait wrapper. + */ + nsCOMPtr<nsIOutputStreamCallback> mAsyncWaitCallback; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverStreamListener. This class is instantiated by +// nsExternalHelperAppService, DownloadCore.sys.mjs, and possibly others. + +class BackgroundFileSaverStreamListener final : public BackgroundFileSaver, + public nsIStreamListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + BackgroundFileSaverStreamListener() = default; + + protected: + virtual bool HasInfiniteBuffer() override; + virtual nsAsyncCopyProgressFun GetProgressCallback() override; + + private: + ~BackgroundFileSaverStreamListener() = default; + + /** + * Protects the state related to whether the request should be suspended. + */ + mozilla::Mutex mSuspensionLock{ + "BackgroundFileSaverStreamListener.mSuspensionLock"}; + + /** + * Whether we should suspend the request because we received too much data. + */ + bool mReceivedTooMuchData MOZ_GUARDED_BY(mSuspensionLock){false}; + + /** + * Request for which we received too much data. This is populated when + * mReceivedTooMuchData becomes true for the first time. + */ + nsCOMPtr<nsIRequest> mRequest MOZ_GUARDED_BY(mSuspensionLock); + + /** + * Whether mRequest is currently suspended. + */ + bool mRequestSuspended MOZ_GUARDED_BY(mSuspensionLock){false}; + + /** + * Called while NS_AsyncCopy is copying data. + */ + static void AsyncCopyProgressCallback(void* aClosure, uint32_t aCount); + + /** + * Called on the control thread to suspend or resume the request. + */ + nsresult NotifySuspendOrResume(); +}; + +// A wrapper around nsIOutputStream, so that we can compute hashes on the +// stream without copying and without polluting pristine NSS code with XPCOM +// interfaces. +class DigestOutputStream : public nsIOutputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + // Constructor. Neither parameter may be null. The caller owns both. + DigestOutputStream(nsIOutputStream* aStream, Digest& aDigest); + + private: + virtual ~DigestOutputStream() = default; + + // Calls to write are passed to this stream. + nsCOMPtr<nsIOutputStream> mOutputStream; + // Digest used to compute the hash, owned by the caller. + Digest& mDigest; + + // Don't accidentally copy construct. + DigestOutputStream(const DigestOutputStream& d) = delete; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/CacheInfoIPCTypes.h b/netwerk/base/CacheInfoIPCTypes.h new file mode 100644 index 0000000000..b3d2277335 --- /dev/null +++ b/netwerk/base/CacheInfoIPCTypes.h @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_CacheInfoIPCTypes_h_ +#define mozilla_net_CacheInfoIPCTypes_h_ + +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "nsICacheInfoChannel.h" + +namespace IPC { + +template <> +struct ParamTraits<nsICacheInfoChannel::PreferredAlternativeDataDeliveryType> + : public ContiguousEnumSerializerInclusive< + nsICacheInfoChannel::PreferredAlternativeDataDeliveryType, + nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::NONE, + nsICacheInfoChannel::PreferredAlternativeDataDeliveryType:: + SERIALIZE> {}; + +} // namespace IPC + +#endif // mozilla_net_CacheInfoIPCTypes_h_ diff --git a/netwerk/base/CaptivePortalService.cpp b/netwerk/base/CaptivePortalService.cpp new file mode 100644 index 0000000000..97a1dde929 --- /dev/null +++ b/netwerk/base/CaptivePortalService.cpp @@ -0,0 +1,428 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/net/CaptivePortalService.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Services.h" +#include "mozilla/Preferences.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsXULAppAPI.h" +#include "xpcpublic.h" +#include "xpcprivate.h" + +static constexpr auto kInterfaceName = u"captive-portal-inteface"_ns; + +static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login"; +static const char kAbortCaptivePortalLoginEvent[] = + "captive-portal-login-abort"; +static const char kCaptivePortalLoginSuccessEvent[] = + "captive-portal-login-success"; + +namespace mozilla { +namespace net { + +static LazyLogModule gCaptivePortalLog("CaptivePortalService"); +#undef LOG +#define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args) + +NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver, + nsISupportsWeakReference, nsITimerCallback, + nsICaptivePortalCallback, nsINamed) + +static StaticRefPtr<CaptivePortalService> gCPService; + +// static +already_AddRefed<nsICaptivePortalService> CaptivePortalService::GetSingleton() { + if (gCPService) { + return do_AddRef(gCPService); + } + + gCPService = new CaptivePortalService(); + ClearOnShutdown(&gCPService); + return do_AddRef(gCPService); +} + +CaptivePortalService::CaptivePortalService() { + mLastChecked = TimeStamp::Now(); +} + +CaptivePortalService::~CaptivePortalService() { + LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n", + XRE_GetProcessType() == GeckoProcessType_Default)); +} + +nsresult CaptivePortalService::PerformCheck() { + LOG( + ("CaptivePortalService::PerformCheck mRequestInProgress:%d " + "mInitialized:%d mStarted:%d\n", + mRequestInProgress, mInitialized, mStarted)); + // Don't issue another request if last one didn't complete + if (mRequestInProgress || !mInitialized || !mStarted) { + return NS_OK; + } + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + // Instantiating CaptiveDetect.sys.mjs before the JS engine is ready will + // lead to a crash (see bug 1800603) + // We can remove this restriction when we rewrite the detector in + // C++ or rust (bug 1809886). + if (!XPCJSRuntime::Get()) { + return NS_ERROR_NOT_INITIALIZED; + } + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + nsresult rv; + if (!mCaptivePortalDetector) { + mCaptivePortalDetector = + do_CreateInstance("@mozilla.org/toolkit/captive-detector;1", &rv); + if (NS_FAILED(rv)) { + LOG(("Unable to get a captive portal detector\n")); + return rv; + } + } + + LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n")); + mRequestInProgress = true; + mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this); + return NS_OK; +} + +nsresult CaptivePortalService::RearmTimer() { + LOG(("CaptivePortalService::RearmTimer\n")); + // Start a timer to recheck + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + if (mTimer) { + mTimer->Cancel(); + } + + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + mTimer = nullptr; + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + // If we have successfully determined the state, and we have never detected + // a captive portal, we don't need to keep polling, but will rely on events + // to trigger detection. + if (mState == NOT_CAPTIVE) { + return NS_OK; + } + + if (!mTimer) { + mTimer = NS_NewTimer(); + } + + if (mTimer && mDelay > 0) { + LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay)); + return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT); + } + + return NS_OK; +} + +nsresult CaptivePortalService::Initialize() { + if (mInitialized) { + return NS_OK; + } + mInitialized = true; + + // Only the main process service should actually do anything. The service in + // the content process only mirrors the CP state in the main process. + if (XRE_GetProcessType() != GeckoProcessType_Default) { + return NS_OK; + } + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true); + observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true); + observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true); + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + } + + LOG(("Initialized CaptivePortalService\n")); + return NS_OK; +} + +nsresult CaptivePortalService::Start() { + if (!mInitialized) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (xpc::AreNonLocalConnectionsDisabled() && + !Preferences::GetBool("network.captive-portal-service.testMode", false)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything if called in the content process. + return NS_OK; + } + + if (mStarted) { + return NS_OK; + } + + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN"); + mStarted = true; + mEverBeenCaptive = false; + + // Get the delay prefs + Preferences::GetUint("network.captive-portal-service.minInterval", + &mMinInterval); + Preferences::GetUint("network.captive-portal-service.maxInterval", + &mMaxInterval); + Preferences::GetFloat("network.captive-portal-service.backoffFactor", + &mBackoffFactor); + + LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", mMinInterval, + mMaxInterval, mBackoffFactor)); + + mSlackCount = 0; + mDelay = mMinInterval; + + // When Start is called, perform a check immediately + PerformCheck(); + RearmTimer(); + return NS_OK; +} + +nsresult CaptivePortalService::Stop() { + LOG(("CaptivePortalService::Stop\n")); + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything when called in the content process. + return NS_OK; + } + + if (!mStarted) { + return NS_OK; + } + + if (mTimer) { + mTimer->Cancel(); + } + mTimer = nullptr; + mRequestInProgress = false; + mStarted = false; + mEverBeenCaptive = false; + if (mCaptivePortalDetector) { + mCaptivePortalDetector->Abort(kInterfaceName); + } + mCaptivePortalDetector = nullptr; + + // Clear the state in case anyone queries the state while detection is off. + mState = UNKNOWN; + return NS_OK; +} + +void CaptivePortalService::SetStateInChild(int32_t aState) { + // This should only be called in the content process, from ContentChild.cpp + // in order to mirror the captive portal state set in the chrome process. + MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default); + + mState = aState; + mLastChecked = TimeStamp::Now(); +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsICaptivePortalService +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +CaptivePortalService::GetState(int32_t* aState) { + *aState = mState; + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::RecheckCaptivePortal() { + LOG(("CaptivePortalService::RecheckCaptivePortal\n")); + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything if called in the content process. + return NS_OK; + } + + // This is called for user activity. We need to reset the slack count, + // so the checks continue to be quite frequent. + mSlackCount = 0; + mDelay = mMinInterval; + + PerformCheck(); + RearmTimer(); + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::GetLastChecked(uint64_t* aLastChecked) { + double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds(); + *aLastChecked = static_cast<uint64_t>(duration); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsITimer +// This callback gets called every mDelay miliseconds +// It issues a checkCaptivePortal operation if one isn't already in progress +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Notify(nsITimer* aTimer) { + LOG(("CaptivePortalService::Notify\n")); + MOZ_ASSERT(aTimer == mTimer); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + + PerformCheck(); + + // This is needed because we don't want to always make requests very often. + // Every 10 checks, we the delay is increased mBackoffFactor times + // to a maximum delay of mMaxInterval + mSlackCount++; + if (mSlackCount % 10 == 0) { + mDelay = mDelay * mBackoffFactor; + } + if (mDelay > mMaxInterval) { + mDelay = mMaxInterval; + } + + // Note - if mDelay is 0, the timer will not be rearmed. + RearmTimer(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsINamed +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +CaptivePortalService::GetName(nsACString& aName) { + aName.AssignLiteral("CaptivePortalService"); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsIObserver +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything if called in the content process. + return NS_OK; + } + + LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic)); + if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) { + // A redirect or altered content has been detected. + // The user needs to log in. We are in a captive portal. + StateTransition(LOCKED_PORTAL); + mLastChecked = TimeStamp::Now(); + mEverBeenCaptive = true; + } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) { + // The user has successfully logged in. We have connectivity. + StateTransition(UNLOCKED_PORTAL); + mLastChecked = TimeStamp::Now(); + mSlackCount = 0; + mDelay = mMinInterval; + + RearmTimer(); + } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) { + // The login has been aborted + StateTransition(UNKNOWN); + mLastChecked = TimeStamp::Now(); + mSlackCount = 0; + } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + Stop(); + return NS_OK; + } + + // Send notification so that the captive portal state is mirrored in the + // content process. + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + if (observerService) { + nsCOMPtr<nsICaptivePortalService> cps(this); + observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE, + nullptr); + } + + return NS_OK; +} + +void CaptivePortalService::NotifyConnectivityAvailable(bool aCaptive) { + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + if (observerService) { + nsCOMPtr<nsICaptivePortalService> cps(this); + observerService->NotifyObservers(cps, NS_CAPTIVE_PORTAL_CONNECTIVITY, + aCaptive ? u"captive" : u"clear"); + } +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsICaptivePortalCallback +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Prepare() { + LOG(("CaptivePortalService::Prepare\n")); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return NS_OK; + } + // XXX: Finish preparation shouldn't be called until dns and routing is + // available. + if (mCaptivePortalDetector) { + mCaptivePortalDetector->FinishPreparation(kInterfaceName); + } + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::Complete(bool success) { + LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success, + mState)); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + mLastChecked = TimeStamp::Now(); + + // Note: this callback gets called when: + // 1. the request is completed, and content is valid (success == true) + // 2. when the request is aborted or times out (success == false) + + if (success) { + if (mEverBeenCaptive) { + StateTransition(UNLOCKED_PORTAL); + NotifyConnectivityAvailable(true); + } else { + StateTransition(NOT_CAPTIVE); + NotifyConnectivityAvailable(false); + } + } + + mRequestInProgress = false; + return NS_OK; +} + +void CaptivePortalService::StateTransition(int32_t aNewState) { + int32_t oldState = mState; + mState = aNewState; + + if ((oldState == UNKNOWN && mState == NOT_CAPTIVE) || + (oldState == LOCKED_PORTAL && mState == UNLOCKED_PORTAL)) { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (observerService) { + nsCOMPtr<nsICaptivePortalService> cps(this); + observerService->NotifyObservers( + cps, NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED, nullptr); + } + } +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/CaptivePortalService.h b/netwerk/base/CaptivePortalService.h new file mode 100644 index 0000000000..f0a7200b4b --- /dev/null +++ b/netwerk/base/CaptivePortalService.h @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CaptivePortalService_h_ +#define CaptivePortalService_h_ + +#include "nsICaptivePortalService.h" +#include "nsICaptivePortalDetector.h" +#include "nsINamed.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsITimer.h" +#include "nsCOMArray.h" +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace net { + +class CaptivePortalService : public nsICaptivePortalService, + public nsIObserver, + public nsSupportsWeakReference, + public nsITimerCallback, + public nsICaptivePortalCallback, + public nsINamed { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICAPTIVEPORTALSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSICAPTIVEPORTALCALLBACK + NS_DECL_NSINAMED + + nsresult Initialize(); + nsresult Start(); + nsresult Stop(); + + static already_AddRefed<nsICaptivePortalService> GetSingleton(); + + // This method is only called in the content process, in order to mirror + // the captive portal state in the parent process. + void SetStateInChild(int32_t aState); + + private: + static const uint32_t kDefaultInterval = 60 * 1000; // check every 60 seconds + + CaptivePortalService(); + virtual ~CaptivePortalService(); + nsresult PerformCheck(); + nsresult RearmTimer(); + void NotifyConnectivityAvailable(bool aCaptive); + + nsCOMPtr<nsICaptivePortalDetector> mCaptivePortalDetector; + int32_t mState{UNKNOWN}; + + nsCOMPtr<nsITimer> mTimer; + bool mStarted{false}; + bool mInitialized{false}; + bool mRequestInProgress{false}; + bool mEverBeenCaptive{false}; + + uint32_t mDelay{kDefaultInterval}; + int32_t mSlackCount{0}; + + uint32_t mMinInterval{kDefaultInterval}; + uint32_t mMaxInterval{25 * kDefaultInterval}; + float mBackoffFactor{5.0}; + + void StateTransition(int32_t aNewState); + + // This holds a timestamp when the last time when the captive portal check + // has changed state. + mozilla::TimeStamp mLastChecked; +}; + +} // namespace net +} // namespace mozilla + +#endif // CaptivePortalService_h_ diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp new file mode 100644 index 0000000000..bc1619ee60 --- /dev/null +++ b/netwerk/base/Dashboard.cpp @@ -0,0 +1,1182 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http:mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/NetDashboardBinding.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/net/Dashboard.h" +#include "mozilla/net/HttpInfo.h" +#include "mozilla/net/HTTPSSVC.h" +#include "mozilla/net/SocketProcessParent.h" +#include "nsHttp.h" +#include "nsICancelable.h" +#include "nsIDNSListener.h" +#include "nsIDNSService.h" +#include "nsIDNSRecord.h" +#include "nsIDNSByTypeRecord.h" +#include "nsIInputStream.h" +#include "nsINamed.h" +#include "nsINetAddr.h" +#include "nsISocketTransport.h" +#include "nsProxyRelease.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "nsURLHelper.h" +#include "mozilla/Logging.h" +#include "nsIOService.h" +#include "../cache2/CacheFileUtils.h" + +using mozilla::AutoSafeJSContext; +using mozilla::dom::Sequence; +using mozilla::dom::ToJSValue; + +namespace mozilla { +namespace net { + +class SocketData : public nsISupports { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + SocketData() = default; + + uint64_t mTotalSent{0}; + uint64_t mTotalRecv{0}; + nsTArray<SocketInfo> mData; + nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback; + nsIEventTarget* mEventTarget{nullptr}; + + private: + virtual ~SocketData() = default; +}; + +static void GetErrorString(nsresult rv, nsAString& errorString); + +NS_IMPL_ISUPPORTS0(SocketData) + +class HttpData : public nsISupports { + virtual ~HttpData() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + HttpData() = default; + + nsTArray<HttpRetParams> mData; + nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback; + nsIEventTarget* mEventTarget{nullptr}; +}; + +NS_IMPL_ISUPPORTS0(HttpData) + +class WebSocketRequest : public nsISupports { + virtual ~WebSocketRequest() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + WebSocketRequest() = default; + + nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback; + nsIEventTarget* mEventTarget{nullptr}; +}; + +NS_IMPL_ISUPPORTS0(WebSocketRequest) + +class DnsData : public nsISupports { + virtual ~DnsData() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + DnsData() = default; + + nsTArray<DNSCacheEntries> mData; + nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback; + nsIEventTarget* mEventTarget{nullptr}; +}; + +NS_IMPL_ISUPPORTS0(DnsData) + +class ConnectionData : public nsITransportEventSink, + public nsITimerCallback, + public nsINamed { + virtual ~ConnectionData() { + if (mTimer) { + mTimer->Cancel(); + } + } + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORTEVENTSINK + NS_DECL_NSITIMERCALLBACK + + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("net::ConnectionData"); + return NS_OK; + } + + void StartTimer(uint32_t aTimeout); + void StopTimer(); + + explicit ConnectionData(Dashboard* target) { mDashboard = target; } + + nsCOMPtr<nsISocketTransport> mSocket; + nsCOMPtr<nsIInputStream> mStreamIn; + nsCOMPtr<nsITimer> mTimer; + nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback; + nsIEventTarget* mEventTarget{nullptr}; + Dashboard* mDashboard; + + nsCString mHost; + uint32_t mPort{0}; + nsCString mProtocol; + uint32_t mTimeout{0}; + + nsString mStatus; +}; + +NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback, + nsINamed) + +class RcwnData : public nsISupports { + virtual ~RcwnData() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + RcwnData() = default; + + nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback; + nsIEventTarget* mEventTarget{nullptr}; +}; + +NS_IMPL_ISUPPORTS0(RcwnData) + +NS_IMETHODIMP +ConnectionData::OnTransportStatus(nsITransport* aTransport, nsresult aStatus, + int64_t aProgress, int64_t aProgressMax) { + if (aStatus == NS_NET_STATUS_CONNECTED_TO) { + StopTimer(); + } + + GetErrorString(aStatus, mStatus); + mEventTarget->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>( + "net::Dashboard::GetConnectionStatus", mDashboard, + &Dashboard::GetConnectionStatus, this), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +NS_IMETHODIMP +ConnectionData::Notify(nsITimer* aTimer) { + MOZ_ASSERT(aTimer == mTimer); + + if (mSocket) { + mSocket->Close(NS_ERROR_ABORT); + mSocket = nullptr; + mStreamIn = nullptr; + } + + mTimer = nullptr; + + mStatus.AssignLiteral(u"NS_ERROR_NET_TIMEOUT"); + mEventTarget->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>( + "net::Dashboard::GetConnectionStatus", mDashboard, + &Dashboard::GetConnectionStatus, this), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +void ConnectionData::StartTimer(uint32_t aTimeout) { + if (!mTimer) { + mTimer = NS_NewTimer(); + } + + mTimer->InitWithCallback(this, aTimeout * 1000, nsITimer::TYPE_ONE_SHOT); +} + +void ConnectionData::StopTimer() { + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } +} + +class LookupHelper; + +class LookupArgument : public nsISupports { + virtual ~LookupArgument() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + LookupArgument(nsIDNSRecord* aRecord, LookupHelper* aHelper) { + mRecord = aRecord; + mHelper = aHelper; + } + + nsCOMPtr<nsIDNSRecord> mRecord; + RefPtr<LookupHelper> mHelper; +}; + +NS_IMPL_ISUPPORTS0(LookupArgument) + +class LookupHelper final : public nsIDNSListener { + virtual ~LookupHelper() { + if (mCancel) { + mCancel->Cancel(NS_ERROR_ABORT); + } + } + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + LookupHelper() = default; + + nsresult ConstructAnswer(LookupArgument* aArgument); + nsresult ConstructHTTPSRRAnswer(LookupArgument* aArgument); + + public: + nsCOMPtr<nsICancelable> mCancel; + nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback; + nsIEventTarget* mEventTarget{nullptr}; + nsresult mStatus{NS_ERROR_NOT_INITIALIZED}; +}; + +NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener) + +NS_IMETHODIMP +LookupHelper::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord, + nsresult aStatus) { + MOZ_ASSERT(aRequest == mCancel); + mCancel = nullptr; + mStatus = aStatus; + + nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord = do_QueryInterface(aRecord); + if (httpsRecord) { + RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this); + mEventTarget->Dispatch( + NewRunnableMethod<RefPtr<LookupArgument>>( + "net::LookupHelper::ConstructHTTPSRRAnswer", this, + &LookupHelper::ConstructHTTPSRRAnswer, arg), + NS_DISPATCH_NORMAL); + return NS_OK; + } + + RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this); + mEventTarget->Dispatch(NewRunnableMethod<RefPtr<LookupArgument>>( + "net::LookupHelper::ConstructAnswer", this, + &LookupHelper::ConstructAnswer, arg), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +nsresult LookupHelper::ConstructAnswer(LookupArgument* aArgument) { + nsIDNSRecord* aRecord = aArgument->mRecord; + AutoSafeJSContext cx; + + mozilla::dom::DNSLookupDict dict; + dict.mAddress.Construct(); + + Sequence<nsString>& addresses = dict.mAddress.Value(); + nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(aRecord); + if (NS_SUCCEEDED(mStatus) && record) { + dict.mAnswer = true; + bool hasMore; + record->HasMore(&hasMore); + while (hasMore) { + nsString* nextAddress = addresses.AppendElement(fallible); + if (!nextAddress) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCString nextAddressASCII; + record->GetNextAddrAsString(nextAddressASCII); + CopyASCIItoUTF16(nextAddressASCII, *nextAddress); + record->HasMore(&hasMore); + } + } else { + dict.mAnswer = false; + GetErrorString(mStatus, dict.mError); + } + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + + this->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +static void CStringToHexString(const nsACString& aIn, nsAString& aOut) { + static const char* const lut = "0123456789ABCDEF"; + + size_t len = aIn.Length(); + + aOut.SetCapacity(2 * len); + for (size_t i = 0; i < aIn.Length(); ++i) { + const char c = static_cast<char>(aIn[i]); + aOut.Append(lut[(c >> 4) & 0x0F]); + aOut.Append(lut[c & 15]); + } +} + +nsresult LookupHelper::ConstructHTTPSRRAnswer(LookupArgument* aArgument) { + nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord = + do_QueryInterface(aArgument->mRecord); + + AutoSafeJSContext cx; + + mozilla::dom::HTTPSRRLookupDict dict; + dict.mRecords.Construct(); + + Sequence<dom::HTTPSRecord>& records = dict.mRecords.Value(); + if (NS_SUCCEEDED(mStatus) && httpsRecord) { + dict.mAnswer = true; + nsTArray<RefPtr<nsISVCBRecord>> svcbRecords; + httpsRecord->GetRecords(svcbRecords); + + for (const auto& record : svcbRecords) { + dom::HTTPSRecord* nextRecord = records.AppendElement(fallible); + if (!nextRecord) { + return NS_ERROR_OUT_OF_MEMORY; + } + + Unused << record->GetPriority(&nextRecord->mPriority); + nsCString name; + Unused << record->GetName(name); + CopyASCIItoUTF16(name, nextRecord->mTargetName); + + nsTArray<RefPtr<nsISVCParam>> values; + Unused << record->GetValues(values); + if (values.IsEmpty()) { + continue; + } + + for (const auto& value : values) { + uint16_t type; + Unused << value->GetType(&type); + switch (type) { + case SvcParamKeyAlpn: { + nextRecord->mAlpn.Construct(); + nextRecord->mAlpn.Value().mType = type; + nsCOMPtr<nsISVCParamAlpn> alpnParam = do_QueryInterface(value); + nsTArray<nsCString> alpn; + Unused << alpnParam->GetAlpn(alpn); + nsAutoCString alpnStr; + for (const auto& str : alpn) { + alpnStr.Append(str); + alpnStr.Append(','); + } + CopyASCIItoUTF16(Span(alpnStr.BeginReading(), alpnStr.Length() - 1), + nextRecord->mAlpn.Value().mAlpn); + break; + } + case SvcParamKeyNoDefaultAlpn: { + nextRecord->mNoDefaultAlpn.Construct(); + nextRecord->mNoDefaultAlpn.Value().mType = type; + break; + } + case SvcParamKeyPort: { + nextRecord->mPort.Construct(); + nextRecord->mPort.Value().mType = type; + nsCOMPtr<nsISVCParamPort> portParam = do_QueryInterface(value); + Unused << portParam->GetPort(&nextRecord->mPort.Value().mPort); + break; + } + case SvcParamKeyIpv4Hint: { + nextRecord->mIpv4Hint.Construct(); + nextRecord->mIpv4Hint.Value().mType = type; + nsCOMPtr<nsISVCParamIPv4Hint> ipv4Param = do_QueryInterface(value); + nsTArray<RefPtr<nsINetAddr>> ipv4Hint; + Unused << ipv4Param->GetIpv4Hint(ipv4Hint); + if (!ipv4Hint.IsEmpty()) { + nextRecord->mIpv4Hint.Value().mAddress.Construct(); + for (const auto& address : ipv4Hint) { + nsString* nextAddress = nextRecord->mIpv4Hint.Value() + .mAddress.Value() + .AppendElement(fallible); + if (!nextAddress) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCString addressASCII; + Unused << address->GetAddress(addressASCII); + CopyASCIItoUTF16(addressASCII, *nextAddress); + } + } + break; + } + case SvcParamKeyIpv6Hint: { + nextRecord->mIpv6Hint.Construct(); + nextRecord->mIpv6Hint.Value().mType = type; + nsCOMPtr<nsISVCParamIPv6Hint> ipv6Param = do_QueryInterface(value); + nsTArray<RefPtr<nsINetAddr>> ipv6Hint; + Unused << ipv6Param->GetIpv6Hint(ipv6Hint); + if (!ipv6Hint.IsEmpty()) { + nextRecord->mIpv6Hint.Value().mAddress.Construct(); + for (const auto& address : ipv6Hint) { + nsString* nextAddress = nextRecord->mIpv6Hint.Value() + .mAddress.Value() + .AppendElement(fallible); + if (!nextAddress) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCString addressASCII; + Unused << address->GetAddress(addressASCII); + CopyASCIItoUTF16(addressASCII, *nextAddress); + } + } + break; + } + case SvcParamKeyEchConfig: { + nextRecord->mEchConfig.Construct(); + nextRecord->mEchConfig.Value().mType = type; + nsCOMPtr<nsISVCParamEchConfig> echConfigParam = + do_QueryInterface(value); + nsCString echConfigStr; + Unused << echConfigParam->GetEchconfig(echConfigStr); + CStringToHexString(echConfigStr, + nextRecord->mEchConfig.Value().mEchConfig); + break; + } + case SvcParamKeyODoHConfig: { + nextRecord->mODoHConfig.Construct(); + nextRecord->mODoHConfig.Value().mType = type; + nsCOMPtr<nsISVCParamODoHConfig> ODoHConfigParam = + do_QueryInterface(value); + nsCString ODoHConfigStr; + Unused << ODoHConfigParam->GetODoHConfig(ODoHConfigStr); + CStringToHexString(ODoHConfigStr, + nextRecord->mODoHConfig.Value().mODoHConfig); + break; + } + default: + break; + } + } + } + } else { + dict.mAnswer = false; + GetErrorString(mStatus, dict.mError); + } + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + + this->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(Dashboard, nsIDashboard, nsIDashboardEventNotifier) + +Dashboard::Dashboard() { mEnableLogging = false; } + +NS_IMETHODIMP +Dashboard::RequestSockets(nsINetDashboardCallback* aCallback) { + RefPtr<SocketData> socketData = new SocketData(); + socketData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>( + "nsINetDashboardCallback", aCallback, true); + socketData->mEventTarget = GetCurrentSerialEventTarget(); + + if (nsIOService::UseSocketProcess()) { + if (!gIOService->SocketProcessReady()) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<Dashboard> self(this); + SocketProcessParent::GetSingleton()->SendGetSocketData()->Then( + GetMainThreadSerialEventTarget(), __func__, + [self{std::move(self)}, + socketData{std::move(socketData)}](SocketDataArgs&& args) { + socketData->mData.Assign(args.info()); + socketData->mTotalSent = args.totalSent(); + socketData->mTotalRecv = args.totalRecv(); + socketData->mEventTarget->Dispatch( + NewRunnableMethod<RefPtr<SocketData>>( + "net::Dashboard::GetSockets", self, &Dashboard::GetSockets, + socketData), + NS_DISPATCH_NORMAL); + }, + [self](const mozilla::ipc::ResponseRejectReason) {}); + return NS_OK; + } + + gSocketTransportService->Dispatch( + NewRunnableMethod<RefPtr<SocketData>>( + "net::Dashboard::GetSocketsDispatch", this, + &Dashboard::GetSocketsDispatch, socketData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetSocketsDispatch(SocketData* aSocketData) { + RefPtr<SocketData> socketData = aSocketData; + if (gSocketTransportService) { + gSocketTransportService->GetSocketConnections(&socketData->mData); + socketData->mTotalSent = gSocketTransportService->GetSentBytes(); + socketData->mTotalRecv = gSocketTransportService->GetReceivedBytes(); + } + socketData->mEventTarget->Dispatch( + NewRunnableMethod<RefPtr<SocketData>>("net::Dashboard::GetSockets", this, + &Dashboard::GetSockets, socketData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetSockets(SocketData* aSocketData) { + RefPtr<SocketData> socketData = aSocketData; + AutoSafeJSContext cx; + + mozilla::dom::SocketsDict dict; + dict.mSockets.Construct(); + dict.mSent = 0; + dict.mReceived = 0; + + Sequence<mozilla::dom::SocketElement>& sockets = dict.mSockets.Value(); + + uint32_t length = socketData->mData.Length(); + if (!sockets.SetCapacity(length, fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < socketData->mData.Length(); i++) { + dom::SocketElement& mSocket = *sockets.AppendElement(fallible); + CopyASCIItoUTF16(socketData->mData[i].host, mSocket.mHost); + mSocket.mPort = socketData->mData[i].port; + mSocket.mActive = socketData->mData[i].active; + CopyASCIItoUTF16(socketData->mData[i].type, mSocket.mType); + mSocket.mSent = (double)socketData->mData[i].sent; + mSocket.mReceived = (double)socketData->mData[i].received; + dict.mSent += socketData->mData[i].sent; + dict.mReceived += socketData->mData[i].received; + } + + dict.mSent += socketData->mTotalSent; + dict.mReceived += socketData->mTotalRecv; + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE; + socketData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestHttpConnections(nsINetDashboardCallback* aCallback) { + RefPtr<HttpData> httpData = new HttpData(); + httpData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>( + "nsINetDashboardCallback", aCallback, true); + httpData->mEventTarget = GetCurrentSerialEventTarget(); + + if (nsIOService::UseSocketProcess()) { + if (!gIOService->SocketProcessReady()) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<Dashboard> self(this); + SocketProcessParent::GetSingleton()->SendGetHttpConnectionData()->Then( + GetMainThreadSerialEventTarget(), __func__, + [self{std::move(self)}, httpData](nsTArray<HttpRetParams>&& params) { + httpData->mData.Assign(std::move(params)); + self->GetHttpConnections(httpData); + httpData->mEventTarget->Dispatch( + NewRunnableMethod<RefPtr<HttpData>>( + "net::Dashboard::GetHttpConnections", self, + &Dashboard::GetHttpConnections, httpData), + NS_DISPATCH_NORMAL); + }, + [self](const mozilla::ipc::ResponseRejectReason) {}); + return NS_OK; + } + + gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<HttpData>>( + "net::Dashboard::GetHttpDispatch", this, + &Dashboard::GetHttpDispatch, httpData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetHttpDispatch(HttpData* aHttpData) { + RefPtr<HttpData> httpData = aHttpData; + HttpInfo::GetHttpConnectionData(&httpData->mData); + httpData->mEventTarget->Dispatch( + NewRunnableMethod<RefPtr<HttpData>>("net::Dashboard::GetHttpConnections", + this, &Dashboard::GetHttpConnections, + httpData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetHttpConnections(HttpData* aHttpData) { + RefPtr<HttpData> httpData = aHttpData; + AutoSafeJSContext cx; + + mozilla::dom::HttpConnDict dict; + dict.mConnections.Construct(); + + using mozilla::dom::DnsAndSockInfoDict; + using mozilla::dom::HttpConnectionElement; + using mozilla::dom::HttpConnInfo; + Sequence<HttpConnectionElement>& connections = dict.mConnections.Value(); + + uint32_t length = httpData->mData.Length(); + if (!connections.SetCapacity(length, fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < httpData->mData.Length(); i++) { + HttpConnectionElement& connection = *connections.AppendElement(fallible); + + CopyASCIItoUTF16(httpData->mData[i].host, connection.mHost); + connection.mPort = httpData->mData[i].port; + CopyASCIItoUTF16(httpData->mData[i].httpVersion, connection.mHttpVersion); + connection.mSsl = httpData->mData[i].ssl; + + connection.mActive.Construct(); + connection.mIdle.Construct(); + connection.mDnsAndSocks.Construct(); + + Sequence<HttpConnInfo>& active = connection.mActive.Value(); + Sequence<HttpConnInfo>& idle = connection.mIdle.Value(); + Sequence<DnsAndSockInfoDict>& dnsAndSocks = connection.mDnsAndSocks.Value(); + + if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) || + !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) || + !dnsAndSocks.SetCapacity(httpData->mData[i].dnsAndSocks.Length(), + fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t j = 0; j < httpData->mData[i].active.Length(); j++) { + HttpConnInfo& info = *active.AppendElement(fallible); + info.mRtt = httpData->mData[i].active[j].rtt; + info.mTtl = httpData->mData[i].active[j].ttl; + info.mProtocolVersion = httpData->mData[i].active[j].protocolVersion; + } + + for (uint32_t j = 0; j < httpData->mData[i].idle.Length(); j++) { + HttpConnInfo& info = *idle.AppendElement(fallible); + info.mRtt = httpData->mData[i].idle[j].rtt; + info.mTtl = httpData->mData[i].idle[j].ttl; + info.mProtocolVersion = httpData->mData[i].idle[j].protocolVersion; + } + + for (uint32_t j = 0; j < httpData->mData[i].dnsAndSocks.Length(); j++) { + DnsAndSockInfoDict& info = *dnsAndSocks.AppendElement(fallible); + info.mSpeculative = httpData->mData[i].dnsAndSocks[j].speculative; + } + } + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + + httpData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::GetEnableLogging(bool* value) { + *value = mEnableLogging; + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::SetEnableLogging(const bool value) { + mEnableLogging = value; + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::AddHost(const nsACString& aHost, uint32_t aSerial, bool aEncrypted) { + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + LogData mData(nsCString(aHost), aSerial, aEncrypted); + if (mWs.data.Contains(mData)) { + return NS_OK; + } + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mWs.data.AppendElement(mData); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::RemoveHost(const nsACString& aHost, uint32_t aSerial) { + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); + if (index == -1) return NS_ERROR_FAILURE; + mWs.data.RemoveElementAt(index); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::NewMsgSent(const nsACString& aHost, uint32_t aSerial, + uint32_t aLength) { + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); + if (index == -1) return NS_ERROR_FAILURE; + mWs.data[index].mMsgSent++; + mWs.data[index].mSizeSent += aLength; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::NewMsgReceived(const nsACString& aHost, uint32_t aSerial, + uint32_t aLength) { + if (mEnableLogging) { + mozilla::MutexAutoLock lock(mWs.lock); + int32_t index = mWs.IndexOf(nsCString(aHost), aSerial); + if (index == -1) return NS_ERROR_FAILURE; + mWs.data[index].mMsgReceived++; + mWs.data[index].mSizeReceived += aLength; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Dashboard::RequestWebsocketConnections(nsINetDashboardCallback* aCallback) { + RefPtr<WebSocketRequest> wsRequest = new WebSocketRequest(); + wsRequest->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>( + "nsINetDashboardCallback", aCallback, true); + wsRequest->mEventTarget = GetCurrentSerialEventTarget(); + + wsRequest->mEventTarget->Dispatch( + NewRunnableMethod<RefPtr<WebSocketRequest>>( + "net::Dashboard::GetWebSocketConnections", this, + &Dashboard::GetWebSocketConnections, wsRequest), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetWebSocketConnections(WebSocketRequest* aWsRequest) { + RefPtr<WebSocketRequest> wsRequest = aWsRequest; + AutoSafeJSContext cx; + + mozilla::dom::WebSocketDict dict; + dict.mWebsockets.Construct(); + Sequence<mozilla::dom::WebSocketElement>& websockets = + dict.mWebsockets.Value(); + + mozilla::MutexAutoLock lock(mWs.lock); + uint32_t length = mWs.data.Length(); + if (!websockets.SetCapacity(length, fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < mWs.data.Length(); i++) { + dom::WebSocketElement& websocket = *websockets.AppendElement(fallible); + CopyASCIItoUTF16(mWs.data[i].mHost, websocket.mHostport); + websocket.mMsgsent = mWs.data[i].mMsgSent; + websocket.mMsgreceived = mWs.data[i].mMsgReceived; + websocket.mSentsize = mWs.data[i].mSizeSent; + websocket.mReceivedsize = mWs.data[i].mSizeReceived; + websocket.mEncrypted = mWs.data[i].mEncrypted; + } + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + wsRequest->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestDNSInfo(nsINetDashboardCallback* aCallback) { + RefPtr<DnsData> dnsData = new DnsData(); + dnsData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>( + "nsINetDashboardCallback", aCallback, true); + + nsresult rv; + dnsData->mData.Clear(); + dnsData->mEventTarget = GetCurrentSerialEventTarget(); + + if (!mDnsService) { + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (nsIOService::UseSocketProcess()) { + if (!gIOService->SocketProcessReady()) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<Dashboard> self(this); + SocketProcessParent::GetSingleton()->SendGetDNSCacheEntries()->Then( + GetMainThreadSerialEventTarget(), __func__, + [self{std::move(self)}, + dnsData{std::move(dnsData)}](nsTArray<DNSCacheEntries>&& entries) { + dnsData->mData.Assign(std::move(entries)); + dnsData->mEventTarget->Dispatch( + NewRunnableMethod<RefPtr<DnsData>>( + "net::Dashboard::GetDNSCacheEntries", self, + &Dashboard::GetDNSCacheEntries, dnsData), + NS_DISPATCH_NORMAL); + }, + [self](const mozilla::ipc::ResponseRejectReason) {}); + return NS_OK; + } + + gSocketTransportService->Dispatch( + NewRunnableMethod<RefPtr<DnsData>>("net::Dashboard::GetDnsInfoDispatch", + this, &Dashboard::GetDnsInfoDispatch, + dnsData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetDnsInfoDispatch(DnsData* aDnsData) { + RefPtr<DnsData> dnsData = aDnsData; + if (mDnsService) { + mDnsService->GetDNSCacheEntries(&dnsData->mData); + } + dnsData->mEventTarget->Dispatch( + NewRunnableMethod<RefPtr<DnsData>>("net::Dashboard::GetDNSCacheEntries", + this, &Dashboard::GetDNSCacheEntries, + dnsData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult Dashboard::GetDNSCacheEntries(DnsData* dnsData) { + AutoSafeJSContext cx; + + mozilla::dom::DNSCacheDict dict; + dict.mEntries.Construct(); + Sequence<mozilla::dom::DnsCacheEntry>& entries = dict.mEntries.Value(); + + uint32_t length = dnsData->mData.Length(); + if (!entries.SetCapacity(length, fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < dnsData->mData.Length(); i++) { + dom::DnsCacheEntry& entry = *entries.AppendElement(fallible); + entry.mHostaddr.Construct(); + + Sequence<nsString>& addrs = entry.mHostaddr.Value(); + if (!addrs.SetCapacity(dnsData->mData[i].hostaddr.Length(), fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + CopyASCIItoUTF16(dnsData->mData[i].hostname, entry.mHostname); + entry.mExpiration = dnsData->mData[i].expiration; + entry.mTrr = dnsData->mData[i].TRR; + + for (uint32_t j = 0; j < dnsData->mData[i].hostaddr.Length(); j++) { + nsString* addr = addrs.AppendElement(fallible); + if (!addr) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + CopyASCIItoUTF16(dnsData->mData[i].hostaddr[j], *addr); + } + + if (dnsData->mData[i].family == PR_AF_INET6) { + entry.mFamily.AssignLiteral(u"ipv6"); + } else { + entry.mFamily.AssignLiteral(u"ipv4"); + } + + entry.mOriginAttributesSuffix = + NS_ConvertUTF8toUTF16(dnsData->mData[i].originAttributesSuffix); + entry.mFlags = NS_ConvertUTF8toUTF16(dnsData->mData[i].flags); + } + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + dnsData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestDNSLookup(const nsACString& aHost, + nsINetDashboardCallback* aCallback) { + nsresult rv; + + if (!mDnsService) { + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + RefPtr<LookupHelper> helper = new LookupHelper(); + helper->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>( + "nsINetDashboardCallback", aCallback, true); + helper->mEventTarget = GetCurrentSerialEventTarget(); + OriginAttributes attrs; + rv = mDnsService->AsyncResolveNative( + aHost, nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, helper.get(), + NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel)); + return rv; +} + +NS_IMETHODIMP +Dashboard::RequestDNSHTTPSRRLookup(const nsACString& aHost, + nsINetDashboardCallback* aCallback) { + nsresult rv; + + if (!mDnsService) { + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + RefPtr<LookupHelper> helper = new LookupHelper(); + helper->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>( + "nsINetDashboardCallback", aCallback, true); + helper->mEventTarget = GetCurrentSerialEventTarget(); + OriginAttributes attrs; + rv = mDnsService->AsyncResolveNative( + aHost, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, + nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, helper.get(), + NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel)); + return rv; +} + +NS_IMETHODIMP +Dashboard::RequestRcwnStats(nsINetDashboardCallback* aCallback) { + RefPtr<RcwnData> rcwnData = new RcwnData(); + rcwnData->mEventTarget = GetCurrentSerialEventTarget(); + rcwnData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>( + "nsINetDashboardCallback", aCallback, true); + + return rcwnData->mEventTarget->Dispatch( + NewRunnableMethod<RefPtr<RcwnData>>("net::Dashboard::GetRcwnData", this, + &Dashboard::GetRcwnData, rcwnData), + NS_DISPATCH_NORMAL); +} + +nsresult Dashboard::GetRcwnData(RcwnData* aData) { + AutoSafeJSContext cx; + mozilla::dom::RcwnStatus dict; + + dict.mTotalNetworkRequests = gIOService->GetTotalRequestNumber(); + dict.mRcwnCacheWonCount = gIOService->GetCacheWonRequestNumber(); + dict.mRcwnNetWonCount = gIOService->GetNetWonRequestNumber(); + + uint32_t cacheSlow, cacheNotSlow; + CacheFileUtils::CachePerfStats::GetSlowStats(&cacheSlow, &cacheNotSlow); + dict.mCacheSlowCount = cacheSlow; + dict.mCacheNotSlowCount = cacheNotSlow; + + dict.mPerfStats.Construct(); + Sequence<mozilla::dom::RcwnPerfStats>& perfStats = dict.mPerfStats.Value(); + uint32_t length = CacheFileUtils::CachePerfStats::LAST; + if (!perfStats.SetCapacity(length, fallible)) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < length; i++) { + CacheFileUtils::CachePerfStats::EDataType perfType = + static_cast<CacheFileUtils::CachePerfStats::EDataType>(i); + dom::RcwnPerfStats& elem = *perfStats.AppendElement(fallible); + elem.mAvgShort = + CacheFileUtils::CachePerfStats::GetAverage(perfType, false); + elem.mAvgLong = CacheFileUtils::CachePerfStats::GetAverage(perfType, true); + elem.mStddevLong = + CacheFileUtils::CachePerfStats::GetStdDev(perfType, true); + } + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + + aData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +void HttpConnInfo::SetHTTPProtocolVersion(HttpVersion pv) { + switch (pv) { + case HttpVersion::v0_9: + protocolVersion.AssignLiteral(u"http/0.9"); + break; + case HttpVersion::v1_0: + protocolVersion.AssignLiteral(u"http/1.0"); + break; + case HttpVersion::v1_1: + protocolVersion.AssignLiteral(u"http/1.1"); + break; + case HttpVersion::v2_0: + protocolVersion.AssignLiteral(u"http/2"); + break; + case HttpVersion::v3_0: + protocolVersion.AssignLiteral(u"http/3"); + break; + default: + protocolVersion.AssignLiteral(u"unknown protocol version"); + } +} + +NS_IMETHODIMP +Dashboard::GetLogPath(nsACString& aLogPath) { + aLogPath.SetLength(2048); + uint32_t len = LogModule::GetLogFile(aLogPath.BeginWriting(), 2048); + aLogPath.SetLength(len); + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestConnection(const nsACString& aHost, uint32_t aPort, + const char* aProtocol, uint32_t aTimeout, + nsINetDashboardCallback* aCallback) { + nsresult rv; + RefPtr<ConnectionData> connectionData = new ConnectionData(this); + connectionData->mHost = aHost; + connectionData->mPort = aPort; + connectionData->mProtocol = aProtocol; + connectionData->mTimeout = aTimeout; + + connectionData->mCallback = + new nsMainThreadPtrHolder<nsINetDashboardCallback>( + "nsINetDashboardCallback", aCallback, true); + connectionData->mEventTarget = GetCurrentSerialEventTarget(); + + rv = TestNewConnection(connectionData); + if (NS_FAILED(rv)) { + mozilla::net::GetErrorString(rv, connectionData->mStatus); + connectionData->mEventTarget->Dispatch( + NewRunnableMethod<RefPtr<ConnectionData>>( + "net::Dashboard::GetConnectionStatus", this, + &Dashboard::GetConnectionStatus, connectionData), + NS_DISPATCH_NORMAL); + return rv; + } + + return NS_OK; +} + +nsresult Dashboard::GetConnectionStatus(ConnectionData* aConnectionData) { + RefPtr<ConnectionData> connectionData = aConnectionData; + AutoSafeJSContext cx; + + mozilla::dom::ConnStatusDict dict; + dict.mStatus = connectionData->mStatus; + + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE; + + connectionData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +nsresult Dashboard::TestNewConnection(ConnectionData* aConnectionData) { + RefPtr<ConnectionData> connectionData = aConnectionData; + + nsresult rv; + if (!connectionData->mHost.Length() || + !net_IsValidHostName(connectionData->mHost)) { + return NS_ERROR_UNKNOWN_HOST; + } + + if (connectionData->mProtocol.EqualsLiteral("ssl")) { + AutoTArray<nsCString, 1> socketTypes = {connectionData->mProtocol}; + rv = gSocketTransportService->CreateTransport( + socketTypes, connectionData->mHost, connectionData->mPort, nullptr, + nullptr, getter_AddRefs(connectionData->mSocket)); + } else { + rv = gSocketTransportService->CreateTransport( + nsTArray<nsCString>(), connectionData->mHost, connectionData->mPort, + nullptr, nullptr, getter_AddRefs(connectionData->mSocket)); + } + if (NS_FAILED(rv)) { + return rv; + } + + rv = connectionData->mSocket->SetEventSink(connectionData, + GetCurrentSerialEventTarget()); + if (NS_FAILED(rv)) { + return rv; + } + + rv = connectionData->mSocket->OpenInputStream( + nsITransport::OPEN_BLOCKING, 0, 0, + getter_AddRefs(connectionData->mStreamIn)); + if (NS_FAILED(rv)) { + return rv; + } + + connectionData->StartTimer(connectionData->mTimeout); + + return rv; +} + +using ErrorEntry = struct { + nsresult key; + const char* error; +}; + +#undef ERROR +#define ERROR(key, val) \ + { key, #key } + +ErrorEntry socketTransportStatuses[] = { + ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)), + ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)), + ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)), + ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)), + ERROR(NS_NET_STATUS_TLS_HANDSHAKE_STARTING, FAILURE(12)), + ERROR(NS_NET_STATUS_TLS_HANDSHAKE_ENDED, FAILURE(13)), + ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)), + ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)), + ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)), +}; +#undef ERROR + +static void GetErrorString(nsresult rv, nsAString& errorString) { + for (auto& socketTransportStatus : socketTransportStatuses) { + if (socketTransportStatus.key == rv) { + errorString.AssignASCII(socketTransportStatus.error); + return; + } + } + nsAutoCString errorCString; + mozilla::GetErrorName(rv, errorCString); + CopyUTF8toUTF16(errorCString, errorString); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/Dashboard.h b/netwerk/base/Dashboard.h new file mode 100644 index 0000000000..1112a013e9 --- /dev/null +++ b/netwerk/base/Dashboard.h @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDashboard_h__ +#define nsDashboard_h__ + +#include "mozilla/Mutex.h" +#include "mozilla/net/DashboardTypes.h" +#include "nsIDashboard.h" +#include "nsIDashboardEventNotifier.h" + +class nsIDNSService; + +namespace mozilla { +namespace net { + +class SocketData; +class HttpData; +class DnsData; +class WebSocketRequest; +class ConnectionData; +class RcwnData; + +class Dashboard final : public nsIDashboard, public nsIDashboardEventNotifier { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDASHBOARD + NS_DECL_NSIDASHBOARDEVENTNOTIFIER + + Dashboard(); + static const char* GetErrorString(nsresult rv); + nsresult GetConnectionStatus(ConnectionData* aConnectionData); + + private: + struct LogData { + LogData(nsCString host, uint32_t serial, bool encryption) + : mHost(host), + mSerial(serial), + mMsgSent(0), + mMsgReceived(0), + mSizeSent(0), + mSizeReceived(0), + mEncrypted(encryption) {} + nsCString mHost; + uint32_t mSerial; + uint32_t mMsgSent; + uint32_t mMsgReceived; + uint64_t mSizeSent; + uint64_t mSizeReceived; + bool mEncrypted; + bool operator==(const LogData& a) const { + return mHost.Equals(a.mHost) && (mSerial == a.mSerial); + } + }; + + struct WebSocketData { + WebSocketData() : lock("Dashboard.webSocketData") {} + uint32_t IndexOf(const nsCString& hostname, uint32_t mSerial) { + LogData temp(hostname, mSerial, false); + return data.IndexOf(temp); + } + nsTArray<LogData> data; + mozilla::Mutex lock MOZ_UNANNOTATED; + }; + + bool mEnableLogging; + WebSocketData mWs; + + private: + virtual ~Dashboard() = default; + + nsresult GetSocketsDispatch(SocketData*); + nsresult GetHttpDispatch(HttpData*); + nsresult GetDnsInfoDispatch(DnsData*); + nsresult TestNewConnection(ConnectionData*); + + /* Helper methods that pass the JSON to the callback function. */ + nsresult GetSockets(SocketData*); + nsresult GetHttpConnections(HttpData*); + nsresult GetDNSCacheEntries(DnsData*); + nsresult GetWebSocketConnections(WebSocketRequest*); + nsresult GetRcwnData(RcwnData*); + + nsCOMPtr<nsIDNSService> mDnsService; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsDashboard_h__ diff --git a/netwerk/base/DashboardTypes.h b/netwerk/base/DashboardTypes.h new file mode 100644 index 0000000000..6dd901d02a --- /dev/null +++ b/netwerk/base/DashboardTypes.h @@ -0,0 +1,175 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_DashboardTypes_h_ +#define mozilla_net_DashboardTypes_h_ + +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "nsHttp.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace net { + +struct SocketInfo { + nsCString host; + uint64_t sent; + uint64_t received; + uint16_t port; + bool active; + nsCString type; +}; + +inline bool operator==(const SocketInfo& a, const SocketInfo& b) { + return a.host == b.host && a.sent == b.sent && a.received == b.received && + a.port == b.port && a.active == b.active && a.type == b.type; +} + +struct DnsAndConnectSockets { + bool speculative; +}; + +struct DNSCacheEntries { + nsCString hostname; + nsTArray<nsCString> hostaddr; + uint16_t family; + int64_t expiration; + nsCString netInterface; + bool TRR; + nsCString originAttributesSuffix; + nsCString flags; +}; + +struct HttpConnInfo { + uint32_t ttl; + uint32_t rtt; + nsString protocolVersion; + + void SetHTTPProtocolVersion(HttpVersion pv); +}; + +struct HttpRetParams { + nsCString host; + CopyableTArray<HttpConnInfo> active; + CopyableTArray<HttpConnInfo> idle; + CopyableTArray<DnsAndConnectSockets> dnsAndSocks; + uint32_t counter; + uint16_t port; + nsCString httpVersion; + bool ssl; +}; + +} // namespace net +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::net::SocketInfo> { + typedef mozilla::net::SocketInfo paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.host); + WriteParam(aWriter, aParam.sent); + WriteParam(aWriter, aParam.received); + WriteParam(aWriter, aParam.port); + WriteParam(aWriter, aParam.active); + WriteParam(aWriter, aParam.type); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->host) && + ReadParam(aReader, &aResult->sent) && + ReadParam(aReader, &aResult->received) && + ReadParam(aReader, &aResult->port) && + ReadParam(aReader, &aResult->active) && + ReadParam(aReader, &aResult->type); + } +}; + +template <> +struct ParamTraits<mozilla::net::DNSCacheEntries> { + typedef mozilla::net::DNSCacheEntries paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.hostname); + WriteParam(aWriter, aParam.hostaddr); + WriteParam(aWriter, aParam.family); + WriteParam(aWriter, aParam.expiration); + WriteParam(aWriter, aParam.netInterface); + WriteParam(aWriter, aParam.TRR); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->hostname) && + ReadParam(aReader, &aResult->hostaddr) && + ReadParam(aReader, &aResult->family) && + ReadParam(aReader, &aResult->expiration) && + ReadParam(aReader, &aResult->netInterface) && + ReadParam(aReader, &aResult->TRR); + } +}; + +template <> +struct ParamTraits<mozilla::net::DnsAndConnectSockets> { + typedef mozilla::net::DnsAndConnectSockets paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.speculative); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->speculative); + } +}; + +template <> +struct ParamTraits<mozilla::net::HttpConnInfo> { + typedef mozilla::net::HttpConnInfo paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.ttl); + WriteParam(aWriter, aParam.rtt); + WriteParam(aWriter, aParam.protocolVersion); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->ttl) && + ReadParam(aReader, &aResult->rtt) && + ReadParam(aReader, &aResult->protocolVersion); + } +}; + +template <> +struct ParamTraits<mozilla::net::HttpRetParams> { + typedef mozilla::net::HttpRetParams paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.host); + WriteParam(aWriter, aParam.active); + WriteParam(aWriter, aParam.idle); + WriteParam(aWriter, aParam.dnsAndSocks); + WriteParam(aWriter, aParam.counter); + WriteParam(aWriter, aParam.port); + WriteParam(aWriter, aParam.httpVersion); + WriteParam(aWriter, aParam.ssl); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->host) && + ReadParam(aReader, &aResult->active) && + ReadParam(aReader, &aResult->idle) && + ReadParam(aReader, &aResult->dnsAndSocks) && + ReadParam(aReader, &aResult->counter) && + ReadParam(aReader, &aResult->port) && + ReadParam(aReader, &aResult->httpVersion) && + ReadParam(aReader, &aResult->ssl); + } +}; + +} // namespace IPC + +#endif // mozilla_net_DashboardTypes_h_ diff --git a/netwerk/base/DefaultURI.cpp b/netwerk/base/DefaultURI.cpp new file mode 100644 index 0000000000..0a70218af5 --- /dev/null +++ b/netwerk/base/DefaultURI.cpp @@ -0,0 +1,572 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DefaultURI.h" +#include "nsIClassInfoImpl.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsURLHelper.h" + +#include "mozilla/ipc/URIParams.h" + +namespace mozilla { +namespace net { + +#define NS_DEFAULTURI_CID \ + { /* 04445aa0-fd27-4c99-bd41-6be6318ae92c */ \ + 0x04445aa0, 0xfd27, 0x4c99, { \ + 0xbd, 0x41, 0x6b, 0xe6, 0x31, 0x8a, 0xe9, 0x2c \ + } \ + } + +#define ASSIGN_AND_ADDREF_THIS(ptrToMutator) \ + do { \ + if (ptrToMutator) { \ + *(ptrToMutator) = do_AddRef(this).take(); \ + } \ + } while (0) + +static NS_DEFINE_CID(kDefaultURICID, NS_DEFAULTURI_CID); + +//---------------------------------------------------------------------------- +// nsIClassInfo +//---------------------------------------------------------------------------- + +NS_IMPL_CLASSINFO(DefaultURI, nullptr, nsIClassInfo::THREADSAFE, + NS_DEFAULTURI_CID) +// Empty CI getter. We only need nsIClassInfo for Serialization +NS_IMPL_CI_INTERFACE_GETTER0(DefaultURI) + +//---------------------------------------------------------------------------- +// nsISupports +//---------------------------------------------------------------------------- + +NS_IMPL_ADDREF(DefaultURI) +NS_IMPL_RELEASE(DefaultURI) +NS_INTERFACE_TABLE_HEAD(DefaultURI) + NS_INTERFACE_TABLE(DefaultURI, nsIURI, nsISerializable) + NS_INTERFACE_TABLE_TO_MAP_SEGUE + NS_IMPL_QUERY_CLASSINFO(DefaultURI) + if (aIID.Equals(kDefaultURICID)) { + foundInterface = static_cast<nsIURI*>(this); + } else + NS_INTERFACE_MAP_ENTRY(nsISizeOf) +NS_INTERFACE_MAP_END + +//---------------------------------------------------------------------------- +// nsISerializable +//---------------------------------------------------------------------------- + +NS_IMETHODIMP DefaultURI::Read(nsIObjectInputStream* aInputStream) { + MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DefaultURI::Write(nsIObjectOutputStream* aOutputStream) { + nsAutoCString spec(mURL->Spec()); + return aOutputStream->WriteStringZ(spec.get()); +} + +//---------------------------------------------------------------------------- +// nsISizeOf +//---------------------------------------------------------------------------- + +size_t DefaultURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mURL->SizeOf(); +} + +size_t DefaultURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +//---------------------------------------------------------------------------- +// nsIURI +//---------------------------------------------------------------------------- + +NS_IMETHODIMP DefaultURI::GetSpec(nsACString& aSpec) { + aSpec = mURL->Spec(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetPrePath(nsACString& aPrePath) { + aPrePath = mURL->PrePath(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetScheme(nsACString& aScheme) { + aScheme = mURL->Scheme(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetUserPass(nsACString& aUserPass) { + aUserPass = mURL->Username(); + nsAutoCString pass(mURL->Password()); + if (pass.IsEmpty()) { + return NS_OK; + } + aUserPass.Append(':'); + aUserPass.Append(pass); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetUsername(nsACString& aUsername) { + aUsername = mURL->Username(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetPassword(nsACString& aPassword) { + aPassword = mURL->Password(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetHostPort(nsACString& aHostPort) { + aHostPort = mURL->HostPort(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetHost(nsACString& aHost) { + aHost = mURL->Host(); + + // Historically nsIURI.host has always returned an IPv6 address that isn't + // enclosed in brackets. Ideally we want to change that, but for the sake of + // consitency we'll leave it like that for the moment. + // Bug 1603199 should fix this. + if (StringBeginsWith(aHost, "["_ns) && StringEndsWith(aHost, "]"_ns) && + aHost.FindChar(':') != kNotFound) { + aHost = Substring(aHost, 1, aHost.Length() - 2); + } + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetPort(int32_t* aPort) { + *aPort = mURL->Port(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetPathQueryRef(nsACString& aPathQueryRef) { + aPathQueryRef = mURL->Path(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::Equals(nsIURI* other, bool* _retval) { + if (!other) { + *_retval = false; + return NS_OK; + } + RefPtr<DefaultURI> otherUri; + nsresult rv = other->QueryInterface(kDefaultURICID, getter_AddRefs(otherUri)); + if (NS_FAILED(rv)) { + *_retval = false; + return NS_OK; + } + + *_retval = mURL->Spec() == otherUri->mURL->Spec(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::SchemeIs(const char* scheme, bool* _retval) { + if (!scheme) { + *_retval = false; + return NS_OK; + } + *_retval = mURL->Scheme().Equals(scheme); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::Resolve(const nsACString& aRelativePath, + nsACString& aResult) { + nsAutoCString scheme; + nsresult rv = net_ExtractURLScheme(aRelativePath, scheme); + if (NS_SUCCEEDED(rv)) { + aResult = aRelativePath; + return NS_OK; + } + + // We try to create another URL with this one as its base. + RefPtr<MozURL> resolvedURL; + rv = MozURL::Init(getter_AddRefs(resolvedURL), aRelativePath, mURL); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If parsing the relative url fails, we revert to the previous behaviour + // and just return the relative path. + aResult = aRelativePath; + return NS_OK; + } + + aResult = resolvedURL->Spec(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetAsciiSpec(nsACString& aAsciiSpec) { + return GetSpec(aAsciiSpec); +} + +NS_IMETHODIMP DefaultURI::GetAsciiHostPort(nsACString& aAsciiHostPort) { + return GetHostPort(aAsciiHostPort); +} + +NS_IMETHODIMP DefaultURI::GetAsciiHost(nsACString& aAsciiHost) { + return GetHost(aAsciiHost); +} + +NS_IMETHODIMP DefaultURI::GetRef(nsACString& aRef) { + aRef = mURL->Ref(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::EqualsExceptRef(nsIURI* other, bool* _retval) { + if (!_retval || !other) { + return NS_ERROR_NULL_POINTER; + } + RefPtr<DefaultURI> otherUri; + nsresult rv = other->QueryInterface(kDefaultURICID, getter_AddRefs(otherUri)); + if (NS_FAILED(rv)) { + *_retval = false; + return NS_OK; + } + + *_retval = mURL->SpecNoRef().Equals(otherUri->mURL->SpecNoRef()); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetSpecIgnoringRef(nsACString& aSpecIgnoringRef) { + aSpecIgnoringRef = mURL->SpecNoRef(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetHasRef(bool* aHasRef) { + *aHasRef = mURL->HasFragment(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetHasUserPass(bool* aHasUserPass) { + *aHasUserPass = !mURL->Username().IsEmpty() || !mURL->Password().IsEmpty(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetFilePath(nsACString& aFilePath) { + aFilePath = mURL->FilePath(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetQuery(nsACString& aQuery) { + aQuery = mURL->Query(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetHasQuery(bool* aHasQuery) { + *aHasQuery = mURL->HasQuery(); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::GetDisplayHost(nsACString& aDisplayHost) { + // At the moment it doesn't seem useful to decode the hostname if it happens + // to contain punycode. + return GetHost(aDisplayHost); +} + +NS_IMETHODIMP DefaultURI::GetDisplayHostPort(nsACString& aDisplayHostPort) { + // At the moment it doesn't seem useful to decode the hostname if it happens + // to contain punycode. + return GetHostPort(aDisplayHostPort); +} + +NS_IMETHODIMP DefaultURI::GetDisplaySpec(nsACString& aDisplaySpec) { + // At the moment it doesn't seem useful to decode the hostname if it happens + // to contain punycode. + return GetSpec(aDisplaySpec); +} + +NS_IMETHODIMP DefaultURI::GetDisplayPrePath(nsACString& aDisplayPrePath) { + // At the moment it doesn't seem useful to decode the hostname if it happens + // to contain punycode. + return GetPrePath(aDisplayPrePath); +} + +NS_IMETHODIMP DefaultURI::Mutate(nsIURIMutator** _retval) { + RefPtr<DefaultURI::Mutator> mutator = new DefaultURI::Mutator(); + mutator->Init(this); + mutator.forget(_retval); + return NS_OK; +} + +void DefaultURI::Serialize(ipc::URIParams& aParams) { + ipc::DefaultURIParams params; + params.spec() = mURL->Spec(); + aParams = params; +} + +//---------------------------------------------------------------------------- +// nsIURIMutator +//---------------------------------------------------------------------------- + +NS_IMPL_ADDREF(DefaultURI::Mutator) +NS_IMPL_RELEASE(DefaultURI::Mutator) +NS_IMETHODIMP DefaultURI::Mutator::QueryInterface(REFNSIID aIID, + void** aInstancePtr) { + NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!"); + nsISupports* foundInterface = nullptr; + if (aIID.Equals(NS_GET_IID(nsIURI))) { + RefPtr<DefaultURI> defaultURI = new DefaultURI(); + mMutator->Finalize(getter_AddRefs(defaultURI->mURL)); + foundInterface = + static_cast<nsISupports*>(static_cast<nsIURI*>((defaultURI.get()))); + NS_ADDREF(foundInterface); + *aInstancePtr = foundInterface; + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsIURIMutator)) || + aIID.Equals(NS_GET_IID(nsISupports))) { + foundInterface = + static_cast<nsISupports*>(static_cast<nsIURIMutator*>(this)); + } else if (aIID.Equals(NS_GET_IID(nsIURISetters))) { + foundInterface = + static_cast<nsISupports*>(static_cast<nsIURISetters*>(this)); + } else if (aIID.Equals(NS_GET_IID(nsIURISetSpec))) { + foundInterface = + static_cast<nsISupports*>(static_cast<nsIURISetSpec*>(this)); + } else if (aIID.Equals(NS_GET_IID(nsISerializable))) { + foundInterface = + static_cast<nsISupports*>(static_cast<nsISerializable*>(this)); + } + + if (foundInterface) { + NS_ADDREF(foundInterface); + *aInstancePtr = foundInterface; + return NS_OK; + } + + return NS_NOINTERFACE; +} + +NS_IMETHODIMP DefaultURI::Mutator::Read(nsIObjectInputStream* aStream) { + nsAutoCString spec; + nsresult rv = aStream->ReadCString(spec); + if (NS_FAILED(rv)) { + return rv; + } + return SetSpec(spec, nullptr); +} + +NS_IMETHODIMP DefaultURI::Mutator::Deserialize( + const mozilla::ipc::URIParams& aParams) { + if (aParams.type() != ipc::URIParams::TDefaultURIParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return NS_ERROR_FAILURE; + } + + const ipc::DefaultURIParams& params = aParams.get_DefaultURIParams(); + auto result = MozURL::Mutator::FromSpec(params.spec()); + if (result.isErr()) { + return result.unwrapErr(); + } + mMutator = Some(result.unwrap()); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::Mutator::Finalize(nsIURI** aURI) { + if (!mMutator.isSome()) { + return NS_ERROR_NOT_AVAILABLE; + } + RefPtr<DefaultURI> uri = new DefaultURI(); + mMutator->Finalize(getter_AddRefs(uri->mURL)); + mMutator = Nothing(); + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP DefaultURI::Mutator::SetSpec(const nsACString& aSpec, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + auto result = MozURL::Mutator::FromSpec(aSpec); + if (result.isErr()) { + return result.unwrapErr(); + } + mMutator = Some(result.unwrap()); + return NS_OK; +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetScheme(const nsACString& aScheme, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetScheme(aScheme); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetUserPass(const nsACString& aUserPass, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + int32_t index = aUserPass.FindChar(':'); + if (index == kNotFound) { + mMutator->SetUsername(aUserPass); + mMutator->SetPassword(""_ns); + return mMutator->GetStatus(); + } + + mMutator->SetUsername(Substring(aUserPass, 0, index)); + nsresult rv = mMutator->GetStatus(); + if (NS_FAILED(rv)) { + return rv; + } + mMutator->SetPassword(Substring(aUserPass, index + 1)); + rv = mMutator->GetStatus(); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetUsername(const nsACString& aUsername, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetUsername(aUsername); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetPassword(const nsACString& aPassword, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetPassword(aPassword); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetHostPort(const nsACString& aHostPort, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetHostPort(aHostPort); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetHost(const nsACString& aHost, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetHostname(aHost); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetPort(int32_t aPort, nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetPort(aPort); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetPathQueryRef(const nsACString& aPathQueryRef, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + if (aPathQueryRef.IsEmpty()) { + mMutator->SetFilePath(""_ns); + mMutator->SetQuery(""_ns); + mMutator->SetRef(""_ns); + return mMutator->GetStatus(); + } + + RefPtr<MozURL> url; + mMutator->Finalize(getter_AddRefs(url)); + mMutator = Nothing(); + + if (!url) { + return NS_ERROR_FAILURE; + } + + nsAutoCString pathQueryRef(aPathQueryRef); + if (url->CannotBeABase()) { + // If the base URL cannot be a base, then setting the pathQueryRef + // needs to change everything after the scheme. + pathQueryRef.Insert(":", 0); + pathQueryRef.Insert(url->Scheme(), 0); + // Clear the URL to make sure FromSpec creates an absolute URL + url = nullptr; + } else if (!StringBeginsWith(pathQueryRef, "/"_ns)) { + // If the base URL can be a base, make sure the path + // begins with a / + pathQueryRef.Insert('/', 0); + } + + auto result = MozURL::Mutator::FromSpec(pathQueryRef, url); + if (result.isErr()) { + return result.unwrapErr(); + } + mMutator = Some(result.unwrap()); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetRef(const nsACString& aRef, nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetRef(aRef); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetFilePath(const nsACString& aFilePath, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetFilePath(aFilePath); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetQuery(const nsACString& aQuery, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + mMutator->SetQuery(aQuery); + return mMutator->GetStatus(); +} + +NS_IMETHODIMP +DefaultURI::Mutator::SetQueryWithEncoding(const nsACString& aQuery, + const mozilla::Encoding* aEncoding, + nsIURIMutator** aMutator) { + ASSIGN_AND_ADDREF_THIS(aMutator); + if (!mMutator.isSome()) { + return NS_ERROR_NULL_POINTER; + } + // we only support UTF-8 for DefaultURI + mMutator->SetQuery(aQuery); + return mMutator->GetStatus(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/DefaultURI.h b/netwerk/base/DefaultURI.h new file mode 100644 index 0000000000..e737067565 --- /dev/null +++ b/netwerk/base/DefaultURI.h @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DefaultURI_h__ +#define DefaultURI_h__ + +#include "nsIURI.h" +#include "nsISerializable.h" +#include "nsISizeOf.h" +#include "nsIURIMutator.h" +#include "mozilla/net/MozURL.h" + +namespace mozilla { +namespace net { + +class DefaultURI : public nsIURI, public nsISerializable, public nsISizeOf { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSISERIALIZABLE + + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override; + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override; + + class Mutator final : public nsIURIMutator, public nsISerializable { + NS_DECL_ISUPPORTS + NS_DECL_NSIURISETSPEC + NS_DECL_NSIURISETTERS + NS_DECL_NSIURIMUTATOR + + NS_IMETHOD + Write(nsIObjectOutputStream* aOutputStream) override { + MOZ_ASSERT_UNREACHABLE("nsIURIMutator.write() should never be called"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override; + + explicit Mutator() = default; + + private: + virtual ~Mutator() = default; + void Init(DefaultURI* aFrom) { mMutator = Some(aFrom->mURL->Mutate()); } + + Maybe<MozURL::Mutator> mMutator; + + friend class DefaultURI; + }; + + private: + virtual ~DefaultURI() = default; + RefPtr<MozURL> mURL; +}; + +} // namespace net +} // namespace mozilla + +#endif // DefaultURI_h__ diff --git a/netwerk/base/EventTokenBucket.cpp b/netwerk/base/EventTokenBucket.cpp new file mode 100644 index 0000000000..87179d6732 --- /dev/null +++ b/netwerk/base/EventTokenBucket.cpp @@ -0,0 +1,410 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EventTokenBucket.h" + +#include "nsICancelable.h" +#include "nsIIOService.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsSocketTransportService2.h" +#ifdef DEBUG +# include "MainThreadUtils.h" +#endif + +#ifdef XP_WIN +# include <windows.h> +# include <mmsystem.h> +#endif + +namespace mozilla { +namespace net { + +//////////////////////////////////////////// +// EventTokenBucketCancelable +//////////////////////////////////////////// + +class TokenBucketCancelable : public nsICancelable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICANCELABLE + + explicit TokenBucketCancelable(class ATokenBucketEvent* event); + void Fire(); + + private: + virtual ~TokenBucketCancelable() = default; + + friend class EventTokenBucket; + ATokenBucketEvent* mEvent; +}; + +NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable) + +TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent* event) + : mEvent(event) {} + +NS_IMETHODIMP +TokenBucketCancelable::Cancel(nsresult reason) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + mEvent = nullptr; + return NS_OK; +} + +void TokenBucketCancelable::Fire() { + if (!mEvent) return; + + ATokenBucketEvent* event = mEvent; + mEvent = nullptr; + event->OnTokenBucketAdmitted(); +} + +//////////////////////////////////////////// +// EventTokenBucket +//////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback, nsINamed) + +// by default 1hz with no burst +EventTokenBucket::EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize) + : mUnitCost(kUsecPerSec), + mMaxCredit(kUsecPerSec), + mCredit(kUsecPerSec), + mPaused(false), + mStopped(false), + mTimerArmed(false) +#ifdef XP_WIN + , + mFineGrainTimerInUse(false), + mFineGrainResetTimerArmed(false) +#endif +{ + mLastUpdate = TimeStamp::Now(); + + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + nsCOMPtr<nsIEventTarget> sts; + nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); + if (NS_SUCCEEDED(rv)) { + sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + } + if (NS_SUCCEEDED(rv)) mTimer = NS_NewTimer(sts); + SetRate(eventsPerSecond, burstSize); +} + +EventTokenBucket::~EventTokenBucket() { + SOCKET_LOG( + ("EventTokenBucket::dtor %p events=%zu\n", this, mEvents.GetSize())); + + CleanupTimers(); + + // Complete any queued events to prevent hangs + while (mEvents.GetSize()) { + RefPtr<TokenBucketCancelable> cancelable = mEvents.PopFront(); + cancelable->Fire(); + } +} + +void EventTokenBucket::CleanupTimers() { + if (mTimer && mTimerArmed) { + mTimer->Cancel(); + } + mTimer = nullptr; + mTimerArmed = false; + +#ifdef XP_WIN + NormalTimers(); + if (mFineGrainResetTimer && mFineGrainResetTimerArmed) { + mFineGrainResetTimer->Cancel(); + } + mFineGrainResetTimer = nullptr; + mFineGrainResetTimerArmed = false; +#endif +} + +void EventTokenBucket::SetRate(uint32_t eventsPerSecond, uint32_t burstSize) { + SOCKET_LOG(("EventTokenBucket::SetRate %p %u %u\n", this, eventsPerSecond, + burstSize)); + + if (eventsPerSecond > kMaxHz) { + eventsPerSecond = kMaxHz; + SOCKET_LOG((" eventsPerSecond out of range\n")); + } + + if (!eventsPerSecond) { + eventsPerSecond = 1; + SOCKET_LOG((" eventsPerSecond out of range\n")); + } + + mUnitCost = kUsecPerSec / eventsPerSecond; + mMaxCredit = mUnitCost * burstSize; + if (mMaxCredit > kUsecPerSec * 60 * 15) { + SOCKET_LOG((" burstSize out of range\n")); + mMaxCredit = kUsecPerSec * 60 * 15; + } + mCredit = mMaxCredit; + mLastUpdate = TimeStamp::Now(); +} + +void EventTokenBucket::ClearCredits() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this)); + mCredit = 0; +} + +uint32_t EventTokenBucket::BurstEventsAvailable() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + return static_cast<uint32_t>(mCredit / mUnitCost); +} + +uint32_t EventTokenBucket::QueuedEvents() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + return mEvents.GetSize(); +} + +void EventTokenBucket::Pause() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::Pause %p\n", this)); + if (mPaused || mStopped) return; + + mPaused = true; + if (mTimerArmed) { + mTimer->Cancel(); + mTimerArmed = false; + } +} + +void EventTokenBucket::UnPause() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::UnPause %p\n", this)); + if (!mPaused || mStopped) return; + + mPaused = false; + DispatchEvents(); + UpdateTimer(); +} + +void EventTokenBucket::Stop() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::Stop %p armed=%d\n", this, mTimerArmed)); + mStopped = true; + CleanupTimers(); + + // Complete any queued events to prevent hangs + while (mEvents.GetSize()) { + RefPtr<TokenBucketCancelable> cancelable = mEvents.PopFront(); + cancelable->Fire(); + } +} + +nsresult EventTokenBucket::SubmitEvent(ATokenBucketEvent* event, + nsICancelable** cancelable) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::SubmitEvent %p\n", this)); + + if (mStopped || !mTimer) return NS_ERROR_FAILURE; + + UpdateCredits(); + + RefPtr<TokenBucketCancelable> cancelEvent = new TokenBucketCancelable(event); + // When this function exits the cancelEvent needs 2 references, one for the + // mEvents queue and one for the caller of SubmitEvent() + + *cancelable = do_AddRef(cancelEvent).take(); + + if (mPaused || !TryImmediateDispatch(cancelEvent.get())) { + // queue it + SOCKET_LOG((" queued\n")); + mEvents.Push(cancelEvent.forget()); + UpdateTimer(); + } else { + SOCKET_LOG((" dispatched synchronously\n")); + } + + return NS_OK; +} + +bool EventTokenBucket::TryImmediateDispatch(TokenBucketCancelable* cancelable) { + if (mCredit < mUnitCost) return false; + + mCredit -= mUnitCost; + cancelable->Fire(); + return true; +} + +void EventTokenBucket::DispatchEvents() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG(("EventTokenBucket::DispatchEvents %p %d\n", this, mPaused)); + if (mPaused || mStopped) return; + + while (mEvents.GetSize() && mUnitCost <= mCredit) { + RefPtr<TokenBucketCancelable> cancelable = mEvents.PopFront(); + if (cancelable->mEvent) { + SOCKET_LOG( + ("EventTokenBucket::DispachEvents [%p] " + "Dispatching queue token bucket event cost=%" PRIu64 + " credit=%" PRIu64 "\n", + this, mUnitCost, mCredit)); + mCredit -= mUnitCost; + cancelable->Fire(); + } + } + +#ifdef XP_WIN + if (!mEvents.GetSize()) WantNormalTimers(); +#endif +} + +void EventTokenBucket::UpdateTimer() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mTimerArmed || mPaused || mStopped || !mEvents.GetSize() || !mTimer) { + return; + } + + if (mCredit >= mUnitCost) return; + + // determine the time needed to wait to accumulate enough credits to admit + // one more event and set the timer for that point. Always round it + // up because firing early doesn't help. + // + uint64_t deficit = mUnitCost - mCredit; + uint64_t msecWait = (deficit + (kUsecPerMsec - 1)) / kUsecPerMsec; + + if (msecWait < 4) { // minimum wait + msecWait = 4; + } else if (msecWait > 60000) { // maximum wait + msecWait = 60000; + } + +#ifdef XP_WIN + FineGrainTimers(); +#endif + + SOCKET_LOG( + ("EventTokenBucket::UpdateTimer %p for %" PRIu64 "ms\n", this, msecWait)); + nsresult rv = mTimer->InitWithCallback(this, static_cast<uint32_t>(msecWait), + nsITimer::TYPE_ONE_SHOT); + mTimerArmed = NS_SUCCEEDED(rv); +} + +NS_IMETHODIMP +EventTokenBucket::Notify(nsITimer* timer) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + +#ifdef XP_WIN + if (timer == mFineGrainResetTimer) { + FineGrainResetTimerNotify(); + return NS_OK; + } +#endif + + SOCKET_LOG(("EventTokenBucket::Notify() %p\n", this)); + mTimerArmed = false; + if (mStopped) return NS_OK; + + UpdateCredits(); + DispatchEvents(); + UpdateTimer(); + + return NS_OK; +} + +NS_IMETHODIMP +EventTokenBucket::GetName(nsACString& aName) { + aName.AssignLiteral("EventTokenBucket"); + return NS_OK; +} + +void EventTokenBucket::UpdateCredits() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + TimeStamp now = TimeStamp::Now(); + TimeDuration elapsed = now - mLastUpdate; + mLastUpdate = now; + + mCredit += static_cast<uint64_t>(elapsed.ToMicroseconds()); + if (mCredit > mMaxCredit) mCredit = mMaxCredit; + SOCKET_LOG(("EventTokenBucket::UpdateCredits %p to %" PRIu64 " (%" PRIu64 + " each.. %3.2f)\n", + this, mCredit, mUnitCost, (double)mCredit / mUnitCost)); +} + +#ifdef XP_WIN +void EventTokenBucket::FineGrainTimers() { + SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p mFineGrainTimerInUse=%d\n", + this, mFineGrainTimerInUse)); + + mLastFineGrainTimerUse = TimeStamp::Now(); + + if (mFineGrainTimerInUse) return; + + if (mUnitCost > kCostFineGrainThreshold) return; + + SOCKET_LOG( + ("EventTokenBucket::FineGrainTimers %p timeBeginPeriod()\n", this)); + + mFineGrainTimerInUse = true; + timeBeginPeriod(1); +} + +void EventTokenBucket::NormalTimers() { + if (!mFineGrainTimerInUse) return; + mFineGrainTimerInUse = false; + + SOCKET_LOG(("EventTokenBucket::NormalTimers %p timeEndPeriod()\n", this)); + timeEndPeriod(1); +} + +void EventTokenBucket::WantNormalTimers() { + if (!mFineGrainTimerInUse) return; + if (mFineGrainResetTimerArmed) return; + + TimeDuration elapsed(TimeStamp::Now() - mLastFineGrainTimerUse); + static const TimeDuration fiveSeconds = TimeDuration::FromSeconds(5); + + if (elapsed >= fiveSeconds) { + NormalTimers(); + return; + } + + if (!mFineGrainResetTimer) mFineGrainResetTimer = NS_NewTimer(); + + // if we can't delay the reset, just do it now + if (!mFineGrainResetTimer) { + NormalTimers(); + return; + } + + // pad the callback out 100ms to avoid having to round trip this again if the + // timer calls back just a tad early. + SOCKET_LOG( + ("EventTokenBucket::WantNormalTimers %p " + "Will reset timer granularity after delay", + this)); + + mFineGrainResetTimer->InitWithCallback( + this, + static_cast<uint32_t>((fiveSeconds - elapsed).ToMilliseconds()) + 100, + nsITimer::TYPE_ONE_SHOT); + mFineGrainResetTimerArmed = true; +} + +void EventTokenBucket::FineGrainResetTimerNotify() { + SOCKET_LOG(("EventTokenBucket::FineGrainResetTimerNotify(%p) events = %zd\n", + this, mEvents.GetSize())); + mFineGrainResetTimerArmed = false; + + // If we are currently processing events then wait for the queue to drain + // before trying to reset back to normal timers again + if (!mEvents.GetSize()) WantNormalTimers(); +} + +#endif + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/EventTokenBucket.h b/netwerk/base/EventTokenBucket.h new file mode 100644 index 0000000000..4206a622f5 --- /dev/null +++ b/netwerk/base/EventTokenBucket.h @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NetEventTokenBucket_h__ +#define NetEventTokenBucket_h__ + +#include "ARefBase.h" +#include "nsCOMPtr.h" +#include "nsDeque.h" +#include "nsINamed.h" +#include "nsITimer.h" + +#include "mozilla/TimeStamp.h" + +class nsICancelable; + +namespace mozilla { +namespace net { + +/* A token bucket is used to govern the maximum rate a series of events + can be executed at. For instance if your event was "eat a piece of cake" + then a token bucket configured to allow "1 piece per day" would spread + the eating of a 8 piece cake over 8 days even if you tried to eat the + whole thing up front. In a practical sense it 'costs' 1 token to execute + an event and tokens are 'earned' at a particular rate as time goes by. + + The token bucket can be perfectly smooth or allow a configurable amount of + burstiness. A bursty token bucket allows you to save up unused credits, while + a perfectly smooth one would not. A smooth "1 per day" cake token bucket + would require 9 days to eat that cake if you skipped a slice on day 4 + (use the token or lose it), while a token bucket configured with a burst + of 2 would just let you eat 2 slices on day 5 (the credits for day 4 and day + 5) and finish the cake in the usual 8 days. + + EventTokenBucket(hz=20, burst=5) creates a token bucket with the following + properties: + + + events from an infinite stream will be admitted 20 times per second (i.e. + hz=20 means 1 event per 50 ms). Timers will be used to space things evenly + down to 5ms gaps (i.e. up to 200hz). Token buckets with rates greater than + 200hz will admit multiple events with 5ms gaps between them. 10000hz is the + maximum rate and 1hz is the minimum rate. + + + The burst size controls the limit of 'credits' that a token bucket can + accumulate when idle. For our (20,5) example each event requires 50ms of + credit (again, 20hz = 50ms per event). a burst size of 5 means that the + token bucket can accumulate a maximum of 250ms (5 * 50ms) for this bucket. + If no events have been admitted for the last full second the bucket can + still only accumulate 250ms of credit - but that credit means that 5 events + can be admitted without delay. A burst size of 1 is the minimum. The + EventTokenBucket is created with maximum credits already applied, but they + can be cleared with the ClearCredits() method. The maximum burst size is 15 + minutes worth of events. + + + An event is submitted to the token bucket asynchronously through + SubmitEvent(). The OnTokenBucketAdmitted() method of the submitted event + is used as a callback when the event is ready to run. A cancelable event is + returned to the SubmitEvent() caller for use in the case they do not wish + to wait for the callback. +*/ + +class EventTokenBucket; + +class ATokenBucketEvent { + public: + virtual void OnTokenBucketAdmitted() = 0; +}; + +class TokenBucketCancelable; + +class EventTokenBucket : public nsITimerCallback, + public nsINamed, + public ARefBase { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + // This should be constructed on the main thread + EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize); + + // These public methods are all meant to be called from the socket thread + void ClearCredits(); + uint32_t BurstEventsAvailable(); + uint32_t QueuedEvents(); + + // a paused token bucket will not process any events, but it will accumulate + // credits. ClearCredits can be used before unpausing if desired. + void Pause(); + void UnPause(); + void Stop(); + + // The returned cancelable event can only be canceled from the socket thread + nsresult SubmitEvent(ATokenBucketEvent* event, nsICancelable** cancelable); + + private: + virtual ~EventTokenBucket(); + void CleanupTimers(); + + friend class RunNotifyEvent; + friend class SetTimerEvent; + + bool TryImmediateDispatch(TokenBucketCancelable* cancelable); + void SetRate(uint32_t eventsPerSecond, uint32_t burstSize); + + void DispatchEvents(); + void UpdateTimer(); + void UpdateCredits(); + + const static uint64_t kUsecPerSec = 1000000; + const static uint64_t kUsecPerMsec = 1000; + const static uint64_t kMaxHz = 10000; + + uint64_t + mUnitCost; // usec of credit needed for 1 event (from eventsPerSecond) + uint64_t mMaxCredit; // usec mCredit limit (from busrtSize) + uint64_t mCredit; // usec of accumulated credit. + + bool mPaused; + bool mStopped; + nsRefPtrDeque<TokenBucketCancelable> mEvents; + bool mTimerArmed; + TimeStamp mLastUpdate; + + // The timer is created on the main thread, but is armed and executes Notify() + // callbacks on the socket thread in order to maintain low latency of event + // delivery. + nsCOMPtr<nsITimer> mTimer; + +#ifdef XP_WIN + // Windows timers are 15ms granularity by default. When we have active events + // that need to be dispatched at 50ms or less granularity we change the OS + // granularity to 1ms. 90 seconds after that need has elapsed we will change + // it back + const static uint64_t kCostFineGrainThreshold = 50 * kUsecPerMsec; + + void FineGrainTimers(); // get 1ms granularity + void NormalTimers(); // reset to default granularity + void WantNormalTimers(); // reset after 90 seconds if not needed in interim + void FineGrainResetTimerNotify(); // delayed callback to reset + + TimeStamp mLastFineGrainTimerUse; + bool mFineGrainTimerInUse; + bool mFineGrainResetTimerArmed; + nsCOMPtr<nsITimer> mFineGrainResetTimer; +#endif +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/FuzzyLayer.cpp b/netwerk/base/FuzzyLayer.cpp new file mode 100644 index 0000000000..8a9ee694dd --- /dev/null +++ b/netwerk/base/FuzzyLayer.cpp @@ -0,0 +1,407 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FuzzyLayer.h" +#include "nsTHashMap.h" +#include "nsDeque.h" +#include "nsIRunnable.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" + +#include "prmem.h" +#include "prio.h" +#include "mozilla/Logging.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +LazyLogModule gFuzzingLog("nsFuzzingNecko"); + +#define FUZZING_LOG(args) \ + MOZ_LOG(mozilla::net::gFuzzingLog, mozilla::LogLevel::Verbose, args) + +// Mutex for modifying our hash tables +Mutex gConnRecvMutex("ConnectRecvMutex"); + +// This structure will be created by addNetworkFuzzingBuffer below +// and then added to the gNetworkFuzzingBuffers structure. +// +// It is assigned on connect and associated with the socket it belongs to. +typedef struct { + const uint8_t* buf; + size_t size; + bool allowRead; + bool allowUnused; + PRNetAddr* addr; +} NetworkFuzzingBuffer; + +// This holds all connections we have currently open. +static nsTHashMap<nsPtrHashKey<PRFileDesc>, NetworkFuzzingBuffer*> + gConnectedNetworkFuzzingBuffers; + +// This holds all buffers for connections we can still open. +static nsDeque<NetworkFuzzingBuffer> gNetworkFuzzingBuffers; + +// This is `true` once all connections are closed and either there are +// no buffers left to be used or all remaining buffers are marked optional. +// Used by `signalNetworkFuzzingDone` to tell the main thread if it needs +// to spin-wait for `gFuzzingConnClosed`. +static Atomic<bool> fuzzingNoWaitRequired(false); + +// Used to memorize if the main thread has indicated that it is done with +// its iteration and we don't expect more connections now. +static Atomic<bool> fuzzingMainSignaledDone(false); + +/* + * The flag `gFuzzingConnClosed` is set by `FuzzyClose` when all connections + * are closed *and* there are no more buffers in `gNetworkFuzzingBuffers` that + * must be used. The main thread spins until this becomes true to synchronize + * the fuzzing iteration between the main thread and the socket thread, if + * the prior call to `signalNetworkFuzzingDone` returned `false`. + */ +Atomic<bool> gFuzzingConnClosed(true); + +void addNetworkFuzzingBuffer(const uint8_t* data, size_t size, bool readFirst, + bool useIsOptional) { + if (size > INT32_MAX) { + MOZ_CRASH("Unsupported buffer size"); + } + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* buf = new NetworkFuzzingBuffer(); + buf->buf = data; + buf->size = size; + buf->allowRead = readFirst; + buf->allowUnused = useIsOptional; + buf->addr = nullptr; + + gNetworkFuzzingBuffers.Push(buf); + + fuzzingMainSignaledDone = false; + fuzzingNoWaitRequired = false; +} + +/* + * This method should be called by fuzzing from the main thread to signal to + * the layer code that a fuzzing iteration is done. As a result, we can throw + * away any optional buffers and signal back once all connections have been + * closed. The main thread should synchronize on all connections being closed + * after the actual request/action is complete. + */ +bool signalNetworkFuzzingDone() { + FUZZING_LOG(("[signalNetworkFuzzingDone] Called.")); + MutexAutoLock lock(gConnRecvMutex); + bool rv = false; + + if (fuzzingNoWaitRequired) { + FUZZING_LOG(("[signalNetworkFuzzingDone] Purging remaining buffers.")); + // Easy case, we already have no connections and non-optional buffers left. + gNetworkFuzzingBuffers.Erase(); + gFuzzingConnClosed = true; + rv = true; + } else { + // We still have either connections left open or non-optional buffers left. + // In this case, FuzzyClose will handle the tear-down and signaling. + fuzzingMainSignaledDone = true; + } + + return rv; +} + +static PRDescIdentity sFuzzyLayerIdentity; +static PRIOMethods sFuzzyLayerMethods; +static PRIOMethods* sFuzzyLayerMethodsPtr = nullptr; + +static PRInt16 PR_CALLBACK FuzzyPoll(PRFileDesc* fd, PRInt16 in_flags, + PRInt16* out_flags) { + *out_flags = 0; + + FUZZING_LOG(("[FuzzyPoll] Called with in_flags=%X.", in_flags)); + + NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd); + + if (in_flags & PR_POLL_READ && fuzzBuf && fuzzBuf->allowRead) { + *out_flags = PR_POLL_READ; + return PR_POLL_READ; + } + + if (in_flags & PR_POLL_WRITE) { + *out_flags = PR_POLL_WRITE; + return PR_POLL_WRITE; + } + + return in_flags; +} + +static PRStatus FuzzyConnect(PRFileDesc* fd, const PRNetAddr* addr, + PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.PopFront(); + if (!buf) { + FUZZING_LOG(("[FuzzyConnect] Denying additional connection.")); + return PR_FAILURE; + } + + gConnectedNetworkFuzzingBuffers.InsertOrUpdate(fd, buf); + fuzzingNoWaitRequired = false; + + FUZZING_LOG(("[FuzzyConnect] Successfully opened connection: %p", fd)); + + gFuzzingConnClosed = false; + + return PR_SUCCESS; +} + +static PRInt32 FuzzySendTo(PRFileDesc* fd, const void* buf, PRInt32 amount, + PRIntn flags, const PRNetAddr* addr, + PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd); + if (!fuzzBuf) { + NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.PopFront(); + if (!buf) { + FUZZING_LOG(("[FuzzySentTo] Denying additional connection.")); + return 0; + } + + gConnectedNetworkFuzzingBuffers.InsertOrUpdate(fd, buf); + + // Store connection address + buf->addr = new PRNetAddr; + memcpy(buf->addr, addr, sizeof(PRNetAddr)); + + fuzzingNoWaitRequired = false; + + FUZZING_LOG(("[FuzzySendTo] Successfully opened connection: %p", fd)); + + gFuzzingConnClosed = false; + } + + // Allow reading once the implementation has written at least some data + if (fuzzBuf && !fuzzBuf->allowRead) { + FUZZING_LOG(("[FuzzySendTo] Write received, allowing further reads.")); + fuzzBuf->allowRead = true; + } + + FUZZING_LOG(("[FuzzySendTo] Sent %" PRIx32 " bytes of data.", amount)); + + return amount; +} + +static PRInt32 FuzzySend(PRFileDesc* fd, const void* buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd); + if (!fuzzBuf) { + FUZZING_LOG(("[FuzzySend] Write on socket that is not connected.")); + amount = 0; + } + + // Allow reading once the implementation has written at least some data + if (fuzzBuf && !fuzzBuf->allowRead) { + FUZZING_LOG(("[FuzzySend] Write received, allowing further reads.")); + fuzzBuf->allowRead = true; + } + + FUZZING_LOG(("[FuzzySend] Sent %" PRIx32 " bytes of data.", amount)); + + return amount; +} + +static PRInt32 FuzzyWrite(PRFileDesc* fd, const void* buf, PRInt32 amount) { + return FuzzySend(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT); +} + +static PRInt32 FuzzyRecv(PRFileDesc* fd, void* buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity); + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd); + if (!fuzzBuf) { + FUZZING_LOG(("[FuzzyRecv] Denying read, connection is closed.")); + return 0; + } + + // As long as we haven't written anything, act as if no data was there yet + if (!fuzzBuf->allowRead) { + FUZZING_LOG(("[FuzzyRecv] Denying read, nothing written before.")); + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + } + + if (gFuzzingConnClosed) { + FUZZING_LOG(("[FuzzyRecv] Denying read, connection is closed.")); + return 0; + } + + // No data left, act as if the connection was closed. + if (!fuzzBuf->size) { + FUZZING_LOG(("[FuzzyRecv] Read failed, no data left.")); + return 0; + } + + // Use the remains of fuzzing buffer, if too little is left + if (fuzzBuf->size < (PRUint32)amount) amount = fuzzBuf->size; + + // Consume buffer, copy data + memcpy(buf, fuzzBuf->buf, amount); + + if (!(flags & PR_MSG_PEEK)) { + fuzzBuf->buf += amount; + fuzzBuf->size -= amount; + } + + FUZZING_LOG(("[FuzzyRecv] Read %" PRIx32 " bytes of data.", amount)); + + return amount; +} + +static PRInt32 FuzzyRecvFrom(PRFileDesc* fd, void* buf, PRInt32 amount, + PRIntn flags, PRNetAddr* addr, + PRIntervalTime timeout) { + // Return the same address used for initial SendTo on this fd + if (addr) { + NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd); + if (!fuzzBuf) { + FUZZING_LOG(("[FuzzyRecvFrom] Denying read, connection is closed.")); + return 0; + } + + if (fuzzBuf->addr) { + memcpy(addr, fuzzBuf->addr, sizeof(PRNetAddr)); + } else { + FUZZING_LOG(("[FuzzyRecvFrom] No address found for connection")); + } + } + return FuzzyRecv(fd, buf, amount, flags, timeout); +} + +static PRInt32 FuzzyRead(PRFileDesc* fd, void* buf, PRInt32 amount) { + return FuzzyRecv(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT); +} + +static PRStatus FuzzyClose(PRFileDesc* fd) { + if (!fd) { + return PR_FAILURE; + } + PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER); + MOZ_RELEASE_ASSERT(layer && layer->identity == sFuzzyLayerIdentity, + "Fuzzy Layer not on top of stack"); + + layer->dtor(layer); + + MutexAutoLock lock(gConnRecvMutex); + + NetworkFuzzingBuffer* fuzzBuf = nullptr; + if (gConnectedNetworkFuzzingBuffers.Remove(fd, &fuzzBuf)) { + FUZZING_LOG(("[FuzzyClose] Received close on socket %p", fd)); + if (fuzzBuf->addr) { + delete fuzzBuf->addr; + } + delete fuzzBuf; + } else { + /* Happens when close is called on a non-connected socket */ + FUZZING_LOG(("[FuzzyClose] Received close on unknown socket %p.", fd)); + } + + PRStatus ret = fd->methods->close(fd); + + if (!gConnectedNetworkFuzzingBuffers.Count()) { + // At this point, all connections are closed, but we might still have + // unused network buffers that were not marked as optional. + bool haveRemainingUnusedBuffers = false; + for (size_t i = 0; i < gNetworkFuzzingBuffers.GetSize(); ++i) { + NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.ObjectAt(i); + + if (!buf->allowUnused) { + haveRemainingUnusedBuffers = true; + break; + } + } + + if (haveRemainingUnusedBuffers) { + FUZZING_LOG( + ("[FuzzyClose] All connections closed, waiting for remaining " + "connections.")); + } else if (!fuzzingMainSignaledDone) { + // We have no connections left, but the main thread hasn't signaled us + // yet. For now, that means once the main thread signals us, we can tell + // it immediately that it won't have to wait for closing connections. + FUZZING_LOG( + ("[FuzzyClose] All connections closed, waiting for main thread.")); + fuzzingNoWaitRequired = true; + } else { + // No connections left and main thread is already done. Perform cleanup + // and then signal the main thread to continue. + FUZZING_LOG(("[FuzzyClose] All connections closed, cleaning up.")); + + gNetworkFuzzingBuffers.Erase(); + gFuzzingConnClosed = true; + + // We need to dispatch this so the main thread is guaranteed to wake up + nsCOMPtr<nsIRunnable> r(new mozilla::Runnable("Dummy")); + NS_DispatchToMainThread(r.forget()); + } + } else { + FUZZING_LOG(("[FuzzyClose] Connection closed.")); + } + + return ret; +} + +nsresult AttachFuzzyIOLayer(PRFileDesc* fd) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (!sFuzzyLayerMethodsPtr) { + sFuzzyLayerIdentity = PR_GetUniqueIdentity("Fuzzy Layer"); + sFuzzyLayerMethods = *PR_GetDefaultIOMethods(); + sFuzzyLayerMethods.connect = FuzzyConnect; + sFuzzyLayerMethods.send = FuzzySend; + sFuzzyLayerMethods.sendto = FuzzySendTo; + sFuzzyLayerMethods.write = FuzzyWrite; + sFuzzyLayerMethods.recv = FuzzyRecv; + sFuzzyLayerMethods.recvfrom = FuzzyRecvFrom; + sFuzzyLayerMethods.read = FuzzyRead; + sFuzzyLayerMethods.close = FuzzyClose; + sFuzzyLayerMethods.poll = FuzzyPoll; + sFuzzyLayerMethodsPtr = &sFuzzyLayerMethods; + } + + PRFileDesc* layer = + PR_CreateIOLayerStub(sFuzzyLayerIdentity, sFuzzyLayerMethodsPtr); + + if (!layer) { + return NS_ERROR_FAILURE; + } + + PRStatus status = PR_PushIOLayer(fd, PR_TOP_IO_LAYER, layer); + + if (status == PR_FAILURE) { + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/FuzzyLayer.h b/netwerk/base/FuzzyLayer.h new file mode 100644 index 0000000000..6b8109f949 --- /dev/null +++ b/netwerk/base/FuzzyLayer.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FuzzyLayer_h__ +#define FuzzyLayer_h__ + +#include "prerror.h" +#include "nsError.h" +#include "nsIFile.h" + +namespace mozilla { +namespace net { + +nsresult AttachFuzzyIOLayer(PRFileDesc* fd); + +extern Atomic<bool> gFuzzingConnClosed; +bool signalNetworkFuzzingDone(); + +void addNetworkFuzzingBuffer(const uint8_t* data, size_t size, + bool readFirst = false, + bool useIsOptional = false); + +} // namespace net +} // namespace mozilla + +#endif // FuzzyLayer_h__ diff --git a/netwerk/base/FuzzySecurityInfo.cpp b/netwerk/base/FuzzySecurityInfo.cpp new file mode 100644 index 0000000000..7a2405ab0a --- /dev/null +++ b/netwerk/base/FuzzySecurityInfo.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FuzzySecurityInfo.h" + +#include "nsIWebProgressListener.h" +#include "nsString.h" + +namespace mozilla { +namespace net { + +FuzzySecurityInfo::FuzzySecurityInfo() {} + +FuzzySecurityInfo::~FuzzySecurityInfo() {} + +NS_IMPL_ISUPPORTS(FuzzySecurityInfo, nsITransportSecurityInfo) + +NS_IMETHODIMP +FuzzySecurityInfo::GetSecurityState(uint32_t* state) { + *state = nsIWebProgressListener::STATE_IS_SECURE; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetErrorCode(int32_t* state) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetErrorCodeString(nsAString& aErrorString) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetFailedCertChain( + nsTArray<RefPtr<nsIX509Cert>>& aFailedCertChain) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetServerCert(nsIX509Cert** aServerCert) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetSucceededCertChain( + nsTArray<RefPtr<nsIX509Cert>>& aSucceededCertChain) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetCipherName(nsACString& aCipherName) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetKeyLength(uint32_t* aKeyLength) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetSecretKeyLength(uint32_t* aSecretKeyLength) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetKeaGroupName(nsACString& aKeaGroup) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetSignatureSchemeName(nsACString& aSignatureScheme) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetProtocolVersion(uint16_t* aProtocolVersion) { + NS_ENSURE_ARG_POINTER(aProtocolVersion); + // Must be >= TLS 1.2 for HTTP2 + *aProtocolVersion = nsITransportSecurityInfo::TLS_VERSION_1_2; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetCertificateTransparencyStatus( + uint16_t* aCertificateTransparencyStatus) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetIsDelegatedCredential(bool* aIsDelegCred) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetIsAcceptedEch(bool* aIsAcceptedEch) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetOverridableErrorCategory( + OverridableErrorCategory* aOverridableErrorCode) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetMadeOCSPRequests(bool* aMadeOCSPRequests) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetUsedPrivateDNS(bool* aUsedPrivateDNS) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetIsExtendedValidation(bool* aIsEV) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::ToString(nsACString& aResult) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +void FuzzySecurityInfo::SerializeToIPC(IPC::MessageWriter* aWriter) { + MOZ_CRASH("Unused"); +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetNegotiatedNPN(nsACString& aNegotiatedNPN) { + aNegotiatedNPN.Assign("h2"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetResumed(bool* aResumed) { + *aResumed = false; + return NS_OK; +} + +NS_IMETHODIMP FuzzySecurityInfo::GetIsBuiltCertChainRootBuiltInRoot( + bool* aIsBuiltInRoot) { + *aIsBuiltInRoot = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySecurityInfo::GetPeerId(nsACString& aResult) { + aResult.Assign(""_ns); + return NS_OK; +} + +} // namespace net + +} // namespace mozilla diff --git a/netwerk/base/FuzzySecurityInfo.h b/netwerk/base/FuzzySecurityInfo.h new file mode 100644 index 0000000000..121c6c8ba9 --- /dev/null +++ b/netwerk/base/FuzzySecurityInfo.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FuzzySecurityInfo_h__ +#define FuzzySecurityInfo_h__ + +#include "nsCOMPtr.h" +#include "nsITransportSecurityInfo.h" + +namespace mozilla { +namespace net { + +class FuzzySecurityInfo final : public nsITransportSecurityInfo { + public: + FuzzySecurityInfo(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORTSECURITYINFO + + protected: + virtual ~FuzzySecurityInfo(); +}; // class FuzzySecurityInfo + +} // namespace net +} // namespace mozilla + +#endif // FuzzySecurityInfo_h__ diff --git a/netwerk/base/FuzzySocketControl.cpp b/netwerk/base/FuzzySocketControl.cpp new file mode 100644 index 0000000000..ff53358417 --- /dev/null +++ b/netwerk/base/FuzzySocketControl.cpp @@ -0,0 +1,192 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FuzzySocketControl.h" + +#include "FuzzySecurityInfo.h" +#include "ipc/IPCMessageUtils.h" +#include "nsITlsHandshakeListener.h" +#include "sslt.h" + +namespace mozilla { +namespace net { + +FuzzySocketControl::FuzzySocketControl() {} + +FuzzySocketControl::~FuzzySocketControl() {} + +NS_IMPL_ISUPPORTS(FuzzySocketControl, nsITLSSocketControl) + +NS_IMETHODIMP +FuzzySocketControl::GetProviderFlags(uint32_t* aProviderFlags) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetKEAUsed(int16_t* aKea) { + // Can be ssl_kea_dh or ssl_kea_ecdh for HTTP2 + *aKea = ssl_kea_ecdh; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetKEAKeyBits(uint32_t* aKeyBits) { + // Must be >= 224 for ecdh and >= 2048 for dh when using HTTP2 + *aKeyBits = 256; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetSSLVersionUsed(int16_t* aSSLVersionUsed) { + // Must be >= TLS 1.2 for HTTP2 + *aSSLVersionUsed = nsITLSSocketControl::TLS_VERSION_1_2; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetSSLVersionOffered(int16_t* aSSLVersionOffered) { + *aSSLVersionOffered = nsITLSSocketControl::TLS_VERSION_1_2; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetMACAlgorithmUsed(int16_t* aMac) { + // The only valid choice for HTTP2 is SSL_MAC_AEAD + *aMac = nsITLSSocketControl::SSL_MAC_AEAD; + return NS_OK; +} + +bool FuzzySocketControl::GetDenyClientCert() { return false; } + +void FuzzySocketControl::SetDenyClientCert(bool aDenyClientCert) { + // Called by mozilla::net::nsHttpConnection::StartSpdy +} + +NS_IMETHODIMP +FuzzySocketControl::GetClientCertSent(bool* arg) { + *arg = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetFailedVerification(bool* arg) { + *arg = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetAlpnEarlySelection(nsACString& aAlpnSelected) { + // TODO: For now we don't support early selection + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +FuzzySocketControl::GetEarlyDataAccepted(bool* aAccepted) { + *aAccepted = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::DriveHandshake() { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::IsAcceptableForHost(const nsACString& hostname, + bool* _retval) { + NS_ENSURE_ARG(_retval); + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::TestJoinConnection(const nsACString& npnProtocol, + const nsACString& hostname, int32_t port, + bool* _retval) { + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::JoinConnection(const nsACString& npnProtocol, + const nsACString& hostname, int32_t port, + bool* _retval) { + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::ProxyStartSSL() { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::StartTLS() { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::SetNPNList(nsTArray<nsCString>& protocolArray) { + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetEsniTxt(nsACString& aEsniTxt) { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::SetEsniTxt(const nsACString& aEsniTxt) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetEchConfig(nsACString& aEchConfig) { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::SetEchConfig(const nsACString& aEchConfig) { + MOZ_CRASH("Unused"); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::GetRetryEchConfig(nsACString& aEchConfig) { return NS_OK; } + +NS_IMETHODIMP +FuzzySocketControl::GetPeerId(nsACString& aResult) { + aResult.Assign(""_ns); + return NS_OK; +} + +NS_IMETHODIMP FuzzySocketControl::SetHandshakeCallbackListener( + nsITlsHandshakeCallbackListener* callback) { + if (callback) { + callback->HandshakeDone(); + } + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::DisableEarlyData(void) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP FuzzySocketControl::GetSecurityInfo( + nsITransportSecurityInfo** aSecurityInfo) { + nsCOMPtr<nsITransportSecurityInfo> securityInfo(new FuzzySecurityInfo()); + securityInfo.forget(aSecurityInfo); + return NS_OK; +} + +NS_IMETHODIMP +FuzzySocketControl::AsyncGetSecurityInfo(JSContext* aCx, + mozilla::dom::Promise** aPromise) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP FuzzySocketControl::Claim() { return NS_OK; } + +NS_IMETHODIMP FuzzySocketControl::SetBrowserId(uint64_t) { return NS_OK; } + +NS_IMETHODIMP FuzzySocketControl::GetBrowserId(uint64_t*) { + MOZ_CRASH("Unused"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/FuzzySocketControl.h b/netwerk/base/FuzzySocketControl.h new file mode 100644 index 0000000000..9debbe32e8 --- /dev/null +++ b/netwerk/base/FuzzySocketControl.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FuzzySocketControl_h__ +#define FuzzySocketControl_h__ + +#include "nsITLSSocketControl.h" + +namespace mozilla { +namespace net { + +class FuzzySocketControl final : public nsITLSSocketControl { + public: + FuzzySocketControl(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITLSSOCKETCONTROL + + protected: + virtual ~FuzzySocketControl(); +}; // class FuzzySocketControl + +} // namespace net +} // namespace mozilla + +#endif // FuzzySocketControl_h__ diff --git a/netwerk/base/IOActivityMonitor.cpp b/netwerk/base/IOActivityMonitor.cpp new file mode 100644 index 0000000000..76047a7a00 --- /dev/null +++ b/netwerk/base/IOActivityMonitor.cpp @@ -0,0 +1,495 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "IOActivityMonitor.h" +#include "nsPrintfCString.h" +#include "nsSocketTransport2.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/Promise.h" +#include "prerror.h" +#include "prio.h" +#include "prmem.h" +#include <vector> + +using namespace mozilla; +using namespace mozilla::net; + +mozilla::StaticRefPtr<IOActivityMonitor> gInstance; +static Atomic<bool> gActivated(false); +static PRDescIdentity sNetActivityMonitorLayerIdentity; +static PRIOMethods sNetActivityMonitorLayerMethods; +static PRIOMethods* sNetActivityMonitorLayerMethodsPtr = nullptr; + +// Maximum number of activities entries in the monitoring class +#define MAX_ACTIVITY_ENTRIES 1000 + +// ActivityMonitorSecret is stored in the activity monitor layer +// and provides a method to get the location. +// +// A location can be : +// - a TCP or UDP socket. The form will be socket://ip:port +// - a File. The form will be file://path +// +// For other cases, the location will be fd://number +class ActivityMonitorSecret final { + public: + // constructor used for sockets + explicit ActivityMonitorSecret(PRFileDesc* aFd) { + mFd = aFd; + mLocationSet = false; + } + + // constructor used for files + explicit ActivityMonitorSecret(PRFileDesc* aFd, const char* aLocation) { + mFd = aFd; + mLocation.AppendPrintf("file://%s", aLocation); + mLocationSet = true; + } + + nsCString getLocation() { + if (!mLocationSet) { + LazySetLocation(); + } + return mLocation; + } + + private: + // Called to set the location using the FD on the first getLocation() usage + // which is typically when a socket is opened. If done earlier, at + // construction time, the host won't be bound yet. + // + // If the location is a file, it needs to be initialized in the + // constructor. + void LazySetLocation() { + mLocationSet = true; + PRFileDesc* extract = mFd; + while (PR_GetDescType(extract) == PR_DESC_LAYERED) { + if (!extract->lower) { + break; + } + extract = extract->lower; + } + + PRDescType fdType = PR_GetDescType(extract); + // we should not use LazySetLocation for files + MOZ_ASSERT(fdType != PR_DESC_FILE); + + switch (fdType) { + case PR_DESC_SOCKET_TCP: + case PR_DESC_SOCKET_UDP: { + mLocation.AppendPrintf("socket://"); + PRNetAddr addr; + PRStatus status = PR_GetSockName(mFd, &addr); + if (NS_WARN_IF(status == PR_FAILURE)) { + mLocation.AppendPrintf("unknown"); + break; + } + + // grabbing the host + char netAddr[mozilla::net::kNetAddrMaxCStrBufSize] = {0}; + status = PR_NetAddrToString(&addr, netAddr, sizeof(netAddr) - 1); + if (NS_WARN_IF(status == PR_FAILURE) || netAddr[0] == 0) { + mLocation.AppendPrintf("unknown"); + break; + } + mLocation.Append(netAddr); + + // adding the port + uint16_t port; + if (addr.raw.family == PR_AF_INET) { + port = addr.inet.port; + } else { + port = addr.ipv6.port; + } + mLocation.AppendPrintf(":%d", port); + } break; + + // for all other cases, we just send back fd://<value> + default: { + mLocation.AppendLiteral("fd://"); + mLocation.AppendInt(PR_FileDesc2NativeHandle(mFd)); + } + } // end switch + } + + private: + nsCString mLocation; + bool mLocationSet; + PRFileDesc* mFd; +}; + +// FileDesc2Location converts a PRFileDesc into a "location" by +// grabbing the ActivityMonitorSecret in layer->secret +static nsAutoCString FileDesc2Location(PRFileDesc* fd) { + nsAutoCString location; + PRFileDesc* monitorLayer = + PR_GetIdentitiesLayer(fd, sNetActivityMonitorLayerIdentity); + if (!monitorLayer) { + location.AppendPrintf("unknown"); + return location; + } + + ActivityMonitorSecret* secret = (ActivityMonitorSecret*)monitorLayer->secret; + location.AppendPrintf("%s", secret->getLocation().get()); + return location; +} + +// +// Wrappers around the socket APIS +// +static PRStatus nsNetMon_Connect(PRFileDesc* fd, const PRNetAddr* addr, + PRIntervalTime timeout) { + return fd->lower->methods->connect(fd->lower, addr, timeout); +} + +static PRStatus nsNetMon_Close(PRFileDesc* fd) { + if (!fd) { + return PR_FAILURE; + } + PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER); + MOZ_RELEASE_ASSERT( + layer && layer->identity == sNetActivityMonitorLayerIdentity, + "NetActivityMonitor Layer not on top of stack"); + + if (layer->secret) { + delete (ActivityMonitorSecret*)layer->secret; + layer->secret = nullptr; + } + layer->dtor(layer); + return fd->methods->close(fd); +} + +static int32_t nsNetMon_Read(PRFileDesc* fd, void* buf, int32_t len) { + int32_t ret = fd->lower->methods->read(fd->lower, buf, len); + if (ret >= 0) { + IOActivityMonitor::Read(fd, len); + } + return ret; +} + +static int32_t nsNetMon_Write(PRFileDesc* fd, const void* buf, int32_t len) { + int32_t ret = fd->lower->methods->write(fd->lower, buf, len); + if (ret > 0) { + IOActivityMonitor::Write(fd, len); + } + return ret; +} + +static int32_t nsNetMon_Writev(PRFileDesc* fd, const PRIOVec* iov, int32_t size, + PRIntervalTime timeout) { + int32_t ret = fd->lower->methods->writev(fd->lower, iov, size, timeout); + if (ret > 0) { + IOActivityMonitor::Write(fd, size); + } + return ret; +} + +static int32_t nsNetMon_Recv(PRFileDesc* fd, void* buf, int32_t amount, + int flags, PRIntervalTime timeout) { + int32_t ret = + fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout); + if (ret > 0) { + IOActivityMonitor::Read(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_Send(PRFileDesc* fd, const void* buf, int32_t amount, + int flags, PRIntervalTime timeout) { + int32_t ret = + fd->lower->methods->send(fd->lower, buf, amount, flags, timeout); + if (ret > 0) { + IOActivityMonitor::Write(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_RecvFrom(PRFileDesc* fd, void* buf, int32_t amount, + int flags, PRNetAddr* addr, + PRIntervalTime timeout) { + int32_t ret = fd->lower->methods->recvfrom(fd->lower, buf, amount, flags, + addr, timeout); + if (ret > 0) { + IOActivityMonitor::Read(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_SendTo(PRFileDesc* fd, const void* buf, int32_t amount, + int flags, const PRNetAddr* addr, + PRIntervalTime timeout) { + int32_t ret = + fd->lower->methods->sendto(fd->lower, buf, amount, flags, addr, timeout); + if (ret > 0) { + IOActivityMonitor::Write(fd, amount); + } + return ret; +} + +static int32_t nsNetMon_AcceptRead(PRFileDesc* listenSock, + PRFileDesc** acceptedSock, + PRNetAddr** peerAddr, void* buf, + int32_t amount, PRIntervalTime timeout) { + int32_t ret = listenSock->lower->methods->acceptread( + listenSock->lower, acceptedSock, peerAddr, buf, amount, timeout); + if (ret > 0) { + IOActivityMonitor::Read(listenSock, amount); + } + return ret; +} + +// +// Class IOActivityMonitor +// +NS_IMPL_ISUPPORTS(IOActivityMonitor, nsINamed) + +IOActivityMonitor::IOActivityMonitor() : mLock("IOActivityMonitor::mLock") { + RefPtr<IOActivityMonitor> mon(gInstance); + MOZ_ASSERT(!mon, "multiple IOActivityMonitor instances!"); +} + +// static +void IOActivityMonitor::RequestActivities(dom::Promise* aPromise) { + MOZ_ASSERT(aPromise); + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + aPromise->MaybeReject(NS_ERROR_FAILURE); + return; + } + + mon->RequestActivitiesInternal(aPromise); +} + +void IOActivityMonitor::RequestActivitiesInternal(dom::Promise* aPromise) { + nsresult result = NS_OK; + FallibleTArray<dom::IOActivityDataDictionary> activities; + + { + mozilla::MutexAutoLock lock(mLock); + // Remove inactive activities + for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) { + dom::IOActivityDataDictionary* activity = &iter.Data(); + if (activity->mRx == 0 && activity->mTx == 0) { + iter.Remove(); + } else { + if (NS_WARN_IF(!activities.AppendElement(iter.Data(), fallible))) { + result = NS_ERROR_OUT_OF_MEMORY; + break; + } + } + } + } + + if (NS_WARN_IF(NS_FAILED(result))) { + aPromise->MaybeReject(result); + return; + } + aPromise->MaybeResolve(activities); +} + +NS_IMETHODIMP +IOActivityMonitor::GetName(nsACString& aName) { + aName.AssignLiteral("IOActivityMonitor"); + return NS_OK; +} + +// static +bool IOActivityMonitor::IsActive() { return gActivated; } + +// static +already_AddRefed<IOActivityMonitor> IOActivityMonitor::Get() { + if (!gActivated) { + return nullptr; + } + + RefPtr<IOActivityMonitor> mon = gInstance; + return mon.forget(); +} + +nsresult IOActivityMonitor::Init() { + if (IsActive()) { + return NS_ERROR_ALREADY_INITIALIZED; + } + RefPtr<IOActivityMonitor> mon = new IOActivityMonitor(); + nsresult rv = mon->InitInternal(); + if (NS_SUCCEEDED(rv)) { + gInstance = mon; + ClearOnShutdown(&gInstance); + gActivated = true; + } + return rv; +} + +nsresult IOActivityMonitor::InitInternal() { + // wraps the socket APIs + if (!sNetActivityMonitorLayerMethodsPtr) { + sNetActivityMonitorLayerIdentity = + PR_GetUniqueIdentity("network activity monitor layer"); + sNetActivityMonitorLayerMethods = *PR_GetDefaultIOMethods(); + sNetActivityMonitorLayerMethods.connect = nsNetMon_Connect; + sNetActivityMonitorLayerMethods.read = nsNetMon_Read; + sNetActivityMonitorLayerMethods.write = nsNetMon_Write; + sNetActivityMonitorLayerMethods.writev = nsNetMon_Writev; + sNetActivityMonitorLayerMethods.recv = nsNetMon_Recv; + sNetActivityMonitorLayerMethods.send = nsNetMon_Send; + sNetActivityMonitorLayerMethods.recvfrom = nsNetMon_RecvFrom; + sNetActivityMonitorLayerMethods.sendto = nsNetMon_SendTo; + sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead; + sNetActivityMonitorLayerMethods.close = nsNetMon_Close; + sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods; + } + + return NS_OK; +} + +nsresult IOActivityMonitor::Shutdown() { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_NOT_INITIALIZED; + } + return mon->ShutdownInternal(); +} + +nsresult IOActivityMonitor::ShutdownInternal() { + mozilla::MutexAutoLock lock(mLock); + mActivities.Clear(); + gActivated = false; + return NS_OK; +} + +nsresult IOActivityMonitor::MonitorSocket(PRFileDesc* aFd) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_OK; + } + PRFileDesc* layer; + PRStatus status; + layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity, + sNetActivityMonitorLayerMethodsPtr); + if (!layer) { + return NS_ERROR_FAILURE; + } + + ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd); + layer->secret = reinterpret_cast<PRFilePrivate*>(secret); + status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer); + + if (status == PR_FAILURE) { + delete secret; + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult IOActivityMonitor::MonitorFile(PRFileDesc* aFd, const char* aPath) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_OK; + } + PRFileDesc* layer; + PRStatus status; + layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity, + sNetActivityMonitorLayerMethodsPtr); + if (!layer) { + return NS_ERROR_FAILURE; + } + + ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd, aPath); + layer->secret = reinterpret_cast<PRFilePrivate*>(secret); + + status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer); + if (status == PR_FAILURE) { + delete secret; + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool IOActivityMonitor::IncrementActivity(const nsACString& aLocation, + uint32_t aRx, uint32_t aTx) { + mLock.AssertCurrentThreadOwns(); + return mActivities.WithEntryHandle(aLocation, fallible, [&](auto&& entry) { + if (!entry) return false; + + if (*entry) { + // already registered + entry->Data().mTx += aTx; + entry->Data().mRx += aRx; + } else { + // Creating a new IOActivity. Notice that mActivities + // will grow indefinitely, which is OK since we won't + // have but a few hundreds entries at the most, but we + // want to assert we have at the most 1000 entries + MOZ_ASSERT(mActivities.Count() <= MAX_ACTIVITY_ENTRIES); + + dom::IOActivityDataDictionary activity; + activity.mLocation.Assign(aLocation); + activity.mTx = aTx; + activity.mRx = aRx; + + entry->Insert(std::move(activity)); + } + + return true; + }); +} + +nsresult IOActivityMonitor::Write(const nsACString& aLocation, + uint32_t aAmount) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->WriteInternal(aLocation, aAmount); +} + +nsresult IOActivityMonitor::Write(PRFileDesc* fd, uint32_t aAmount) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->Write(FileDesc2Location(fd), aAmount); +} + +nsresult IOActivityMonitor::WriteInternal(const nsACString& aLocation, + uint32_t aAmount) { + mozilla::MutexAutoLock lock(mLock); + if (!IncrementActivity(aLocation, aAmount, 0)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult IOActivityMonitor::Read(PRFileDesc* fd, uint32_t aAmount) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->Read(FileDesc2Location(fd), aAmount); +} + +nsresult IOActivityMonitor::Read(const nsACString& aLocation, + uint32_t aAmount) { + RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get(); + if (!mon) { + return NS_ERROR_FAILURE; + } + return mon->ReadInternal(aLocation, aAmount); +} + +nsresult IOActivityMonitor::ReadInternal(const nsACString& aLocation, + uint32_t aAmount) { + mozilla::MutexAutoLock lock(mLock); + if (!IncrementActivity(aLocation, 0, aAmount)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} diff --git a/netwerk/base/IOActivityMonitor.h b/netwerk/base/IOActivityMonitor.h new file mode 100644 index 0000000000..5e46bc9bfc --- /dev/null +++ b/netwerk/base/IOActivityMonitor.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IOActivityMonitor_h___ +#define IOActivityMonitor_h___ + +#include "mozilla/dom/ChromeUtilsBinding.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsClassHashtable.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsINamed.h" +#include "nsISupports.h" +#include "prinrval.h" +#include "prio.h" +#include "private/pprio.h" +#include <stdint.h> + +namespace mozilla { + +namespace dom { +class Promise; +} + +namespace net { + +#define IO_ACTIVITY_ENABLED_PREF "io.activity.enabled" + +using Activities = nsTHashMap<nsCStringHashKey, dom::IOActivityDataDictionary>; + +// IOActivityMonitor has several roles: +// - maintains an IOActivity per resource and updates it +// - sends a dump of the activities to a promise via RequestActivities +class IOActivityMonitor final : public nsINamed { + public: + IOActivityMonitor(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSINAMED + + // initializes and destroys the singleton + static nsresult Init(); + static nsresult Shutdown(); + + // collect amounts of data that are written/read by location + static nsresult Read(const nsACString& location, uint32_t aAmount); + static nsresult Write(const nsACString& location, uint32_t aAmount); + + static nsresult MonitorFile(PRFileDesc* aFd, const char* aPath); + static nsresult MonitorSocket(PRFileDesc* aFd); + static nsresult Read(PRFileDesc* fd, uint32_t aAmount); + static nsresult Write(PRFileDesc* fd, uint32_t aAmount); + + static bool IsActive(); + static void RequestActivities(dom::Promise* aPromise); + + private: + ~IOActivityMonitor() = default; + + static already_AddRefed<IOActivityMonitor> Get(); + + nsresult InitInternal(); + nsresult ShutdownInternal(); + bool IncrementActivity(const nsACString& location, uint32_t aRx, + uint32_t aTx); + nsresult WriteInternal(const nsACString& location, uint32_t aAmount); + nsresult ReadInternal(const nsACString& location, uint32_t aAmount); + void RequestActivitiesInternal(dom::Promise* aPromise); + + Activities mActivities; + // protects mActivities accesses + Mutex mLock MOZ_UNANNOTATED; +}; + +} // namespace net +} // namespace mozilla + +#endif /* IOActivityMonitor_h___ */ diff --git a/netwerk/base/IPv6Utils.h b/netwerk/base/IPv6Utils.h new file mode 100644 index 0000000000..437c0befec --- /dev/null +++ b/netwerk/base/IPv6Utils.h @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NETWORK_IPV6_UTILS_H_ +#define NETWORK_IPV6_UTILS_H_ + +namespace mozilla { +namespace net { +namespace utils { + +// IPv6 address scopes. +#define IPV6_SCOPE_GLOBAL 0 // Global scope. +#define IPV6_SCOPE_LINKLOCAL 1 // Link-local scope. +#define IPV6_SCOPE_SITELOCAL 2 // Site-local scope (deprecated). +#define IPV6_SCOPE_UNIQUELOCAL 3 // Unique local +#define IPV6_SCOPE_NODELOCAL 4 // Loopback + +// Return the scope of the given address. +static int ipv6_scope(const unsigned char addr[16]) { + const unsigned char* b = addr; + unsigned short w = (unsigned short)((b[0] << 8) | b[1]); + + if ((b[0] & 0xFE) == 0xFC) { + return IPV6_SCOPE_UNIQUELOCAL; + } + switch (w & 0xFFC0) { + case 0xFE80: + return IPV6_SCOPE_LINKLOCAL; + case 0xFEC0: + return IPV6_SCOPE_SITELOCAL; + case 0x0000: + w = b[1] | b[2] | b[3] | b[4] | b[5] | b[6] | b[7] | b[8] | b[9] | b[10] | + b[11] | b[12] | b[13] | b[14]; + if (w || b[15] != 0x01) { + break; + } + return IPV6_SCOPE_NODELOCAL; + default: + break; + } + + return IPV6_SCOPE_GLOBAL; +} + +} // namespace utils +} // namespace net +} // namespace mozilla + +#endif // NETWORK_IPV6_UTILS_H_ diff --git a/netwerk/base/InterceptionInfo.cpp b/netwerk/base/InterceptionInfo.cpp new file mode 100644 index 0000000000..977ec02e47 --- /dev/null +++ b/netwerk/base/InterceptionInfo.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/net/InterceptionInfo.h" +#include "nsContentUtils.h" + +namespace mozilla::net { + +NS_IMPL_ISUPPORTS(InterceptionInfo, nsIInterceptionInfo) + +InterceptionInfo::InterceptionInfo(nsIPrincipal* aTriggeringPrincipal, + nsContentPolicyType aContentPolicyType, + const RedirectHistoryArray& aRedirectChain, + bool aFromThirdParty) + : mTriggeringPrincipal(aTriggeringPrincipal), + mContentPolicyType(aContentPolicyType), + mFromThirdParty(aFromThirdParty) { + SetRedirectChain(aRedirectChain); +} + +nsIPrincipal* InterceptionInfo::TriggeringPrincipal() { + return mTriggeringPrincipal; +} + +void InterceptionInfo::SetTriggeringPrincipal(nsIPrincipal* aPrincipal) { + mTriggeringPrincipal = aPrincipal; +} + +nsContentPolicyType InterceptionInfo::ContentPolicyType() { + return mContentPolicyType; +} + +nsContentPolicyType InterceptionInfo::ExternalContentPolicyType() { + return static_cast<nsContentPolicyType>( + nsContentUtils::InternalContentPolicyTypeToExternal(mContentPolicyType)); +} + +void InterceptionInfo::SetContentPolicyType( + const nsContentPolicyType aContentPolicyType) { + mContentPolicyType = aContentPolicyType; +} + +const RedirectHistoryArray& InterceptionInfo::RedirectChain() { + return mRedirectChain; +} + +void InterceptionInfo::SetRedirectChain( + const RedirectHistoryArray& aRedirectChain) { + for (auto entry : aRedirectChain) { + mRedirectChain.AppendElement(entry); + } +} + +bool InterceptionInfo::FromThirdParty() { return mFromThirdParty; } + +void InterceptionInfo::SetFromThirdParty(bool aFromThirdParty) { + mFromThirdParty = aFromThirdParty; +} + +} // namespace mozilla::net diff --git a/netwerk/base/InterceptionInfo.h b/netwerk/base/InterceptionInfo.h new file mode 100644 index 0000000000..ad2f2a4499 --- /dev/null +++ b/netwerk/base/InterceptionInfo.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_InterceptionInfo_h +#define mozilla_net_InterceptionInfo_h + +#include "nsIContentSecurityPolicy.h" +#include "nsIInterceptionInfo.h" +#include "nsIPrincipal.h" +#include "nsIRedirectHistoryEntry.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" + +namespace mozilla::net { + +using RedirectHistoryArray = nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>; + +class InterceptionInfo final : public nsIInterceptionInfo { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERCEPTIONINFO + + InterceptionInfo(nsIPrincipal* aTriggeringPrincipal, + nsContentPolicyType aContentPolicyType, + const RedirectHistoryArray& aRedirectChain, + bool aFromThirdParty); + + using nsIInterceptionInfo::GetExtContentPolicyType; + + private: + ~InterceptionInfo() = default; + + nsCOMPtr<nsIPrincipal> mTriggeringPrincipal; + nsContentPolicyType mContentPolicyType{nsIContentPolicy::TYPE_INVALID}; + RedirectHistoryArray mRedirectChain; + bool mFromThirdParty = false; +}; + +} // namespace mozilla::net + +#endif diff --git a/netwerk/base/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp new file mode 100644 index 0000000000..d544bf7275 --- /dev/null +++ b/netwerk/base/LoadContextInfo.cpp @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LoadContextInfo.h" + +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/StoragePrincipalHelper.h" +#include "nsDocShell.h" +#include "nsIChannel.h" +#include "nsILoadContext.h" +#include "nsIWebNavigation.h" +#include "nsNetUtil.h" + +using namespace mozilla::dom; +namespace mozilla { +namespace net { + +// LoadContextInfo + +NS_IMPL_ISUPPORTS(LoadContextInfo, nsILoadContextInfo) + +LoadContextInfo::LoadContextInfo(bool aIsAnonymous, + OriginAttributes aOriginAttributes) + : mIsAnonymous(aIsAnonymous), + mOriginAttributes(std::move(aOriginAttributes)) {} + +NS_IMETHODIMP LoadContextInfo::GetIsPrivate(bool* aIsPrivate) { + *aIsPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfo::GetIsAnonymous(bool* aIsAnonymous) { + *aIsAnonymous = mIsAnonymous; + return NS_OK; +} + +OriginAttributes const* LoadContextInfo::OriginAttributesPtr() { + return &mOriginAttributes; +} + +NS_IMETHODIMP LoadContextInfo::GetOriginAttributes( + JSContext* aCx, JS::MutableHandle<JS::Value> aVal) { + if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +// LoadContextInfoFactory + +NS_IMPL_ISUPPORTS(LoadContextInfoFactory, nsILoadContextInfoFactory) + +NS_IMETHODIMP LoadContextInfoFactory::GetDefault( + nsILoadContextInfo** aDefault) { + nsCOMPtr<nsILoadContextInfo> info = + GetLoadContextInfo(false, OriginAttributes()); + info.forget(aDefault); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::GetPrivate( + nsILoadContextInfo** aPrivate) { + OriginAttributes attrs; + attrs.SyncAttributesWithPrivateBrowsing(true); + nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(false, attrs); + info.forget(aPrivate); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::GetAnonymous( + nsILoadContextInfo** aAnonymous) { + nsCOMPtr<nsILoadContextInfo> info = + GetLoadContextInfo(true, OriginAttributes()); + info.forget(aAnonymous); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::Custom( + bool aAnonymous, JS::Handle<JS::Value> aOriginAttributes, JSContext* cx, + nsILoadContextInfo** _retval) { + OriginAttributes attrs; + bool status = attrs.Init(cx, aOriginAttributes); + NS_ENSURE_TRUE(status, NS_ERROR_FAILURE); + + nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aAnonymous, attrs); + info.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::FromLoadContext( + nsILoadContext* aLoadContext, bool aAnonymous, + nsILoadContextInfo** _retval) { + nsCOMPtr<nsILoadContextInfo> info = + GetLoadContextInfo(aLoadContext, aAnonymous); + info.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::FromWindow(nsIDOMWindow* aWindow, + bool aAnonymous, + nsILoadContextInfo** _retval) { + nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aWindow, aAnonymous); + info.forget(_retval); + return NS_OK; +} + +// Helper functions + +LoadContextInfo* GetLoadContextInfo(nsIChannel* aChannel) { + nsresult rv; + + DebugOnly<bool> pb = NS_UsePrivateBrowsing(aChannel); + + bool anon = false; + nsLoadFlags loadFlags; + rv = aChannel->GetLoadFlags(&loadFlags); + if (NS_SUCCEEDED(rv)) { + anon = !!(loadFlags & nsIChannel::LOAD_ANONYMOUS); + } + + OriginAttributes oa; + StoragePrincipalHelper::GetOriginAttributesForNetworkState(aChannel, oa); + MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0)); + + return new LoadContextInfo(anon, oa); +} + +LoadContextInfo* GetLoadContextInfo(nsILoadContext* aLoadContext, + bool aIsAnonymous) { + if (!aLoadContext) { + return new LoadContextInfo(aIsAnonymous, OriginAttributes()); + } + + OriginAttributes oa; + aLoadContext->GetOriginAttributes(oa); + +#ifdef DEBUG + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aLoadContext); + if (!docShell || + nsDocShell::Cast(docShell)->GetBrowsingContext()->IsContent()) { + MOZ_ASSERT(aLoadContext->UsePrivateBrowsing() == + (oa.mPrivateBrowsingId > 0)); + } +#endif + + return new LoadContextInfo(aIsAnonymous, oa); +} + +LoadContextInfo* GetLoadContextInfo(nsIDOMWindow* aWindow, bool aIsAnonymous) { + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow); + nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav); + + return GetLoadContextInfo(loadContext, aIsAnonymous); +} + +LoadContextInfo* GetLoadContextInfo(nsILoadContextInfo* aInfo) { + return new LoadContextInfo(aInfo->IsAnonymous(), + *aInfo->OriginAttributesPtr()); +} + +LoadContextInfo* GetLoadContextInfo(bool const aIsAnonymous, + OriginAttributes const& aOriginAttributes) { + return new LoadContextInfo(aIsAnonymous, aOriginAttributes); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/LoadContextInfo.h b/netwerk/base/LoadContextInfo.h new file mode 100644 index 0000000000..efaf912db4 --- /dev/null +++ b/netwerk/base/LoadContextInfo.h @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsLoadContextInfo_h__ +#define nsLoadContextInfo_h__ + +#include "nsILoadContextInfo.h" + +class nsIChannel; +class nsILoadContext; + +namespace mozilla { +namespace net { + +class LoadContextInfo final : public nsILoadContextInfo { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSILOADCONTEXTINFO + + LoadContextInfo(bool aIsAnonymous, OriginAttributes aOriginAttributes); + + private: + virtual ~LoadContextInfo() = default; + + protected: + bool mIsAnonymous : 1; + OriginAttributes mOriginAttributes; +}; + +class LoadContextInfoFactory : public nsILoadContextInfoFactory { + virtual ~LoadContextInfoFactory() = default; + + public: + NS_DECL_ISUPPORTS // deliberately not thread-safe + NS_DECL_NSILOADCONTEXTINFOFACTORY +}; + +LoadContextInfo* GetLoadContextInfo(nsIChannel* aChannel); + +LoadContextInfo* GetLoadContextInfo(nsILoadContext* aLoadContext, + bool aIsAnonymous); + +LoadContextInfo* GetLoadContextInfo(nsIDOMWindow* aWindow, bool aIsAnonymous); + +LoadContextInfo* GetLoadContextInfo(nsILoadContextInfo* aInfo); + +LoadContextInfo* GetLoadContextInfo(bool const aIsAnonymous, + OriginAttributes const& aOriginAttributes); + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp new file mode 100644 index 0000000000..eb90324c37 --- /dev/null +++ b/netwerk/base/LoadInfo.cpp @@ -0,0 +1,2372 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/LoadInfo.h" + +#include "js/Array.h" // JS::NewArrayObject +#include "js/PropertyAndElement.h" // JS_DefineElement +#include "mozilla/Assertions.h" +#include "mozilla/ExpandedPrincipal.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/ClientSource.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/Performance.h" +#include "mozilla/dom/PerformanceStorage.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozIThirdPartyUtil.h" +#include "ThirdPartyUtil.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIDocShell.h" +#include "mozilla/dom/Document.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIScriptElement.h" +#include "nsISupportsImpl.h" +#include "nsISupportsUtils.h" +#include "nsIXPConnect.h" +#include "nsDocShell.h" +#include "nsGlobalWindowInner.h" +#include "nsMixedContentBlocker.h" +#include "nsQueryObject.h" +#include "nsRedirectHistoryEntry.h" +#include "nsSandboxFlags.h" +#include "nsICookieService.h" + +using namespace mozilla::dom; + +namespace mozilla::net { + +static nsCString CurrentRemoteType() { + MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsContentProcess()); + if (ContentChild* cc = ContentChild::GetSingleton()) { + return nsCString(cc->GetRemoteType()); + } + return NOT_REMOTE_TYPE; +} + +static nsContentPolicyType InternalContentPolicyTypeForFrame( + CanonicalBrowsingContext* aBrowsingContext) { + const auto& maybeEmbedderElementType = + aBrowsingContext->GetEmbedderElementType(); + MOZ_ASSERT(maybeEmbedderElementType.isSome()); + auto embedderElementType = maybeEmbedderElementType.value(); + + // Assign same type as in nsDocShell::DetermineContentType. + // N.B. internal content policy type will never be TYPE_DOCUMENT + return embedderElementType.EqualsLiteral("iframe") + ? nsIContentPolicy::TYPE_INTERNAL_IFRAME + : nsIContentPolicy::TYPE_INTERNAL_FRAME; +} + +/* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForDocument( + dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, const nsACString& aTriggeringRemoteType, + const OriginAttributes& aOriginAttributes, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags) { + return MakeAndAddRef<LoadInfo>(aBrowsingContext, aURI, aTriggeringPrincipal, + aTriggeringRemoteType, aOriginAttributes, + aSecurityFlags, aSandboxFlags); +} + +/* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForFrame( + dom::CanonicalBrowsingContext* aBrowsingContext, + nsIPrincipal* aTriggeringPrincipal, const nsACString& aTriggeringRemoteType, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) { + return MakeAndAddRef<LoadInfo>(aBrowsingContext, aTriggeringPrincipal, + aTriggeringRemoteType, aSecurityFlags, + aSandboxFlags); +} + +/* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForNonDocument( + dom::WindowGlobalParent* aParentWGP, nsIPrincipal* aTriggeringPrincipal, + nsContentPolicyType aContentPolicyType, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags) { + return MakeAndAddRef<LoadInfo>( + aParentWGP, aTriggeringPrincipal, aParentWGP->GetRemoteType(), + aContentPolicyType, aSecurityFlags, aSandboxFlags); +} + +LoadInfo::LoadInfo( + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsINode* aLoadingContext, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + const Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo, + const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController, + uint32_t aSandboxFlags, bool aSkipCheckForBrokenURLOrZeroSized) + : mLoadingPrincipal(aLoadingContext ? aLoadingContext->NodePrincipal() + : aLoadingPrincipal), + mTriggeringPrincipal(aTriggeringPrincipal ? aTriggeringPrincipal + : mLoadingPrincipal.get()), + mTriggeringRemoteType(CurrentRemoteType()), + mSandboxedNullPrincipalID(nsID::GenerateUUID()), + mClientInfo(aLoadingClientInfo), + mController(aController), + mLoadingContext(do_GetWeakReference(aLoadingContext)), + mSecurityFlags(aSecurityFlags), + mSandboxFlags(aSandboxFlags), + mInternalContentPolicyType(aContentPolicyType), + mSkipCheckForBrokenURLOrZeroSized(aSkipCheckForBrokenURLOrZeroSized) { + MOZ_ASSERT(mLoadingPrincipal); + MOZ_ASSERT(mTriggeringPrincipal); + +#ifdef DEBUG + // TYPE_DOCUMENT loads initiated by javascript tests will go through + // nsIOService and use the wrong constructor. Don't enforce the + // !TYPE_DOCUMENT check in those cases + bool skipContentTypeCheck = false; + skipContentTypeCheck = + Preferences::GetBool("network.loadinfo.skip_type_assertion"); +#endif + + // This constructor shouldn't be used for TYPE_DOCUMENT loads that don't + // have a loadingPrincipal + MOZ_ASSERT(skipContentTypeCheck || mLoadingPrincipal || + mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT); + + // We should only get an explicit controller for subresource requests. + MOZ_DIAGNOSTIC_ASSERT(aController.isNothing() || + !nsContentUtils::IsNonSubresourceInternalPolicyType( + mInternalContentPolicyType)); + + // TODO(bug 1259873): Above, we initialize mIsThirdPartyContext to false + // meaning that consumers of LoadInfo that don't pass a context or pass a + // context from which we can't find a window will default to assuming that + // they're 1st party. It would be nice if we could default "safe" and assume + // that we are 3rd party until proven otherwise. + + // if consumers pass both, aLoadingContext and aLoadingPrincipal + // then the loadingPrincipal must be the same as the node's principal + MOZ_ASSERT(!aLoadingContext || !aLoadingPrincipal || + aLoadingContext->NodePrincipal() == aLoadingPrincipal); + + // if the load is sandboxed, we can not also inherit the principal + if (mSandboxFlags & SANDBOXED_ORIGIN) { + mForceInheritPrincipalDropped = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + ExtContentPolicyType externalType = + nsContentUtils::InternalContentPolicyTypeToExternal(aContentPolicyType); + + if (aLoadingContext) { + // Ensure that all network requests for a window client have the ClientInfo + // properly set. Workers must currently pass the loading ClientInfo + // explicitly. We allow main thread requests to explicitly pass the value as + // well. + if (mClientInfo.isNothing()) { + mClientInfo = aLoadingContext->OwnerDoc()->GetClientInfo(); + } + + // For subresource loads set the service worker based on the calling + // context's controller. Workers must currently pass the controller in + // explicitly. We allow main thread requests to explicitly pass the value + // as well, but otherwise extract from the loading context here. + if (mController.isNothing() && + !nsContentUtils::IsNonSubresourceInternalPolicyType( + mInternalContentPolicyType)) { + mController = aLoadingContext->OwnerDoc()->GetController(); + } + + nsCOMPtr<nsPIDOMWindowOuter> contextOuter = + aLoadingContext->OwnerDoc()->GetWindow(); + if (contextOuter) { + ComputeIsThirdPartyContext(contextOuter); + RefPtr<dom::BrowsingContext> bc = contextOuter->GetBrowsingContext(); + MOZ_ASSERT(bc); + mBrowsingContextID = bc->Id(); + + nsGlobalWindowInner* innerWindow = + nsGlobalWindowInner::Cast(contextOuter->GetCurrentInnerWindow()); + if (innerWindow) { + mTopLevelPrincipal = innerWindow->GetTopLevelAntiTrackingPrincipal(); + + if (!mTopLevelPrincipal && + externalType == ExtContentPolicy::TYPE_SUBDOCUMENT && bc->IsTop()) { + // If this is the first level iframe, innerWindow is our top-level + // principal. + mTopLevelPrincipal = innerWindow->GetPrincipal(); + } + } + + // Let's inherit the cookie behavior and permission from the parent + // document. + mCookieJarSettings = aLoadingContext->OwnerDoc()->CookieJarSettings(); + } + + mInnerWindowID = aLoadingContext->OwnerDoc()->InnerWindowID(); + RefPtr<WindowContext> ctx = WindowContext::GetById(mInnerWindowID); + if (ctx) { + mLoadingEmbedderPolicy = ctx->GetEmbedderPolicy(); + } + mDocumentHasUserInteracted = + aLoadingContext->OwnerDoc()->UserHasInteracted(); + + // Inherit HTTPS-Only Mode flags from parent document + mHttpsOnlyStatus |= aLoadingContext->OwnerDoc()->HttpsOnlyStatus(); + + // When the element being loaded is a frame, we choose the frame's window + // for the window ID and the frame element's window as the parent + // window. This is the behavior that Chrome exposes to add-ons. + // NB: If the frameLoaderOwner doesn't have a frame loader, then the load + // must be coming from an object (such as a plugin) that's loaded into it + // instead of a document being loaded. In that case, treat this object like + // any other non-document-loading element. + RefPtr<nsFrameLoaderOwner> frameLoaderOwner = + do_QueryObject(aLoadingContext); + RefPtr<nsFrameLoader> fl = + frameLoaderOwner ? frameLoaderOwner->GetFrameLoader() : nullptr; + if (fl) { + nsCOMPtr<nsIDocShell> docShell = fl->GetDocShell(IgnoreErrors()); + if (docShell) { + nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_GetInterface(docShell); + if (outerWindow) { + RefPtr<dom::BrowsingContext> bc = outerWindow->GetBrowsingContext(); + mFrameBrowsingContextID = bc ? bc->Id() : 0; + } + } + } + + // if the document forces all mixed content to be blocked, then we + // store that bit for all requests on the loadinfo. + mBlockAllMixedContent = + aLoadingContext->OwnerDoc()->GetBlockAllMixedContent(false) || + (nsContentUtils::IsPreloadType(mInternalContentPolicyType) && + aLoadingContext->OwnerDoc()->GetBlockAllMixedContent(true)); + + if (mLoadingPrincipal && BasePrincipal::Cast(mTriggeringPrincipal) + ->OverridesCSP(mLoadingPrincipal)) { + // if the load is triggered by an addon which potentially overrides the + // CSP of the document, then do not force insecure requests to be + // upgraded. + mUpgradeInsecureRequests = false; + } else { + // if the document forces all requests to be upgraded from http to https, + // then we should do that for all requests. If it only forces preloads to + // be upgraded then we should enforce upgrade insecure requests only for + // preloads. + mUpgradeInsecureRequests = + aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(false) || + (nsContentUtils::IsPreloadType(mInternalContentPolicyType) && + aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(true)); + } + + if (nsMixedContentBlocker::IsUpgradableContentType( + mInternalContentPolicyType, /* aConsiderPrefs */ false)) { + // Check the load is within a secure context but ignore loopback URLs + if (mLoadingPrincipal->GetIsOriginPotentiallyTrustworthy() && + !mLoadingPrincipal->GetIsLoopbackHost()) { + if (nsMixedContentBlocker::IsUpgradableContentType( + mInternalContentPolicyType, /* aConsiderPrefs */ true)) { + mBrowserUpgradeInsecureRequests = true; + } else { + mBrowserWouldUpgradeInsecureRequests = true; + } + } + } + } + mOriginAttributes = mLoadingPrincipal->OriginAttributesRef(); + + // We need to do this after inheriting the document's origin attributes + // above, in case the loading principal ends up being the system principal. + if (aLoadingContext) { + nsCOMPtr<nsILoadContext> loadContext = + aLoadingContext->OwnerDoc()->GetLoadContext(); + nsCOMPtr<nsIDocShell> docShell = aLoadingContext->OwnerDoc()->GetDocShell(); + if (loadContext && docShell && + docShell->GetBrowsingContext()->IsContent()) { + bool usePrivateBrowsing; + nsresult rv = loadContext->GetUsePrivateBrowsing(&usePrivateBrowsing); + if (NS_SUCCEEDED(rv)) { + mOriginAttributes.SyncAttributesWithPrivateBrowsing(usePrivateBrowsing); + } + } + + if (!loadContext) { + // Things like svg documents being used as images don't have a load + // context or a docshell, in that case try to inherit private browsing + // from the documents channel (which is how we determine which imgLoader + // is used). + nsCOMPtr<nsIChannel> channel = aLoadingContext->OwnerDoc()->GetChannel(); + if (channel) { + mOriginAttributes.SyncAttributesWithPrivateBrowsing( + NS_UsePrivateBrowsing(channel)); + } + } + + // For chrome docshell, the mPrivateBrowsingId remains 0 even its + // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in + // origin attributes if the type of the docshell is content. + MOZ_ASSERT(!docShell || !docShell->GetBrowsingContext()->IsChrome() || + mOriginAttributes.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } +} + +/* Constructor takes an outer window, but no loadingNode or loadingPrincipal. + * This constructor should only be used for TYPE_DOCUMENT loads, since they + * have a null loadingNode and loadingPrincipal. + */ +LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, + nsISupports* aContextForTopLevelLoad, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) + : mTriggeringPrincipal(aTriggeringPrincipal), + mTriggeringRemoteType(CurrentRemoteType()), + mSandboxedNullPrincipalID(nsID::GenerateUUID()), + mContextForTopLevelLoad(do_GetWeakReference(aContextForTopLevelLoad)), + mSecurityFlags(aSecurityFlags), + mSandboxFlags(aSandboxFlags), + mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT) { + // Top-level loads are never third-party + // Grab the information we can out of the window. + MOZ_ASSERT(aOuterWindow); + MOZ_ASSERT(mTriggeringPrincipal); + + // if the load is sandboxed, we can not also inherit the principal + if (mSandboxFlags & SANDBOXED_ORIGIN) { + mForceInheritPrincipalDropped = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + RefPtr<BrowsingContext> bc = aOuterWindow->GetBrowsingContext(); + mBrowsingContextID = bc ? bc->Id() : 0; + + // This should be removed in bug 1618557 + nsGlobalWindowInner* innerWindow = + nsGlobalWindowInner::Cast(aOuterWindow->GetCurrentInnerWindow()); + if (innerWindow) { + mTopLevelPrincipal = innerWindow->GetTopLevelAntiTrackingPrincipal(); + } + + // get the docshell from the outerwindow, and then get the originattributes + nsCOMPtr<nsIDocShell> docShell = aOuterWindow->GetDocShell(); + MOZ_ASSERT(docShell); + mOriginAttributes = nsDocShell::Cast(docShell)->GetOriginAttributes(); + + // We sometimes use this constructor for security checks for outer windows + // that aren't top level. + if (aSecurityFlags != nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK) { + MOZ_ASSERT(aOuterWindow->GetBrowsingContext()->IsTop()); + } + +#ifdef DEBUG + if (docShell->GetBrowsingContext()->IsChrome()) { + MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } +#endif + + // Let's take the current cookie behavior and current cookie permission + // for the documents' loadInfo. Note that for any other loadInfos, + // cookieBehavior will be BEHAVIOR_REJECT for security reasons. + bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + bool shouldResistFingerprinting = + nsContentUtils::ShouldResistFingerprinting_dangerous( + aURI, mOriginAttributes, + "We are creating CookieJarSettings, so we can't have one already.", + RFPTarget::IsAlwaysEnabledForPrecompute); + mCookieJarSettings = CookieJarSettings::Create( + isPrivate ? CookieJarSettings::ePrivate : CookieJarSettings::eRegular, + shouldResistFingerprinting); +} + +LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext, + nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + const OriginAttributes& aOriginAttributes, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) + : mTriggeringPrincipal(aTriggeringPrincipal), + mTriggeringRemoteType(aTriggeringRemoteType), + mSandboxedNullPrincipalID(nsID::GenerateUUID()), + mSecurityFlags(aSecurityFlags), + mSandboxFlags(aSandboxFlags), + mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT) { + // Top-level loads are never third-party + // Grab the information we can out of the window. + MOZ_ASSERT(aBrowsingContext); + MOZ_ASSERT(mTriggeringPrincipal); + MOZ_ASSERT(aSecurityFlags != + nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK); + + // if the load is sandboxed, we can not also inherit the principal + if (mSandboxFlags & SANDBOXED_ORIGIN) { + mForceInheritPrincipalDropped = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + mBrowsingContextID = aBrowsingContext->Id(); + mOriginAttributes = aOriginAttributes; + +#ifdef DEBUG + if (aBrowsingContext->IsChrome()) { + MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } +#endif + + // If we think we should not resist fingerprinting, defer to the opener's + // RFP bit (if there is an opener.) If the opener is also exempted, it stays + // true, otherwise we will put a false into the CJS and that will be respected + // on this document. + bool shouldResistFingerprinting = + nsContentUtils::ShouldResistFingerprinting_dangerous( + aURI, mOriginAttributes, + "We are creating CookieJarSettings, so we can't have one already.", + RFPTarget::IsAlwaysEnabledForPrecompute); + RefPtr<BrowsingContext> opener = aBrowsingContext->GetOpener(); + if (!shouldResistFingerprinting && opener && + opener->GetCurrentWindowContext()) { + shouldResistFingerprinting = + opener->GetCurrentWindowContext()->ShouldResistFingerprinting(); + } + + const bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + + // Let's take the current cookie behavior and current cookie permission + // for the documents' loadInfo. Note that for any other loadInfos, + // cookieBehavior will be BEHAVIOR_REJECT for security reasons. + mCookieJarSettings = CookieJarSettings::Create( + isPrivate ? CookieJarSettings::ePrivate : CookieJarSettings::eRegular, + shouldResistFingerprinting); +} + +LoadInfo::LoadInfo(dom::WindowGlobalParent* aParentWGP, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + nsContentPolicyType aContentPolicyType, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) + : mTriggeringPrincipal(aTriggeringPrincipal), + mTriggeringRemoteType(aTriggeringRemoteType), + mSandboxedNullPrincipalID(nsID::GenerateUUID()), + mSecurityFlags(aSecurityFlags), + mSandboxFlags(aSandboxFlags), + mInternalContentPolicyType(aContentPolicyType) { + CanonicalBrowsingContext* parentBC = aParentWGP->BrowsingContext(); + MOZ_ASSERT(parentBC); + ComputeAncestors(parentBC, mAncestorPrincipals, mAncestorBrowsingContextIDs); + + RefPtr<WindowGlobalParent> topLevelWGP = aParentWGP->TopWindowContext(); + + // if the load is sandboxed, we can not also inherit the principal + if (mSandboxFlags & SANDBOXED_ORIGIN) { + mForceInheritPrincipalDropped = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + // Ensure that all network requests for a window client have the ClientInfo + // properly set. + mClientInfo = aParentWGP->GetClientInfo(); + mLoadingPrincipal = aParentWGP->DocumentPrincipal(); + ComputeIsThirdPartyContext(aParentWGP); + + mBrowsingContextID = parentBC->Id(); + + // Let's inherit the cookie behavior and permission from the embedder + // document. + mCookieJarSettings = aParentWGP->CookieJarSettings(); + if (topLevelWGP->BrowsingContext()->IsTop()) { + if (mCookieJarSettings) { + bool stopAtOurLevel = mCookieJarSettings->GetCookieBehavior() == + nsICookieService::BEHAVIOR_REJECT_TRACKER; + if (!stopAtOurLevel || + topLevelWGP->OuterWindowId() != aParentWGP->OuterWindowId()) { + mTopLevelPrincipal = topLevelWGP->DocumentPrincipal(); + } + } + } + + if (!mTopLevelPrincipal && parentBC->IsTop()) { + // If this is the first level iframe, embedder WindowGlobalParent's document + // principal is our top-level principal. + mTopLevelPrincipal = aParentWGP->DocumentPrincipal(); + } + + mInnerWindowID = aParentWGP->InnerWindowId(); + mDocumentHasUserInteracted = aParentWGP->DocumentHasUserInteracted(); + + // if the document forces all mixed content to be blocked, then we + // store that bit for all requests on the loadinfo. + mBlockAllMixedContent = aParentWGP->GetDocumentBlockAllMixedContent(); + + if (mTopLevelPrincipal && BasePrincipal::Cast(mTriggeringPrincipal) + ->OverridesCSP(mTopLevelPrincipal)) { + // if the load is triggered by an addon which potentially overrides the + // CSP of the document, then do not force insecure requests to be + // upgraded. + mUpgradeInsecureRequests = false; + } else { + // if the document forces all requests to be upgraded from http to https, + // then we should do that for all requests. If it only forces preloads to + // be upgraded then we should enforce upgrade insecure requests only for + // preloads. + mUpgradeInsecureRequests = aParentWGP->GetDocumentUpgradeInsecureRequests(); + } + mOriginAttributes = mLoadingPrincipal->OriginAttributesRef(); + + // We need to do this after inheriting the document's origin attributes + // above, in case the loading principal ends up being the system principal. + if (parentBC->IsContent()) { + mOriginAttributes.SyncAttributesWithPrivateBrowsing( + parentBC->UsePrivateBrowsing()); + } + + mHttpsOnlyStatus |= aParentWGP->HttpsOnlyStatus(); + + // For chrome BC, the mPrivateBrowsingId remains 0 even its + // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in + // origin attributes if the type of the BC is content. + if (parentBC->IsChrome()) { + MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } + + RefPtr<WindowContext> ctx = WindowContext::GetById(mInnerWindowID); + if (ctx) { + mLoadingEmbedderPolicy = ctx->GetEmbedderPolicy(); + + if (Document* document = ctx->GetDocument()) { + mIsOriginTrialCoepCredentiallessEnabledForTopLevel = + document->Trials().IsEnabled(OriginTrial::CoepCredentialless); + } + } +} + +// Used for TYPE_FRAME or TYPE_IFRAME load. +LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) + : LoadInfo(aBrowsingContext->GetParentWindowContext(), aTriggeringPrincipal, + aTriggeringRemoteType, + InternalContentPolicyTypeForFrame(aBrowsingContext), + aSecurityFlags, aSandboxFlags) { + mFrameBrowsingContextID = aBrowsingContext->Id(); +} + +LoadInfo::LoadInfo(const LoadInfo& rhs) + : mLoadingPrincipal(rhs.mLoadingPrincipal), + mTriggeringPrincipal(rhs.mTriggeringPrincipal), + mPrincipalToInherit(rhs.mPrincipalToInherit), + mTopLevelPrincipal(rhs.mTopLevelPrincipal), + mResultPrincipalURI(rhs.mResultPrincipalURI), + mChannelCreationOriginalURI(rhs.mChannelCreationOriginalURI), + mCookieJarSettings(rhs.mCookieJarSettings), + mCspToInherit(rhs.mCspToInherit), + mTriggeringRemoteType(rhs.mTriggeringRemoteType), + mSandboxedNullPrincipalID(rhs.mSandboxedNullPrincipalID), + mClientInfo(rhs.mClientInfo), + // mReservedClientSource must be handled specially during redirect + // mReservedClientInfo must be handled specially during redirect + // mInitialClientInfo must be handled specially during redirect + mController(rhs.mController), + mPerformanceStorage(rhs.mPerformanceStorage), + mLoadingContext(rhs.mLoadingContext), + mContextForTopLevelLoad(rhs.mContextForTopLevelLoad), + mSecurityFlags(rhs.mSecurityFlags), + mSandboxFlags(rhs.mSandboxFlags), + mTriggeringSandboxFlags(rhs.mTriggeringSandboxFlags), + mTriggeringWindowId(rhs.mTriggeringWindowId), + mTriggeringStorageAccess(rhs.mTriggeringStorageAccess), + mInternalContentPolicyType(rhs.mInternalContentPolicyType), + mTainting(rhs.mTainting), + mBlockAllMixedContent(rhs.mBlockAllMixedContent), + mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests), + mBrowserUpgradeInsecureRequests(rhs.mBrowserUpgradeInsecureRequests), + mBrowserDidUpgradeInsecureRequests( + rhs.mBrowserDidUpgradeInsecureRequests), + mBrowserWouldUpgradeInsecureRequests( + rhs.mBrowserWouldUpgradeInsecureRequests), + mForceAllowDataURI(rhs.mForceAllowDataURI), + mAllowInsecureRedirectToDataURI(rhs.mAllowInsecureRedirectToDataURI), + mSkipContentPolicyCheckForWebRequest( + rhs.mSkipContentPolicyCheckForWebRequest), + mOriginalFrameSrcLoad(rhs.mOriginalFrameSrcLoad), + mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped), + mInnerWindowID(rhs.mInnerWindowID), + mBrowsingContextID(rhs.mBrowsingContextID), + mWorkerAssociatedBrowsingContextID( + rhs.mWorkerAssociatedBrowsingContextID), + mFrameBrowsingContextID(rhs.mFrameBrowsingContextID), + mInitialSecurityCheckDone(rhs.mInitialSecurityCheckDone), + mIsThirdPartyContext(rhs.mIsThirdPartyContext), + mIsThirdPartyContextToTopWindow(rhs.mIsThirdPartyContextToTopWindow), + mIsFormSubmission(rhs.mIsFormSubmission), + mSendCSPViolationEvents(rhs.mSendCSPViolationEvents), + mOriginAttributes(rhs.mOriginAttributes), + mRedirectChainIncludingInternalRedirects( + rhs.mRedirectChainIncludingInternalRedirects.Clone()), + mRedirectChain(rhs.mRedirectChain.Clone()), + mAncestorPrincipals(rhs.mAncestorPrincipals.Clone()), + mAncestorBrowsingContextIDs(rhs.mAncestorBrowsingContextIDs.Clone()), + mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders.Clone()), + mRequestBlockingReason(rhs.mRequestBlockingReason), + mForcePreflight(rhs.mForcePreflight), + mIsPreflight(rhs.mIsPreflight), + mLoadTriggeredFromExternal(rhs.mLoadTriggeredFromExternal), + mDocumentHasUserInteracted(rhs.mDocumentHasUserInteracted), + mAllowListFutureDocumentsCreatedFromThisRedirectChain( + rhs.mAllowListFutureDocumentsCreatedFromThisRedirectChain), + mNeedForCheckingAntiTrackingHeuristic( + rhs.mNeedForCheckingAntiTrackingHeuristic), + mCspNonce(rhs.mCspNonce), + mIntegrityMetadata(rhs.mIntegrityMetadata), + mSkipContentSniffing(rhs.mSkipContentSniffing), + mHttpsOnlyStatus(rhs.mHttpsOnlyStatus), + mHstsStatus(rhs.mHstsStatus), + mHasValidUserGestureActivation(rhs.mHasValidUserGestureActivation), + mAllowDeprecatedSystemRequests(rhs.mAllowDeprecatedSystemRequests), + mIsInDevToolsContext(rhs.mIsInDevToolsContext), + mParserCreatedScript(rhs.mParserCreatedScript), + mStoragePermission(rhs.mStoragePermission), + mOverriddenFingerprintingSettings(rhs.mOverriddenFingerprintingSettings), +#ifdef DEBUG + mOverriddenFingerprintingSettingsIsSet( + rhs.mOverriddenFingerprintingSettingsIsSet), +#endif + mIsMetaRefresh(rhs.mIsMetaRefresh), + mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes), + mIsMediaRequest(rhs.mIsMediaRequest), + mIsMediaInitialRequest(rhs.mIsMediaInitialRequest), + mIsFromObjectOrEmbed(rhs.mIsFromObjectOrEmbed), + mLoadingEmbedderPolicy(rhs.mLoadingEmbedderPolicy), + mIsOriginTrialCoepCredentiallessEnabledForTopLevel( + rhs.mIsOriginTrialCoepCredentiallessEnabledForTopLevel), + mUnstrippedURI(rhs.mUnstrippedURI), + mInterceptionInfo(rhs.mInterceptionInfo), + mHasInjectedCookieForCookieBannerHandling( + rhs.mHasInjectedCookieForCookieBannerHandling), + mWasSchemelessInput(rhs.mWasSchemelessInput) { +} + +LoadInfo::LoadInfo( + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aTopLevelPrincipal, + nsIURI* aResultPrincipalURI, nsICookieJarSettings* aCookieJarSettings, + nsIContentSecurityPolicy* aCspToInherit, + const nsACString& aTriggeringRemoteType, + const nsID& aSandboxedNullPrincipalID, const Maybe<ClientInfo>& aClientInfo, + const Maybe<ClientInfo>& aReservedClientInfo, + const Maybe<ClientInfo>& aInitialClientInfo, + const Maybe<ServiceWorkerDescriptor>& aController, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags, + uint32_t aTriggeringSandboxFlags, uint64_t aTriggeringWindowId, + bool aTriggeringStorageAccess, nsContentPolicyType aContentPolicyType, + LoadTainting aTainting, bool aBlockAllMixedContent, + bool aUpgradeInsecureRequests, bool aBrowserUpgradeInsecureRequests, + bool aBrowserDidUpgradeInsecureRequests, + bool aBrowserWouldUpgradeInsecureRequests, bool aForceAllowDataURI, + bool aAllowInsecureRedirectToDataURI, + bool aSkipContentPolicyCheckForWebRequest, bool aOriginalFrameSrcLoad, + bool aForceInheritPrincipalDropped, uint64_t aInnerWindowID, + uint64_t aBrowsingContextID, uint64_t aFrameBrowsingContextID, + bool aInitialSecurityCheckDone, bool aIsThirdPartyContext, + const Maybe<bool>& aIsThirdPartyContextToTopWindow, bool aIsFormSubmission, + bool aSendCSPViolationEvents, const OriginAttributes& aOriginAttributes, + RedirectHistoryArray&& aRedirectChainIncludingInternalRedirects, + RedirectHistoryArray&& aRedirectChain, + nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals, + const nsTArray<uint64_t>& aAncestorBrowsingContextIDs, + const nsTArray<nsCString>& aCorsUnsafeHeaders, bool aForcePreflight, + bool aIsPreflight, bool aLoadTriggeredFromExternal, + bool aServiceWorkerTaintingSynthesized, bool aDocumentHasUserInteracted, + bool aAllowListFutureDocumentsCreatedFromThisRedirectChain, + bool aNeedForCheckingAntiTrackingHeuristic, const nsAString& aCspNonce, + const nsAString& aIntegrityMetadata, bool aSkipContentSniffing, + uint32_t aHttpsOnlyStatus, bool aHstsStatus, + bool aHasValidUserGestureActivation, bool aAllowDeprecatedSystemRequests, + bool aIsInDevToolsContext, bool aParserCreatedScript, + nsILoadInfo::StoragePermissionState aStoragePermission, + const Maybe<RFPTarget>& aOverriddenFingerprintingSettings, + bool aIsMetaRefresh, uint32_t aRequestBlockingReason, + nsINode* aLoadingContext, + nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy, + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel, + nsIURI* aUnstrippedURI, nsIInterceptionInfo* aInterceptionInfo, + bool aHasInjectedCookieForCookieBannerHandling, bool aWasSchemelessInput) + : mLoadingPrincipal(aLoadingPrincipal), + mTriggeringPrincipal(aTriggeringPrincipal), + mPrincipalToInherit(aPrincipalToInherit), + mTopLevelPrincipal(aTopLevelPrincipal), + mResultPrincipalURI(aResultPrincipalURI), + mCookieJarSettings(aCookieJarSettings), + mCspToInherit(aCspToInherit), + mTriggeringRemoteType(aTriggeringRemoteType), + mSandboxedNullPrincipalID(aSandboxedNullPrincipalID), + mClientInfo(aClientInfo), + mReservedClientInfo(aReservedClientInfo), + mInitialClientInfo(aInitialClientInfo), + mController(aController), + mLoadingContext(do_GetWeakReference(aLoadingContext)), + mSecurityFlags(aSecurityFlags), + mSandboxFlags(aSandboxFlags), + mTriggeringSandboxFlags(aTriggeringSandboxFlags), + mTriggeringWindowId(aTriggeringWindowId), + mTriggeringStorageAccess(aTriggeringStorageAccess), + mInternalContentPolicyType(aContentPolicyType), + mTainting(aTainting), + mBlockAllMixedContent(aBlockAllMixedContent), + mUpgradeInsecureRequests(aUpgradeInsecureRequests), + mBrowserUpgradeInsecureRequests(aBrowserUpgradeInsecureRequests), + mBrowserDidUpgradeInsecureRequests(aBrowserDidUpgradeInsecureRequests), + mBrowserWouldUpgradeInsecureRequests( + aBrowserWouldUpgradeInsecureRequests), + mForceAllowDataURI(aForceAllowDataURI), + mAllowInsecureRedirectToDataURI(aAllowInsecureRedirectToDataURI), + mSkipContentPolicyCheckForWebRequest( + aSkipContentPolicyCheckForWebRequest), + mOriginalFrameSrcLoad(aOriginalFrameSrcLoad), + mForceInheritPrincipalDropped(aForceInheritPrincipalDropped), + mInnerWindowID(aInnerWindowID), + mBrowsingContextID(aBrowsingContextID), + mFrameBrowsingContextID(aFrameBrowsingContextID), + mInitialSecurityCheckDone(aInitialSecurityCheckDone), + mIsThirdPartyContext(aIsThirdPartyContext), + mIsThirdPartyContextToTopWindow(aIsThirdPartyContextToTopWindow), + mIsFormSubmission(aIsFormSubmission), + mSendCSPViolationEvents(aSendCSPViolationEvents), + mOriginAttributes(aOriginAttributes), + mRedirectChainIncludingInternalRedirects( + std::move(aRedirectChainIncludingInternalRedirects)), + mRedirectChain(std::move(aRedirectChain)), + mAncestorPrincipals(std::move(aAncestorPrincipals)), + mAncestorBrowsingContextIDs(aAncestorBrowsingContextIDs.Clone()), + mCorsUnsafeHeaders(aCorsUnsafeHeaders.Clone()), + mRequestBlockingReason(aRequestBlockingReason), + mForcePreflight(aForcePreflight), + mIsPreflight(aIsPreflight), + mLoadTriggeredFromExternal(aLoadTriggeredFromExternal), + mServiceWorkerTaintingSynthesized(aServiceWorkerTaintingSynthesized), + mDocumentHasUserInteracted(aDocumentHasUserInteracted), + mAllowListFutureDocumentsCreatedFromThisRedirectChain( + aAllowListFutureDocumentsCreatedFromThisRedirectChain), + mNeedForCheckingAntiTrackingHeuristic( + aNeedForCheckingAntiTrackingHeuristic), + mCspNonce(aCspNonce), + mIntegrityMetadata(aIntegrityMetadata), + mSkipContentSniffing(aSkipContentSniffing), + mHttpsOnlyStatus(aHttpsOnlyStatus), + mHstsStatus(aHstsStatus), + mHasValidUserGestureActivation(aHasValidUserGestureActivation), + mAllowDeprecatedSystemRequests(aAllowDeprecatedSystemRequests), + mIsInDevToolsContext(aIsInDevToolsContext), + mParserCreatedScript(aParserCreatedScript), + mStoragePermission(aStoragePermission), + mOverriddenFingerprintingSettings(aOverriddenFingerprintingSettings), + mIsMetaRefresh(aIsMetaRefresh), + mLoadingEmbedderPolicy(aLoadingEmbedderPolicy), + mIsOriginTrialCoepCredentiallessEnabledForTopLevel( + aIsOriginTrialCoepCredentiallessEnabledForTopLevel), + mUnstrippedURI(aUnstrippedURI), + mInterceptionInfo(aInterceptionInfo), + mHasInjectedCookieForCookieBannerHandling( + aHasInjectedCookieForCookieBannerHandling), + mWasSchemelessInput(aWasSchemelessInput) { + // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal + MOZ_ASSERT(mLoadingPrincipal || + aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT); + MOZ_ASSERT(mTriggeringPrincipal); +} + +// static +void LoadInfo::ComputeAncestors( + CanonicalBrowsingContext* aBC, + nsTArray<nsCOMPtr<nsIPrincipal>>& aAncestorPrincipals, + nsTArray<uint64_t>& aBrowsingContextIDs) { + MOZ_ASSERT(aAncestorPrincipals.IsEmpty()); + MOZ_ASSERT(aBrowsingContextIDs.IsEmpty()); + CanonicalBrowsingContext* ancestorBC = aBC; + // Iterate over ancestor WindowGlobalParents, collecting principals and outer + // window IDs. + while (WindowGlobalParent* ancestorWGP = + ancestorBC->GetParentWindowContext()) { + ancestorBC = ancestorWGP->BrowsingContext(); + + nsCOMPtr<nsIPrincipal> parentPrincipal = ancestorWGP->DocumentPrincipal(); + MOZ_ASSERT(parentPrincipal, "Ancestor principal is null"); + aAncestorPrincipals.AppendElement(parentPrincipal.forget()); + aBrowsingContextIDs.AppendElement(ancestorBC->Id()); + } +} +void LoadInfo::ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow) { + ExtContentPolicyType type = + nsContentUtils::InternalContentPolicyTypeToExternal( + mInternalContentPolicyType); + if (type == ExtContentPolicy::TYPE_DOCUMENT) { + // Top-level loads are never third-party. + mIsThirdPartyContext = false; + return; + } + + nsCOMPtr<mozIThirdPartyUtil> util(do_GetService(THIRDPARTYUTIL_CONTRACTID)); + if (NS_WARN_IF(!util)) { + return; + } + + util->IsThirdPartyWindow(aOuterWindow, nullptr, &mIsThirdPartyContext); +} + +void LoadInfo::ComputeIsThirdPartyContext(dom::WindowGlobalParent* aGlobal) { + if (nsILoadInfo::GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_DOCUMENT) { + // Top-level loads are never third-party. + mIsThirdPartyContext = false; + return; + } + + ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); + if (!thirdPartyUtil) { + return; + } + thirdPartyUtil->IsThirdPartyGlobal(aGlobal, &mIsThirdPartyContext); +} + +NS_IMPL_ISUPPORTS(LoadInfo, nsILoadInfo) + +LoadInfo::~LoadInfo() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); } + +already_AddRefed<nsILoadInfo> LoadInfo::Clone() const { + RefPtr<LoadInfo> copy(new LoadInfo(*this)); + return copy.forget(); +} + +already_AddRefed<nsILoadInfo> LoadInfo::CloneWithNewSecFlags( + nsSecurityFlags aSecurityFlags) const { + RefPtr<LoadInfo> copy(new LoadInfo(*this)); + copy->mSecurityFlags = aSecurityFlags; + return copy.forget(); +} + +already_AddRefed<nsILoadInfo> LoadInfo::CloneForNewRequest() const { + RefPtr<LoadInfo> copy(new LoadInfo(*this)); + copy->mInitialSecurityCheckDone = false; + copy->mRedirectChainIncludingInternalRedirects.Clear(); + copy->mRedirectChain.Clear(); + copy->mResultPrincipalURI = nullptr; + return copy.forget(); +} + +NS_IMETHODIMP +LoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) { + *aLoadingPrincipal = do_AddRef(mLoadingPrincipal).take(); + return NS_OK; +} + +nsIPrincipal* LoadInfo::VirtualGetLoadingPrincipal() { + return mLoadingPrincipal; +} + +NS_IMETHODIMP +LoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) { + *aTriggeringPrincipal = do_AddRef(mTriggeringPrincipal).take(); + return NS_OK; +} + +nsIPrincipal* LoadInfo::TriggeringPrincipal() { return mTriggeringPrincipal; } + +NS_IMETHODIMP +LoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) { + *aPrincipalToInherit = do_AddRef(mPrincipalToInherit).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) { + MOZ_ASSERT(aPrincipalToInherit, "must be a valid principal to inherit"); + mPrincipalToInherit = aPrincipalToInherit; + return NS_OK; +} + +nsIPrincipal* LoadInfo::PrincipalToInherit() { return mPrincipalToInherit; } + +nsIPrincipal* LoadInfo::FindPrincipalToInherit(nsIChannel* aChannel) { + if (mPrincipalToInherit) { + return mPrincipalToInherit; + } + + nsCOMPtr<nsIURI> uri = mResultPrincipalURI; + if (!uri) { + Unused << aChannel->GetOriginalURI(getter_AddRefs(uri)); + } + + auto* prin = BasePrincipal::Cast(mTriggeringPrincipal); + return prin->PrincipalToInherit(uri); +} + +const nsID& LoadInfo::GetSandboxedNullPrincipalID() { + MOZ_ASSERT(!mSandboxedNullPrincipalID.Equals(nsID{}), + "mSandboxedNullPrincipalID wasn't initialized?"); + return mSandboxedNullPrincipalID; +} + +void LoadInfo::ResetSandboxedNullPrincipalID() { + mSandboxedNullPrincipalID = nsID::GenerateUUID(); +} + +nsIPrincipal* LoadInfo::GetTopLevelPrincipal() { return mTopLevelPrincipal; } + +NS_IMETHODIMP +LoadInfo::GetTriggeringRemoteType(nsACString& aTriggeringRemoteType) { + aTriggeringRemoteType = mTriggeringRemoteType; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetTriggeringRemoteType(const nsACString& aTriggeringRemoteType) { + mTriggeringRemoteType = aTriggeringRemoteType; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadingDocument(Document** aResult) { + if (nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext)) { + RefPtr<Document> context = node->OwnerDoc(); + context.forget(aResult); + } + return NS_OK; +} + +nsINode* LoadInfo::LoadingNode() { + nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext); + return node; +} + +already_AddRefed<nsISupports> LoadInfo::ContextForTopLevelLoad() { + // Most likely you want to query LoadingNode() instead of + // ContextForTopLevelLoad() if this assertion fires. + MOZ_ASSERT(mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "should only query this context for top level document loads"); + nsCOMPtr<nsISupports> context = do_QueryReferent(mContextForTopLevelLoad); + return context.forget(); +} + +already_AddRefed<nsISupports> LoadInfo::GetLoadingContext() { + nsCOMPtr<nsISupports> context; + if (mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) { + context = ContextForTopLevelLoad(); + } else { + context = LoadingNode(); + } + return context.forget(); +} + +NS_IMETHODIMP +LoadInfo::GetLoadingContextXPCOM(nsISupports** aResult) { + nsCOMPtr<nsISupports> context = GetLoadingContext(); + context.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSecurityFlags(nsSecurityFlags* aResult) { + *aResult = mSecurityFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSandboxFlags(uint32_t* aResult) { + *aResult = mSandboxFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetTriggeringSandboxFlags(uint32_t* aResult) { + *aResult = mTriggeringSandboxFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetTriggeringSandboxFlags(uint32_t aFlags) { + mTriggeringSandboxFlags = aFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetTriggeringWindowId(uint64_t* aResult) { + *aResult = mTriggeringWindowId; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetTriggeringWindowId(uint64_t aFlags) { + mTriggeringWindowId = aFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetTriggeringStorageAccess(bool* aResult) { + *aResult = mTriggeringStorageAccess; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetTriggeringStorageAccess(bool aFlags) { + mTriggeringStorageAccess = aFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSecurityMode(uint32_t* aFlags) { + *aFlags = (mSecurityFlags & + (nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT | + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED | + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT | + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL | + nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT)); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) { + *aIsInThirdPartyContext = mIsThirdPartyContext; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsInThirdPartyContext(bool aIsInThirdPartyContext) { + mIsThirdPartyContext = aIsInThirdPartyContext; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsThirdPartyContextToTopWindow( + bool* aIsThirdPartyContextToTopWindow) { + *aIsThirdPartyContextToTopWindow = + mIsThirdPartyContextToTopWindow.valueOr(true); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsThirdPartyContextToTopWindow( + bool aIsThirdPartyContextToTopWindow) { + mIsThirdPartyContextToTopWindow = Some(aIsThirdPartyContextToTopWindow); + return NS_OK; +} + +static const uint32_t sCookiePolicyMask = + nsILoadInfo::SEC_COOKIES_DEFAULT | nsILoadInfo::SEC_COOKIES_INCLUDE | + nsILoadInfo::SEC_COOKIES_SAME_ORIGIN | nsILoadInfo::SEC_COOKIES_OMIT; + +NS_IMETHODIMP +LoadInfo::GetCookiePolicy(uint32_t* aResult) { + uint32_t policy = mSecurityFlags & sCookiePolicyMask; + if (policy == nsILoadInfo::SEC_COOKIES_DEFAULT) { + policy = (mSecurityFlags & SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT) + ? nsILoadInfo::SEC_COOKIES_SAME_ORIGIN + : nsILoadInfo::SEC_COOKIES_INCLUDE; + } + + *aResult = policy; + return NS_OK; +} + +namespace { + +already_AddRefed<nsICookieJarSettings> CreateCookieJarSettings( + nsContentPolicyType aContentPolicyType, bool aIsPrivate, + bool shouldResistFingerprinting) { + if (StaticPrefs::network_cookieJarSettings_unblocked_for_testing()) { + return aIsPrivate ? CookieJarSettings::Create(CookieJarSettings::ePrivate, + shouldResistFingerprinting) + : CookieJarSettings::Create(CookieJarSettings::eRegular, + shouldResistFingerprinting); + } + + // These contentPolictTypes require a real CookieJarSettings because favicon + // and save-as requests must send cookies. Anything else should not + // send/receive cookies. + if (aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON || + aContentPolicyType == nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD) { + return aIsPrivate ? CookieJarSettings::Create(CookieJarSettings::ePrivate, + shouldResistFingerprinting) + : CookieJarSettings::Create(CookieJarSettings::eRegular, + shouldResistFingerprinting); + } + + return CookieJarSettings::GetBlockingAll(shouldResistFingerprinting); +} + +} // namespace + +NS_IMETHODIMP +LoadInfo::GetCookieJarSettings(nsICookieJarSettings** aCookieJarSettings) { + if (!mCookieJarSettings) { + bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + nsCOMPtr<nsIPrincipal> loadingPrincipal; + Unused << this->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal)); + bool shouldResistFingerprinting = + nsContentUtils::ShouldResistFingerprinting_dangerous( + loadingPrincipal, + "CookieJarSettings can't exist yet, we're creating it", + RFPTarget::IsAlwaysEnabledForPrecompute); + mCookieJarSettings = CreateCookieJarSettings( + mInternalContentPolicyType, isPrivate, shouldResistFingerprinting); + } + + nsCOMPtr<nsICookieJarSettings> cookieJarSettings = mCookieJarSettings; + cookieJarSettings.forget(aCookieJarSettings); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetCookieJarSettings(nsICookieJarSettings* aCookieJarSettings) { + MOZ_ASSERT(aCookieJarSettings); + // We allow the overwrite of CookieJarSettings. + mCookieJarSettings = aCookieJarSettings; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetStoragePermission( + nsILoadInfo::StoragePermissionState* aStoragePermission) { + *aStoragePermission = mStoragePermission; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetStoragePermission( + nsILoadInfo::StoragePermissionState aStoragePermission) { + mStoragePermission = aStoragePermission; + return NS_OK; +} + +const Maybe<RFPTarget>& LoadInfo::GetOverriddenFingerprintingSettings() { +#ifdef DEBUG + RefPtr<BrowsingContext> browsingContext; + GetTargetBrowsingContext(getter_AddRefs(browsingContext)); + + // Exclude this check if the target browsing context is for the parent + // process. + MOZ_ASSERT_IF(XRE_IsParentProcess() && browsingContext && + !browsingContext->IsInProcess(), + mOverriddenFingerprintingSettingsIsSet); +#endif + return mOverriddenFingerprintingSettings; +} + +void LoadInfo::SetOverriddenFingerprintingSettings(RFPTarget aTargets) { + mOverriddenFingerprintingSettings.reset(); + mOverriddenFingerprintingSettings.emplace(aTargets); +} + +NS_IMETHODIMP +LoadInfo::GetIsMetaRefresh(bool* aIsMetaRefresh) { + *aIsMetaRefresh = mIsMetaRefresh; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsMetaRefresh(bool aIsMetaRefresh) { + mIsMetaRefresh = aIsMetaRefresh; + return NS_OK; +} + +void LoadInfo::SetIncludeCookiesSecFlag() { + MOZ_ASSERT((mSecurityFlags & sCookiePolicyMask) == + nsILoadInfo::SEC_COOKIES_DEFAULT); + mSecurityFlags = + (mSecurityFlags & ~sCookiePolicyMask) | nsILoadInfo::SEC_COOKIES_INCLUDE; +} + +NS_IMETHODIMP +LoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal) { + *aInheritPrincipal = + (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal) { + *aInheritPrincipal = + (mSecurityFlags & + nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed) { + *aLoadingSandboxed = (mSandboxFlags & SANDBOXED_ORIGIN); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAboutBlankInherits(bool* aResult) { + *aResult = (mSecurityFlags & nsILoadInfo::SEC_ABOUT_BLANK_INHERITS); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAllowChrome(bool* aResult) { + *aResult = (mSecurityFlags & nsILoadInfo::SEC_ALLOW_CHROME); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetDisallowScript(bool* aResult) { + *aResult = (mSecurityFlags & nsILoadInfo::SEC_DISALLOW_SCRIPT); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetDontFollowRedirects(bool* aResult) { + *aResult = (mSecurityFlags & nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadErrorPage(bool* aResult) { + *aResult = (mSecurityFlags & nsILoadInfo::SEC_LOAD_ERROR_PAGE); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsFormSubmission(bool* aResult) { + *aResult = mIsFormSubmission; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsFormSubmission(bool aValue) { + mIsFormSubmission = aValue; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSendCSPViolationEvents(bool* aResult) { + *aResult = mSendCSPViolationEvents; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetSendCSPViolationEvents(bool aValue) { + mSendCSPViolationEvents = aValue; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult) { + // We have to use nsContentPolicyType because ExtContentPolicyType is not + // visible from xpidl. + *aResult = static_cast<nsContentPolicyType>( + nsContentUtils::InternalContentPolicyTypeToExternal( + mInternalContentPolicyType)); + return NS_OK; +} + +nsContentPolicyType LoadInfo::InternalContentPolicyType() { + return mInternalContentPolicyType; +} + +NS_IMETHODIMP +LoadInfo::GetBlockAllMixedContent(bool* aResult) { + *aResult = mBlockAllMixedContent; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetUpgradeInsecureRequests(bool* aResult) { + *aResult = mUpgradeInsecureRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetBrowserUpgradeInsecureRequests(bool* aResult) { + *aResult = mBrowserUpgradeInsecureRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetBrowserDidUpgradeInsecureRequests(bool* aResult) { + *aResult = mBrowserDidUpgradeInsecureRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetBrowserWouldUpgradeInsecureRequests(bool* aResult) { + *aResult = mBrowserWouldUpgradeInsecureRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI) { + MOZ_ASSERT(!mForceAllowDataURI || + mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "can only allow data URI navigation for TYPE_DOCUMENT"); + mForceAllowDataURI = aForceAllowDataURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetForceAllowDataURI(bool* aForceAllowDataURI) { + *aForceAllowDataURI = mForceAllowDataURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetAllowInsecureRedirectToDataURI( + bool aAllowInsecureRedirectToDataURI) { + mAllowInsecureRedirectToDataURI = aAllowInsecureRedirectToDataURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAllowInsecureRedirectToDataURI( + bool* aAllowInsecureRedirectToDataURI) { + *aAllowInsecureRedirectToDataURI = mAllowInsecureRedirectToDataURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetSkipContentPolicyCheckForWebRequest(bool aSkip) { + mSkipContentPolicyCheckForWebRequest = aSkip; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSkipContentPolicyCheckForWebRequest(bool* aSkip) { + *aSkip = mSkipContentPolicyCheckForWebRequest; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetOriginalFrameSrcLoad(bool aOriginalFrameSrcLoad) { + mOriginalFrameSrcLoad = aOriginalFrameSrcLoad; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetOriginalFrameSrcLoad(bool* aOriginalFrameSrcLoad) { + *aOriginalFrameSrcLoad = mOriginalFrameSrcLoad; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetForceInheritPrincipalDropped(bool* aResult) { + *aResult = mForceInheritPrincipalDropped; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetInnerWindowID(uint64_t* aResult) { + *aResult = mInnerWindowID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetBrowsingContextID(uint64_t* aResult) { + *aResult = mBrowsingContextID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetWorkerAssociatedBrowsingContextID(uint64_t* aResult) { + *aResult = mWorkerAssociatedBrowsingContextID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetWorkerAssociatedBrowsingContextID(uint64_t aID) { + mWorkerAssociatedBrowsingContextID = aID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetFrameBrowsingContextID(uint64_t* aResult) { + *aResult = mFrameBrowsingContextID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetTargetBrowsingContextID(uint64_t* aResult) { + return (nsILoadInfo::GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_SUBDOCUMENT) + ? GetFrameBrowsingContextID(aResult) + : GetBrowsingContextID(aResult); +} + +NS_IMETHODIMP +LoadInfo::GetBrowsingContext(dom::BrowsingContext** aResult) { + *aResult = BrowsingContext::Get(mBrowsingContextID).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetWorkerAssociatedBrowsingContext(dom::BrowsingContext** aResult) { + *aResult = BrowsingContext::Get(mWorkerAssociatedBrowsingContextID).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetFrameBrowsingContext(dom::BrowsingContext** aResult) { + *aResult = BrowsingContext::Get(mFrameBrowsingContextID).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetTargetBrowsingContext(dom::BrowsingContext** aResult) { + uint64_t targetBrowsingContextID = 0; + MOZ_ALWAYS_SUCCEEDS(GetTargetBrowsingContextID(&targetBrowsingContextID)); + *aResult = BrowsingContext::Get(targetBrowsingContextID).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetScriptableOriginAttributes( + JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) { + if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::ResetPrincipalToInheritToNullPrincipal() { + // take the originAttributes from the LoadInfo and create + // a new NullPrincipal using those origin attributes. + nsCOMPtr<nsIPrincipal> newNullPrincipal = + NullPrincipal::Create(mOriginAttributes); + + mPrincipalToInherit = newNullPrincipal; + + // setting SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER will overrule + // any non null owner set on the channel and will return the principal + // form the loadinfo instead. + mSecurityFlags |= SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER; + + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetScriptableOriginAttributes( + JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) { + OriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + mOriginAttributes = attrs; + return NS_OK; +} + +nsresult LoadInfo::GetOriginAttributes( + mozilla::OriginAttributes* aOriginAttributes) { + NS_ENSURE_ARG(aOriginAttributes); + *aOriginAttributes = mOriginAttributes; + return NS_OK; +} + +nsresult LoadInfo::SetOriginAttributes( + const mozilla::OriginAttributes& aOriginAttributes) { + mOriginAttributes = aOriginAttributes; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone) { + // Indicates whether the channel was ever evaluated by the + // ContentSecurityManager. Once set to true, this flag must + // remain true throughout the lifetime of the channel. + // Setting it to anything else than true will be discarded. + MOZ_ASSERT(aInitialSecurityCheckDone, + "aInitialSecurityCheckDone must be true"); + mInitialSecurityCheckDone = + mInitialSecurityCheckDone || aInitialSecurityCheckDone; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetInitialSecurityCheckDone(bool* aResult) { + *aResult = mInitialSecurityCheckDone; + return NS_OK; +} + +// To prevent unintenional credential and information leaks in content +// processes we can use this function to truncate a Principal's URI as much as +// possible. +already_AddRefed<nsIPrincipal> CreateTruncatedPrincipal( + nsIPrincipal* aPrincipal) { + nsCOMPtr<nsIPrincipal> truncatedPrincipal; + // System Principal URIs don't need to be truncated as they don't contain any + // sensitive browsing history information. + if (aPrincipal->IsSystemPrincipal()) { + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); + } + + // Content Principal URIs are the main location of the information we need to + // truncate. + if (aPrincipal->GetIsContentPrincipal()) { + // Certain URIs (chrome, resource, about, jar) don't need to be truncated + // as they should be free of any sensitive user browsing history. + if (aPrincipal->SchemeIs("chrome") || aPrincipal->SchemeIs("resource") || + aPrincipal->SchemeIs("about") || aPrincipal->SchemeIs("jar")) { + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); + } + + // Different parts of the URI are preserved due to being vital to the + // browser's operation. + // Scheme for differentiating between different types of URIs and how to + // truncate them and later on utilize them. + // Host and Port to retain the redirect chain's core functionality. + // Path would ideally be removed but needs to be retained to ensure that + // http/https redirect loops can be detected. + // The entirety of the Query String, Reference Fragment, and User Info + // subcomponents must be stripped to avoid leaking Oauth tokens, user + // identifiers, and similar bits of information that these subcomponents may + // contain. + nsAutoCString scheme; + nsAutoCString separator("://"); + nsAutoCString hostPort; + nsAutoCString path; + nsAutoCString uriString(""); + if (aPrincipal->SchemeIs("view-source")) { + // The path portion of the view-source URI will be the URI whose source is + // being viewed, so we create a new URI object with a truncated form of + // the path and append the view-source scheme to the front again. + nsAutoCString viewSourcePath; + aPrincipal->GetFilePath(viewSourcePath); + + nsCOMPtr<nsIURI> nestedURI; + nsresult rv = NS_NewURI(getter_AddRefs(nestedURI), viewSourcePath); + + if (NS_FAILED(rv)) { + // Since the path here should be an already validated URI this should + // never happen. + NS_WARNING(viewSourcePath.get()); + MOZ_ASSERT(false, + "Failed to create truncated form of URI with NS_NewURI."); + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); + } + + nestedURI->GetScheme(scheme); + nestedURI->GetHostPort(hostPort); + nestedURI->GetFilePath(path); + uriString += "view-source:"; + } else { + aPrincipal->GetScheme(scheme); + aPrincipal->GetHostPort(hostPort); + aPrincipal->GetFilePath(path); + } + uriString += scheme + separator + hostPort + path; + + nsCOMPtr<nsIURI> truncatedURI; + nsresult rv = NS_NewURI(getter_AddRefs(truncatedURI), uriString); + if (NS_FAILED(rv)) { + NS_WARNING(uriString.get()); + MOZ_ASSERT(false, + "Failed to create truncated form of URI with NS_NewURI."); + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); + } + + return BasePrincipal::CreateContentPrincipal( + truncatedURI, aPrincipal->OriginAttributesRef()); + } + + // Null Principal Precursor URIs can also contain information that needs to + // be truncated. + if (aPrincipal->GetIsNullPrincipal()) { + nsCOMPtr<nsIPrincipal> precursorPrincipal = + aPrincipal->GetPrecursorPrincipal(); + // If there is no precursor then nothing needs to be truncated. + if (!precursorPrincipal) { + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); + } + + // Otherwise we return a new Null Principal with the original's Origin + // Attributes and a truncated version of the original's precursor URI. + nsCOMPtr<nsIPrincipal> truncatedPrecursor = + CreateTruncatedPrincipal(precursorPrincipal); + return NullPrincipal::CreateWithInheritedAttributes(truncatedPrecursor); + } + + // Expanded Principals shouldn't contain sensitive information but their + // allowlists might so we truncate that information here. + if (aPrincipal->GetIsExpandedPrincipal()) { + nsTArray<nsCOMPtr<nsIPrincipal>> truncatedAllowList; + + for (const auto& allowedPrincipal : BasePrincipal::Cast(aPrincipal) + ->As<ExpandedPrincipal>() + ->AllowList()) { + nsCOMPtr<nsIPrincipal> truncatedPrincipal = + CreateTruncatedPrincipal(allowedPrincipal); + + truncatedAllowList.AppendElement(truncatedPrincipal); + } + + return ExpandedPrincipal::Create(truncatedAllowList, + aPrincipal->OriginAttributesRef()); + } + + // If we hit this assertion we need to update this function to add the + // Principals and URIs seen as new corner cases to handle. + MOZ_ASSERT(false, "Unhandled Principal or URI type encountered."); + + truncatedPrincipal = aPrincipal; + return truncatedPrincipal.forget(); +} + +NS_IMETHODIMP +LoadInfo::AppendRedirectHistoryEntry(nsIChannel* aChannel, + bool aIsInternalRedirect) { + NS_ENSURE_ARG(aChannel); + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIPrincipal> uriPrincipal; + nsIScriptSecurityManager* sm = nsContentUtils::GetSecurityManager(); + sm->GetChannelURIPrincipal(aChannel, getter_AddRefs(uriPrincipal)); + + nsCOMPtr<nsIURI> referrer; + nsCString remoteAddress; + + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + if (httpChannel) { + nsCOMPtr<nsIReferrerInfo> referrerInfo; + Unused << httpChannel->GetReferrerInfo(getter_AddRefs(referrerInfo)); + if (referrerInfo) { + referrer = referrerInfo->GetComputedReferrer(); + } + + nsCOMPtr<nsIHttpChannelInternal> intChannel(do_QueryInterface(aChannel)); + if (intChannel) { + Unused << intChannel->GetRemoteAddress(remoteAddress); + } + } + + nsCOMPtr<nsIPrincipal> truncatedPrincipal = + CreateTruncatedPrincipal(uriPrincipal); + + nsCOMPtr<nsIRedirectHistoryEntry> entry = + new nsRedirectHistoryEntry(truncatedPrincipal, referrer, remoteAddress); + + mRedirectChainIncludingInternalRedirects.AppendElement(entry); + if (!aIsInternalRedirect) { + mRedirectChain.AppendElement(entry); + } + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetRedirects(JSContext* aCx, JS::MutableHandle<JS::Value> aRedirects, + const RedirectHistoryArray& aArray) { + JS::Rooted<JSObject*> redirects(aCx, + JS::NewArrayObject(aCx, aArray.Length())); + NS_ENSURE_TRUE(redirects, NS_ERROR_OUT_OF_MEMORY); + + JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); + NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect(); + + for (size_t idx = 0; idx < aArray.Length(); idx++) { + JS::Rooted<JSObject*> jsobj(aCx); + nsresult rv = + xpc->WrapNative(aCx, global, aArray[idx], + NS_GET_IID(nsIRedirectHistoryEntry), jsobj.address()); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_STATE(jsobj); + + bool rc = JS_DefineElement(aCx, redirects, idx, jsobj, JSPROP_ENUMERATE); + NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED); + } + + aRedirects.setObject(*redirects); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetRedirectChainIncludingInternalRedirects( + JSContext* aCx, JS::MutableHandle<JS::Value> aChain) { + return GetRedirects(aCx, aChain, mRedirectChainIncludingInternalRedirects); +} + +const RedirectHistoryArray& +LoadInfo::RedirectChainIncludingInternalRedirects() { + return mRedirectChainIncludingInternalRedirects; +} + +NS_IMETHODIMP +LoadInfo::GetRedirectChain(JSContext* aCx, + JS::MutableHandle<JS::Value> aChain) { + return GetRedirects(aCx, aChain, mRedirectChain); +} + +const RedirectHistoryArray& LoadInfo::RedirectChain() { return mRedirectChain; } + +const nsTArray<nsCOMPtr<nsIPrincipal>>& LoadInfo::AncestorPrincipals() { + return mAncestorPrincipals; +} + +const nsTArray<uint64_t>& LoadInfo::AncestorBrowsingContextIDs() { + return mAncestorBrowsingContextIDs; +} + +void LoadInfo::SetCorsPreflightInfo(const nsTArray<nsCString>& aHeaders, + bool aForcePreflight) { + MOZ_ASSERT(GetSecurityMode() == + nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT); + MOZ_ASSERT(!mInitialSecurityCheckDone); + mCorsUnsafeHeaders = aHeaders.Clone(); + mForcePreflight = aForcePreflight; +} + +const nsTArray<nsCString>& LoadInfo::CorsUnsafeHeaders() { + return mCorsUnsafeHeaders; +} + +NS_IMETHODIMP +LoadInfo::GetForcePreflight(bool* aForcePreflight) { + *aForcePreflight = mForcePreflight; + return NS_OK; +} + +void LoadInfo::SetIsPreflight() { + MOZ_ASSERT(GetSecurityMode() == + nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT); + MOZ_ASSERT(!mInitialSecurityCheckDone); + mIsPreflight = true; +} + +void LoadInfo::SetUpgradeInsecureRequests(bool aValue) { + mUpgradeInsecureRequests = aValue; +} + +void LoadInfo::SetBrowserUpgradeInsecureRequests() { + mBrowserUpgradeInsecureRequests = true; +} + +NS_IMETHODIMP +LoadInfo::SetBrowserDidUpgradeInsecureRequests( + bool aBrowserDidUpgradeInsecureRequests) { + mBrowserDidUpgradeInsecureRequests = aBrowserDidUpgradeInsecureRequests; + return NS_OK; +} + +void LoadInfo::SetBrowserWouldUpgradeInsecureRequests() { + mBrowserWouldUpgradeInsecureRequests = true; +} + +NS_IMETHODIMP +LoadInfo::GetIsPreflight(bool* aIsPreflight) { + *aIsPreflight = mIsPreflight; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetLoadTriggeredFromExternal(bool aLoadTriggeredFromExternal) { + MOZ_ASSERT(!aLoadTriggeredFromExternal || + mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "can only set load triggered from external for TYPE_DOCUMENT"); + mLoadTriggeredFromExternal = aLoadTriggeredFromExternal; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadTriggeredFromExternal(bool* aLoadTriggeredFromExternal) { + *aLoadTriggeredFromExternal = mLoadTriggeredFromExternal; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetServiceWorkerTaintingSynthesized( + bool* aServiceWorkerTaintingSynthesized) { + MOZ_ASSERT(aServiceWorkerTaintingSynthesized); + *aServiceWorkerTaintingSynthesized = mServiceWorkerTaintingSynthesized; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetTainting(uint32_t* aTaintingOut) { + MOZ_ASSERT(aTaintingOut); + *aTaintingOut = static_cast<uint32_t>(mTainting); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::MaybeIncreaseTainting(uint32_t aTainting) { + NS_ENSURE_ARG(aTainting <= TAINTING_OPAQUE); + + // Skip if the tainting has been set by the service worker. + if (mServiceWorkerTaintingSynthesized) { + return NS_OK; + } + + LoadTainting tainting = static_cast<LoadTainting>(aTainting); + if (tainting > mTainting) { + mTainting = tainting; + } + return NS_OK; +} + +void LoadInfo::SynthesizeServiceWorkerTainting(LoadTainting aTainting) { + MOZ_DIAGNOSTIC_ASSERT(aTainting <= LoadTainting::Opaque); + mTainting = aTainting; + + // Flag to prevent the tainting from being increased. + mServiceWorkerTaintingSynthesized = true; +} + +NS_IMETHODIMP +LoadInfo::GetDocumentHasUserInteracted(bool* aDocumentHasUserInteracted) { + MOZ_ASSERT(aDocumentHasUserInteracted); + *aDocumentHasUserInteracted = mDocumentHasUserInteracted; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetDocumentHasUserInteracted(bool aDocumentHasUserInteracted) { + mDocumentHasUserInteracted = aDocumentHasUserInteracted; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAllowListFutureDocumentsCreatedFromThisRedirectChain( + bool* aValue) { + MOZ_ASSERT(aValue); + *aValue = mAllowListFutureDocumentsCreatedFromThisRedirectChain; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetAllowListFutureDocumentsCreatedFromThisRedirectChain(bool aValue) { + mAllowListFutureDocumentsCreatedFromThisRedirectChain = aValue; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetNeedForCheckingAntiTrackingHeuristic(bool* aValue) { + MOZ_ASSERT(aValue); + *aValue = mNeedForCheckingAntiTrackingHeuristic; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetNeedForCheckingAntiTrackingHeuristic(bool aValue) { + mNeedForCheckingAntiTrackingHeuristic = aValue; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetCspNonce(nsAString& aCspNonce) { + aCspNonce = mCspNonce; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetCspNonce(const nsAString& aCspNonce) { + MOZ_ASSERT(!mInitialSecurityCheckDone, + "setting the nonce is only allowed before any sec checks"); + mCspNonce = aCspNonce; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIntegrityMetadata(nsAString& aIntegrityMetadata) { + aIntegrityMetadata = mIntegrityMetadata; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) { + MOZ_ASSERT(!mInitialSecurityCheckDone, + "setting the nonce is only allowed before any sec checks"); + mIntegrityMetadata = aIntegrityMetadata; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) { + *aSkipContentSniffing = mSkipContentSniffing; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) { + mSkipContentSniffing = aSkipContentSniffing; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetHttpsOnlyStatus(uint32_t* aHttpsOnlyStatus) { + *aHttpsOnlyStatus = mHttpsOnlyStatus; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetHttpsOnlyStatus(uint32_t aHttpsOnlyStatus) { + mHttpsOnlyStatus = aHttpsOnlyStatus; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetHstsStatus(bool* aHstsStatus) { + *aHstsStatus = mHstsStatus; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetHstsStatus(bool aHstsStatus) { + mHstsStatus = aHstsStatus; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetHasValidUserGestureActivation( + bool* aHasValidUserGestureActivation) { + *aHasValidUserGestureActivation = mHasValidUserGestureActivation; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetHasValidUserGestureActivation( + bool aHasValidUserGestureActivation) { + mHasValidUserGestureActivation = aHasValidUserGestureActivation; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetAllowDeprecatedSystemRequests( + bool* aAllowDeprecatedSystemRequests) { + *aAllowDeprecatedSystemRequests = mAllowDeprecatedSystemRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetAllowDeprecatedSystemRequests( + bool aAllowDeprecatedSystemRequests) { + mAllowDeprecatedSystemRequests = aAllowDeprecatedSystemRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsUserTriggeredSave(bool* aIsUserTriggeredSave) { + *aIsUserTriggeredSave = + mIsUserTriggeredSave || + mInternalContentPolicyType == nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsUserTriggeredSave(bool aIsUserTriggeredSave) { + mIsUserTriggeredSave = aIsUserTriggeredSave; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsInDevToolsContext(bool* aIsInDevToolsContext) { + *aIsInDevToolsContext = mIsInDevToolsContext; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsInDevToolsContext(bool aIsInDevToolsContext) { + mIsInDevToolsContext = aIsInDevToolsContext; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetParserCreatedScript(bool* aParserCreatedScript) { + *aParserCreatedScript = mParserCreatedScript; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetParserCreatedScript(bool aParserCreatedScript) { + mParserCreatedScript = aParserCreatedScript; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsTopLevelLoad(bool* aResult) { + RefPtr<dom::BrowsingContext> bc; + GetTargetBrowsingContext(getter_AddRefs(bc)); + *aResult = !bc || bc->IsTop(); + return NS_OK; +} + +void LoadInfo::SetIsFromProcessingFrameAttributes() { + mIsFromProcessingFrameAttributes = true; +} + +NS_IMETHODIMP +LoadInfo::GetIsFromProcessingFrameAttributes( + bool* aIsFromProcessingFrameAttributes) { + MOZ_ASSERT(aIsFromProcessingFrameAttributes); + *aIsFromProcessingFrameAttributes = mIsFromProcessingFrameAttributes; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsMediaRequest(bool aIsMediaRequest) { + mIsMediaRequest = aIsMediaRequest; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsMediaRequest(bool* aIsMediaRequest) { + MOZ_ASSERT(aIsMediaRequest); + *aIsMediaRequest = mIsMediaRequest; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsMediaInitialRequest(bool aIsMediaInitialRequest) { + mIsMediaInitialRequest = aIsMediaInitialRequest; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsMediaInitialRequest(bool* aIsMediaInitialRequest) { + MOZ_ASSERT(aIsMediaInitialRequest); + *aIsMediaInitialRequest = mIsMediaInitialRequest; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsFromObjectOrEmbed(bool aIsFromObjectOrEmbed) { + mIsFromObjectOrEmbed = aIsFromObjectOrEmbed; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsFromObjectOrEmbed(bool* aIsFromObjectOrEmbed) { + MOZ_ASSERT(aIsFromObjectOrEmbed); + *aIsFromObjectOrEmbed = mIsFromObjectOrEmbed; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetShouldSkipCheckForBrokenURLOrZeroSized( + bool* aShouldSkipCheckForBrokenURLOrZeroSized) { + MOZ_ASSERT(aShouldSkipCheckForBrokenURLOrZeroSized); + *aShouldSkipCheckForBrokenURLOrZeroSized = mSkipCheckForBrokenURLOrZeroSized; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetResultPrincipalURI(nsIURI** aURI) { + *aURI = do_AddRef(mResultPrincipalURI).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetResultPrincipalURI(nsIURI* aURI) { + mResultPrincipalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetChannelCreationOriginalURI(nsIURI** aURI) { + *aURI = do_AddRef(mChannelCreationOriginalURI).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetChannelCreationOriginalURI(nsIURI* aURI) { + mChannelCreationOriginalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetRequestBlockingReason(uint32_t aReason) { + mRequestBlockingReason = aReason; + return NS_OK; +} +NS_IMETHODIMP +LoadInfo::GetRequestBlockingReason(uint32_t* aReason) { + *aReason = mRequestBlockingReason; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetUnstrippedURI(nsIURI** aURI) { + *aURI = do_AddRef(mUnstrippedURI).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetUnstrippedURI(nsIURI* aURI) { + mUnstrippedURI = aURI; + return NS_OK; +} + +void LoadInfo::SetClientInfo(const ClientInfo& aClientInfo) { + mClientInfo.emplace(aClientInfo); +} + +const Maybe<ClientInfo>& LoadInfo::GetClientInfo() { return mClientInfo; } + +void LoadInfo::GiveReservedClientSource( + UniquePtr<ClientSource>&& aClientSource) { + MOZ_DIAGNOSTIC_ASSERT(aClientSource); + mReservedClientSource = std::move(aClientSource); + SetReservedClientInfo(mReservedClientSource->Info()); +} + +UniquePtr<ClientSource> LoadInfo::TakeReservedClientSource() { + if (mReservedClientSource) { + // If the reserved ClientInfo was set due to a ClientSource being present, + // then clear that info object when the ClientSource is taken. + mReservedClientInfo.reset(); + } + return std::move(mReservedClientSource); +} + +void LoadInfo::SetReservedClientInfo(const ClientInfo& aClientInfo) { + MOZ_DIAGNOSTIC_ASSERT(mInitialClientInfo.isNothing()); + // Treat assignments of the same value as a no-op. The emplace below + // will normally assert when overwriting an existing value. + if (mReservedClientInfo.isSome()) { + if (mReservedClientInfo.ref() == aClientInfo) { + return; + } + MOZ_DIAGNOSTIC_ASSERT(false, "mReservedClientInfo already set"); + mReservedClientInfo.reset(); + } + mReservedClientInfo.emplace(aClientInfo); +} + +void LoadInfo::OverrideReservedClientInfoInParent( + const ClientInfo& aClientInfo) { + // This should only be called to handle redirects in the parent process. + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + + mInitialClientInfo.reset(); + mReservedClientInfo.reset(); + mReservedClientInfo.emplace(aClientInfo); +} + +const Maybe<ClientInfo>& LoadInfo::GetReservedClientInfo() { + return mReservedClientInfo; +} + +void LoadInfo::SetInitialClientInfo(const ClientInfo& aClientInfo) { + MOZ_DIAGNOSTIC_ASSERT(!mReservedClientSource); + MOZ_DIAGNOSTIC_ASSERT(mReservedClientInfo.isNothing()); + // Treat assignments of the same value as a no-op. The emplace below + // will normally assert when overwriting an existing value. + if (mInitialClientInfo.isSome() && mInitialClientInfo.ref() == aClientInfo) { + return; + } + mInitialClientInfo.emplace(aClientInfo); +} + +const Maybe<ClientInfo>& LoadInfo::GetInitialClientInfo() { + return mInitialClientInfo; +} + +void LoadInfo::SetController(const ServiceWorkerDescriptor& aServiceWorker) { + mController.emplace(aServiceWorker); +} + +void LoadInfo::ClearController() { mController.reset(); } + +const Maybe<ServiceWorkerDescriptor>& LoadInfo::GetController() { + return mController; +} + +void LoadInfo::SetPerformanceStorage(PerformanceStorage* aPerformanceStorage) { + mPerformanceStorage = aPerformanceStorage; +} + +PerformanceStorage* LoadInfo::GetPerformanceStorage() { + if (mPerformanceStorage) { + return mPerformanceStorage; + } + + auto* innerWindow = nsGlobalWindowInner::GetInnerWindowWithId(mInnerWindowID); + if (!innerWindow) { + return nullptr; + } + + if (!TriggeringPrincipal()->Equals(innerWindow->GetPrincipal())) { + return nullptr; + } + + if (nsILoadInfo::GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_SUBDOCUMENT && + !GetIsFromProcessingFrameAttributes()) { + // We only report loads caused by processing the attributes of the + // browsing context container. + return nullptr; + } + + mozilla::dom::Performance* performance = innerWindow->GetPerformance(); + if (!performance) { + return nullptr; + } + + return performance->AsPerformanceStorage(); +} + +NS_IMETHODIMP +LoadInfo::GetCspEventListener(nsICSPEventListener** aCSPEventListener) { + *aCSPEventListener = do_AddRef(mCSPEventListener).take(); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener) { + mCSPEventListener = aCSPEventListener; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetInternalContentPolicyType(nsContentPolicyType* aResult) { + *aResult = mInternalContentPolicyType; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetLoadingEmbedderPolicy( + nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) { + *aOutPolicy = mLoadingEmbedderPolicy; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetLoadingEmbedderPolicy( + nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) { + mLoadingEmbedderPolicy = aPolicy; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool* aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + *aIsOriginTrialCoepCredentiallessEnabledForTopLevel = + mIsOriginTrialCoepCredentiallessEnabledForTopLevel; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + mIsOriginTrialCoepCredentiallessEnabledForTopLevel = + aIsOriginTrialCoepCredentiallessEnabledForTopLevel; + return NS_OK; +} + +already_AddRefed<nsIContentSecurityPolicy> LoadInfo::GetCsp() { + // Before querying the CSP from the client we have to check if the + // triggeringPrincipal originates from an addon and potentially + // overrides the CSP stored within the client. + if (mLoadingPrincipal && BasePrincipal::Cast(mTriggeringPrincipal) + ->OverridesCSP(mLoadingPrincipal)) { + nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(mTriggeringPrincipal); + nsCOMPtr<nsIContentSecurityPolicy> addonCSP; + if (ep) { + addonCSP = ep->GetCsp(); + } + return addonCSP.forget(); + } + + if (mClientInfo.isNothing()) { + return nullptr; + } + + nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext); + RefPtr<Document> doc = node ? node->OwnerDoc() : nullptr; + + // If the client is of type window, then we return the cached CSP + // stored on the document instead of having to deserialize the CSP + // from the ClientInfo. + if (doc && mClientInfo->Type() == ClientType::Window) { + nsCOMPtr<nsIContentSecurityPolicy> docCSP = doc->GetCsp(); + return docCSP.forget(); + } + + Maybe<mozilla::ipc::CSPInfo> cspInfo = mClientInfo->GetCspInfo(); + if (cspInfo.isNothing()) { + return nullptr; + } + nsCOMPtr<nsIContentSecurityPolicy> clientCSP = + CSPInfoToCSP(cspInfo.ref(), doc); + return clientCSP.forget(); +} + +already_AddRefed<nsIContentSecurityPolicy> LoadInfo::GetPreloadCsp() { + if (mClientInfo.isNothing()) { + return nullptr; + } + + nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext); + RefPtr<Document> doc = node ? node->OwnerDoc() : nullptr; + + // If the client is of type window, then we return the cached CSP + // stored on the document instead of having to deserialize the CSP + // from the ClientInfo. + if (doc && mClientInfo->Type() == ClientType::Window) { + nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = doc->GetPreloadCsp(); + return preloadCsp.forget(); + } + + Maybe<mozilla::ipc::CSPInfo> cspInfo = mClientInfo->GetPreloadCspInfo(); + if (cspInfo.isNothing()) { + return nullptr; + } + nsCOMPtr<nsIContentSecurityPolicy> preloadCSP = + CSPInfoToCSP(cspInfo.ref(), doc); + return preloadCSP.forget(); +} + +already_AddRefed<nsIContentSecurityPolicy> LoadInfo::GetCspToInherit() { + nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = mCspToInherit; + return cspToInherit.forget(); +} + +nsIInterceptionInfo* LoadInfo::InterceptionInfo() { return mInterceptionInfo; } + +void LoadInfo::SetInterceptionInfo(nsIInterceptionInfo* aInfo) { + mInterceptionInfo = aInfo; +} + +NS_IMETHODIMP +LoadInfo::GetHasInjectedCookieForCookieBannerHandling( + bool* aHasInjectedCookieForCookieBannerHandling) { + *aHasInjectedCookieForCookieBannerHandling = + mHasInjectedCookieForCookieBannerHandling; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetHasInjectedCookieForCookieBannerHandling( + bool aHasInjectedCookieForCookieBannerHandling) { + mHasInjectedCookieForCookieBannerHandling = + aHasInjectedCookieForCookieBannerHandling; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetWasSchemelessInput(bool* aWasSchemelessInput) { + *aWasSchemelessInput = mWasSchemelessInput; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) { + mWasSchemelessInput = aWasSchemelessInput; + return NS_OK; +} + +} // namespace mozilla::net diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h new file mode 100644 index 0000000000..a8631b09b2 --- /dev/null +++ b/netwerk/base/LoadInfo.h @@ -0,0 +1,413 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_LoadInfo_h +#define mozilla_LoadInfo_h + +#include "nsIContentSecurityPolicy.h" +#include "nsIInterceptionInfo.h" +#include "nsILoadInfo.h" +#include "nsIPrincipal.h" +#include "nsIWeakReferenceUtils.h" // for nsWeakPtr +#include "nsIURI.h" +#include "nsContentUtils.h" +#include "nsString.h" +#include "nsTArray.h" + +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ServiceWorkerDescriptor.h" + +class nsDocShell; +class nsICookieJarSettings; +class nsINode; +class nsPIDOMWindowOuter; + +namespace mozilla { + +namespace dom { +class PerformanceStorage; +class XMLHttpRequestMainThread; +class CanonicalBrowsingContext; +class WindowGlobalParent; +} // namespace dom + +namespace net { +class EarlyHintPreloader; +class LoadInfoArgs; +class LoadInfo; +} // namespace net + +namespace ipc { +// we have to forward declare that function so we can use it as a friend. +nsresult LoadInfoArgsToLoadInfo(const mozilla::net::LoadInfoArgs& aLoadInfoArgs, + const nsACString& aOriginRemoteType, + nsINode* aCspToInheritLoadingContext, + net::LoadInfo** outLoadInfo); +} // namespace ipc + +namespace net { + +using RedirectHistoryArray = nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>; + +/** + * Class that provides an nsILoadInfo implementation. + */ +class LoadInfo final : public nsILoadInfo { + template <typename T, typename... Args> + friend already_AddRefed<T> mozilla::MakeAndAddRef(Args&&... aArgs); + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOADINFO + + // Used for TYPE_DOCUMENT load. + static already_AddRefed<LoadInfo> CreateForDocument( + dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + const OriginAttributes& aOriginAttributes, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags); + + // Used for TYPE_FRAME or TYPE_IFRAME load. + static already_AddRefed<LoadInfo> CreateForFrame( + dom::CanonicalBrowsingContext* aBrowsingContext, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags); + + // Use for non-{TYPE_DOCUMENT|TYPE_FRAME|TYPE_IFRAME} load. + static already_AddRefed<LoadInfo> CreateForNonDocument( + dom::WindowGlobalParent* aParentWGP, nsIPrincipal* aTriggeringPrincipal, + nsContentPolicyType aContentPolicyType, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags); + + // aLoadingPrincipal MUST NOT BE NULL. + LoadInfo(nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsINode* aLoadingContext, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + const Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo = + Maybe<mozilla::dom::ClientInfo>(), + const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController = + Maybe<mozilla::dom::ServiceWorkerDescriptor>(), + uint32_t aSandboxFlags = 0, + bool aSkipCheckForBrokenURLOrZeroSized = 0); + + // Constructor used for TYPE_DOCUMENT loads which have a different + // loadingContext than other loads. This ContextForTopLevelLoad is + // only used for content policy checks. + LoadInfo(nsPIDOMWindowOuter* aOuterWindow, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, + nsISupports* aContextForTopLevelLoad, nsSecurityFlags aSecurityFlags, + uint32_t aSandboxFlags); + + private: + // Use factory function CreateForDocument + // Used for TYPE_DOCUMENT load. + LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + const OriginAttributes& aOriginAttributes, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags); + + // Use factory function CreateForFrame + // Used for TYPE_FRAME or TYPE_IFRAME load. + LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags); + + // Used for loads initiated by DocumentLoadListener that are not TYPE_DOCUMENT + // | TYPE_FRAME | TYPE_FRAME. + LoadInfo(dom::WindowGlobalParent* aParentWGP, + nsIPrincipal* aTriggeringPrincipal, + const nsACString& aTriggeringRemoteType, + nsContentPolicyType aContentPolicyType, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags); + + public: + // Compute a list of ancestor principals and BrowsingContext IDs. + // See methods AncestorPrincipals and AncestorBrowsingContextIDs + // in nsILoadInfo.idl for details. + static void ComputeAncestors( + dom::CanonicalBrowsingContext* aBC, + nsTArray<nsCOMPtr<nsIPrincipal>>& aAncestorPrincipals, + nsTArray<uint64_t>& aBrowsingContextIDs); + + // create an exact copy of the loadinfo + already_AddRefed<nsILoadInfo> Clone() const; + + // hands off!!! don't use CloneWithNewSecFlags unless you know + // exactly what you are doing - it should only be used within + // nsBaseChannel::Redirect() + already_AddRefed<nsILoadInfo> CloneWithNewSecFlags( + nsSecurityFlags aSecurityFlags) const; + // creates a copy of the loadinfo which is appropriate to use for a + // separate request. I.e. not for a redirect or an inner channel, but + // when a separate request is made with the same security properties. + already_AddRefed<nsILoadInfo> CloneForNewRequest() const; + + // The `nsContentPolicyType GetExternalContentPolicyType()` version in the + // base class is hidden by the implementation of + // `GetExternalContentPolicyType(nsContentPolicyType* aResult)` in + // LoadInfo.cpp. Explicit mark it visible. + using nsILoadInfo::GetExternalContentPolicyType; + + void SetIsPreflight(); + void SetUpgradeInsecureRequests(bool aValue); + void SetBrowserUpgradeInsecureRequests(); + void SetBrowserWouldUpgradeInsecureRequests(); + void SetIsFromProcessingFrameAttributes(); + + // Hands off from the cspToInherit functionality! + // + // For navigations, GetCSPToInherit returns what the spec calls the + // "request's client's global object's CSP list", or more precisely + // a snapshot of it taken when the navigation starts. For navigations + // that need to inherit their CSP, this is the right CSP to use for + // the new document. We need a way to transfer the CSP from the + // docshell (where the navigation starts) to the point where the new + // document is created and decides whether to inherit its CSP, and + // this is the mechanism we use for that. + // + // For example: + // A document with a CSP triggers a new top-level data: URI load. + // We pass the CSP of the document that triggered the load all the + // way to docshell. Within docshell we call SetCSPToInherit() on the + // loadinfo. Within Document::InitCSP() we check if the newly created + // document needs to inherit the CSP. If so, we call GetCSPToInherit() + // and set the inherited CSP as the CSP for the new document. Please + // note that any additonal Meta CSP in that document will be merged + // into that CSP. Any subresource loads within that document + // subesquently will receive the correct CSP by querying + // loadinfo->GetCsp() from that point on. + void SetCSPToInherit(nsIContentSecurityPolicy* aCspToInherit) { + mCspToInherit = aCspToInherit; + } + + bool HasIsThirdPartyContextToTopWindowSet() { + return mIsThirdPartyContextToTopWindow.isSome(); + } + void ClearIsThirdPartyContextToTopWindow() { + mIsThirdPartyContextToTopWindow.reset(); + } + +#ifdef DEBUG + void MarkOverriddenFingerprintingSettingsAsSet() { + mOverriddenFingerprintingSettingsIsSet = true; + } +#endif + + private: + // private constructor that is only allowed to be called from within + // HttpChannelParent and FTPChannelParent declared as friends undeneath. + // In e10s we can not serialize nsINode, hence we store the innerWindowID. + // Please note that aRedirectChain uses swapElements. + LoadInfo( + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aTopLevelPrincipal, + nsIURI* aResultPrincipalURI, nsICookieJarSettings* aCookieJarSettings, + nsIContentSecurityPolicy* aCspToInherit, + const nsACString& aTriggeringRemoteType, + const nsID& aSandboxedNullPrincipalID, + const Maybe<mozilla::dom::ClientInfo>& aClientInfo, + const Maybe<mozilla::dom::ClientInfo>& aReservedClientInfo, + const Maybe<mozilla::dom::ClientInfo>& aInitialClientInfo, + const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController, + nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags, + uint32_t aTriggeringSandboxFlags, uint64_t aTriggeringWindowId, + bool aTriggeringStorageAccess, nsContentPolicyType aContentPolicyType, + LoadTainting aTainting, bool aBlockAllMixedContent, + bool aUpgradeInsecureRequests, bool aBrowserUpgradeInsecureRequests, + bool aBrowserDidUpgradeInsecureRequests, + bool aBrowserWouldUpgradeInsecureRequests, bool aForceAllowDataURI, + bool aAllowInsecureRedirectToDataURI, + bool aSkipContentPolicyCheckForWebRequest, bool aOriginalFrameSrcLoad, + bool aForceInheritPrincipalDropped, uint64_t aInnerWindowID, + uint64_t aBrowsingContextID, uint64_t aFrameBrowsingContextID, + bool aInitialSecurityCheckDone, bool aIsThirdPartyContext, + const Maybe<bool>& aIsThirdPartyContextToTopWindow, + bool aIsFormSubmission, bool aSendCSPViolationEvents, + const OriginAttributes& aOriginAttributes, + RedirectHistoryArray&& aRedirectChainIncludingInternalRedirects, + RedirectHistoryArray&& aRedirectChain, + nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals, + const nsTArray<uint64_t>& aAncestorBrowsingContextIDs, + const nsTArray<nsCString>& aCorsUnsafeHeaders, bool aForcePreflight, + bool aIsPreflight, bool aLoadTriggeredFromExternal, + bool aServiceWorkerTaintingSynthesized, bool aDocumentHasUserInteracted, + bool aAllowListFutureDocumentsCreatedFromThisRedirectChain, + bool aNeedForCheckingAntiTrackingHeuristic, const nsAString& aCspNonce, + const nsAString& aIntegrityMetadata, bool aSkipContentSniffing, + uint32_t aHttpsOnlyStatus, bool aHstsStatus, + bool aHasValidUserGestureActivation, bool aAllowDeprecatedSystemRequests, + bool aIsInDevToolsContext, bool aParserCreatedScript, + nsILoadInfo::StoragePermissionState aStoragePermission, + const Maybe<RFPTarget>& aOverriddenFingerprintingSettings, + bool aIsMetaRefresh, uint32_t aRequestBlockingReason, + nsINode* aLoadingContext, + nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy, + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel, + nsIURI* aUnstrippedURI, nsIInterceptionInfo* aInterceptionInfo, + bool aHasInjectedCookieForCookieBannerHandling, bool aWasSchemelessInput); + LoadInfo(const LoadInfo& rhs); + + NS_IMETHOD GetRedirects(JSContext* aCx, + JS::MutableHandle<JS::Value> aRedirects, + const RedirectHistoryArray& aArra); + + friend nsresult mozilla::ipc::LoadInfoArgsToLoadInfo( + const mozilla::net::LoadInfoArgs& aLoadInfoArgs, + const nsACString& aOriginRemoteType, nsINode* aCspToInheritLoadingContext, + net::LoadInfo** outLoadInfo); + + ~LoadInfo(); + + void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow); + void ComputeIsThirdPartyContext(dom::WindowGlobalParent* aGlobal); + + // This function is the *only* function which can change the securityflags + // of a loadinfo. It only exists because of the XHR code. Don't call it + // from anywhere else! + void SetIncludeCookiesSecFlag(); + friend class mozilla::dom::XMLHttpRequestMainThread; + + // nsDocShell::OpenInitializedChannel and EarlyHintPreloader::OpenChannel + // needs to update the loadInfo with the correct browsingContext. + friend class ::nsDocShell; + friend class mozilla::net::EarlyHintPreloader; + void UpdateBrowsingContextID(uint64_t aBrowsingContextID) { + mBrowsingContextID = aBrowsingContextID; + } + void UpdateFrameBrowsingContextID(uint64_t aFrameBrowsingContextID) { + mFrameBrowsingContextID = aFrameBrowsingContextID; + } + MOZ_NEVER_INLINE void ReleaseMembers(); + + // if you add a member, please also update the copy constructor and consider + // if it should be merged from parent channel through + // ParentLoadInfoForwarderArgs. + nsCOMPtr<nsIPrincipal> mLoadingPrincipal; + nsCOMPtr<nsIPrincipal> mTriggeringPrincipal; + nsCOMPtr<nsIPrincipal> mPrincipalToInherit; + nsCOMPtr<nsIPrincipal> mTopLevelPrincipal; + nsCOMPtr<nsIURI> mResultPrincipalURI; + nsCOMPtr<nsIURI> mChannelCreationOriginalURI; + nsCOMPtr<nsICSPEventListener> mCSPEventListener; + nsCOMPtr<nsICookieJarSettings> mCookieJarSettings; + nsCOMPtr<nsIContentSecurityPolicy> mCspToInherit; + nsCString mTriggeringRemoteType; + nsID mSandboxedNullPrincipalID; + + Maybe<mozilla::dom::ClientInfo> mClientInfo; + UniquePtr<mozilla::dom::ClientSource> mReservedClientSource; + Maybe<mozilla::dom::ClientInfo> mReservedClientInfo; + Maybe<mozilla::dom::ClientInfo> mInitialClientInfo; + Maybe<mozilla::dom::ServiceWorkerDescriptor> mController; + RefPtr<mozilla::dom::PerformanceStorage> mPerformanceStorage; + + nsWeakPtr mLoadingContext; + nsWeakPtr mContextForTopLevelLoad; + nsSecurityFlags mSecurityFlags; + uint32_t mSandboxFlags; + uint32_t mTriggeringSandboxFlags = 0; + uint64_t mTriggeringWindowId = 0; + bool mTriggeringStorageAccess = false; + nsContentPolicyType mInternalContentPolicyType; + LoadTainting mTainting = LoadTainting::Basic; + bool mBlockAllMixedContent = false; + bool mUpgradeInsecureRequests = false; + bool mBrowserUpgradeInsecureRequests = false; + bool mBrowserDidUpgradeInsecureRequests = false; + bool mBrowserWouldUpgradeInsecureRequests = false; + bool mForceAllowDataURI = false; + bool mAllowInsecureRedirectToDataURI = false; + bool mSkipContentPolicyCheckForWebRequest = false; + bool mOriginalFrameSrcLoad = false; + bool mForceInheritPrincipalDropped = false; + uint64_t mInnerWindowID = 0; + uint64_t mBrowsingContextID = 0; + uint64_t mWorkerAssociatedBrowsingContextID = 0; + uint64_t mFrameBrowsingContextID = 0; + bool mInitialSecurityCheckDone = false; + // NB: TYPE_DOCUMENT implies !third-party. + bool mIsThirdPartyContext = false; + Maybe<bool> mIsThirdPartyContextToTopWindow; + bool mIsFormSubmission = false; + bool mSendCSPViolationEvents = true; + OriginAttributes mOriginAttributes; + RedirectHistoryArray mRedirectChainIncludingInternalRedirects; + RedirectHistoryArray mRedirectChain; + nsTArray<nsCOMPtr<nsIPrincipal>> mAncestorPrincipals; + nsTArray<uint64_t> mAncestorBrowsingContextIDs; + nsTArray<nsCString> mCorsUnsafeHeaders; + uint32_t mRequestBlockingReason = BLOCKING_REASON_NONE; + bool mForcePreflight = false; + bool mIsPreflight = false; + bool mLoadTriggeredFromExternal = false; + bool mServiceWorkerTaintingSynthesized = false; + bool mDocumentHasUserInteracted = false; + bool mAllowListFutureDocumentsCreatedFromThisRedirectChain = false; + bool mNeedForCheckingAntiTrackingHeuristic = false; + nsString mCspNonce; + nsString mIntegrityMetadata; + bool mSkipContentSniffing = false; + uint32_t mHttpsOnlyStatus = nsILoadInfo::HTTPS_ONLY_UNINITIALIZED; + bool mHstsStatus = false; + bool mHasValidUserGestureActivation = false; + bool mAllowDeprecatedSystemRequests = false; + bool mIsUserTriggeredSave = false; + bool mIsInDevToolsContext = false; + bool mParserCreatedScript = false; + nsILoadInfo::StoragePermissionState mStoragePermission = + nsILoadInfo::NoStoragePermission; + Maybe<RFPTarget> mOverriddenFingerprintingSettings; +#ifdef DEBUG + // A boolean used to ensure the mOverriddenFingerprintingSettings is set + // before use it. + bool mOverriddenFingerprintingSettingsIsSet = false; +#endif + bool mIsMetaRefresh = false; + + // Is true if this load was triggered by processing the attributes of the + // browsing context container. + // See nsILoadInfo.isFromProcessingFrameAttributes + bool mIsFromProcessingFrameAttributes = false; + + // See nsILoadInfo.isMediaRequest and nsILoadInfo.isMediaInitialRequest. + bool mIsMediaRequest = false; + bool mIsMediaInitialRequest = false; + + // See nsILoadInfo.isFromObjectOrEmbed + bool mIsFromObjectOrEmbed = false; + + bool mSkipCheckForBrokenURLOrZeroSized = false; + + // The cross origin embedder policy that the loading need to respect. + // If the value is nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP, CORP checking + // must be performed for the loading. + // See https://wicg.github.io/cross-origin-embedder-policy/#corp-check. + nsILoadInfo::CrossOriginEmbedderPolicy mLoadingEmbedderPolicy = + nsILoadInfo::EMBEDDER_POLICY_NULL; + + bool mIsOriginTrialCoepCredentiallessEnabledForTopLevel = false; + + nsCOMPtr<nsIURI> mUnstrippedURI; + + nsCOMPtr<nsIInterceptionInfo> mInterceptionInfo; + + bool mHasInjectedCookieForCookieBannerHandling = false; + bool mWasSchemelessInput = false; +}; + +// This is exposed solely for testing purposes and should not be used outside of +// LoadInfo +already_AddRefed<nsIPrincipal> CreateTruncatedPrincipal(nsIPrincipal*); + +} // namespace net +} // namespace mozilla + +#endif // mozilla_LoadInfo_h diff --git a/netwerk/base/LoadTainting.h b/netwerk/base/LoadTainting.h new file mode 100644 index 0000000000..11dc8a304f --- /dev/null +++ b/netwerk/base/LoadTainting.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_LoadTainting_h +#define mozilla_LoadTainting_h + +#include <stdint.h> + +namespace mozilla { + +// Define an enumeration to reflect the concept of response tainting from the +// the fetch spec: +// +// https://fetch.spec.whatwg.org/#concept-request-response-tainting +// +// Roughly the tainting means: +// +// * Basic: the request resulted in a same-origin or non-http load +// * CORS: the request resulted in a cross-origin load with CORS headers +// * Opaque: the request resulted in a cross-origin load without CORS headers +// +// The enumeration is purposefully designed such that more restrictive tainting +// corresponds to a higher integral value. +enum class LoadTainting : uint8_t { Basic = 0, CORS = 1, Opaque = 2 }; + +} // namespace mozilla + +#endif // mozilla_LoadTainting_h diff --git a/netwerk/base/MemoryDownloader.cpp b/netwerk/base/MemoryDownloader.cpp new file mode 100644 index 0000000000..afb4552cca --- /dev/null +++ b/netwerk/base/MemoryDownloader.cpp @@ -0,0 +1,71 @@ +/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MemoryDownloader.h" + +#include "mozilla/Assertions.h" +#include "nsIInputStream.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(MemoryDownloader, nsIStreamListener, nsIRequestObserver) + +MemoryDownloader::MemoryDownloader(IObserver* aObserver) + : mObserver(aObserver), mStatus(NS_ERROR_NOT_INITIALIZED) {} + +NS_IMETHODIMP +MemoryDownloader::OnStartRequest(nsIRequest* aRequest) { + MOZ_ASSERT(!mData); + mData.reset(new FallibleTArray<uint8_t>()); + mStatus = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP +MemoryDownloader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { + MOZ_ASSERT_IF(NS_FAILED(mStatus), NS_FAILED(aStatus)); + MOZ_ASSERT(!mData == NS_FAILED(mStatus)); + Data data; + data.swap(mData); + RefPtr<IObserver> observer; + observer.swap(mObserver); + observer->OnDownloadComplete(this, aRequest, aStatus, std::move(data)); + return NS_OK; +} + +nsresult MemoryDownloader::ConsumeData(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) { + MemoryDownloader* self = static_cast<MemoryDownloader*>(aClosure); + if (!self->mData->AppendElements(aFromRawSegment, aCount, fallible)) { + // The error returned by ConsumeData isn't propagated to the + // return of ReadSegments, so it has to be passed as state. + self->mStatus = NS_ERROR_OUT_OF_MEMORY; + return NS_ERROR_OUT_OF_MEMORY; + } + *aWriteCount = aCount; + return NS_OK; +} + +NS_IMETHODIMP +MemoryDownloader::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStr, + uint64_t aSourceOffset, uint32_t aCount) { + uint32_t n; + MOZ_ASSERT(mData); + nsresult rv = aInStr->ReadSegments(ConsumeData, this, aCount, &n); + if (NS_SUCCEEDED(mStatus) && NS_FAILED(rv)) { + mStatus = rv; + } + if (NS_WARN_IF(NS_FAILED(mStatus))) { + mData.reset(nullptr); + return mStatus; + } + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/MemoryDownloader.h b/netwerk/base/MemoryDownloader.h new file mode 100644 index 0000000000..8b5700f5bf --- /dev/null +++ b/netwerk/base/MemoryDownloader.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_MemoryDownloader_h__ +#define mozilla_net_MemoryDownloader_h__ + +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIStreamListener.h" +#include "nsTArray.h" + +/** + * mozilla::net::MemoryDownloader + * + * This class is similar to nsIDownloader, but stores the downloaded + * stream in memory instead of a file. Ownership of the temporary + * memory is transferred to the observer when download is complete; + * there is no need to retain a reference to the downloader. + */ + +namespace mozilla { +namespace net { + +class MemoryDownloader final : public nsIStreamListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + using Data = mozilla::UniquePtr<FallibleTArray<uint8_t>>; + + class IObserver : public nsISupports { + public: + // Note: aData may be null if (and only if) aStatus indicates failure. + virtual void OnDownloadComplete(MemoryDownloader* aDownloader, + nsIRequest* aRequest, nsresult aStatus, + Data aData) = 0; + }; + + explicit MemoryDownloader(IObserver* aObserver); + + private: + virtual ~MemoryDownloader() = default; + + static nsresult ConsumeData(nsIInputStream* in, void* closure, + const char* fromRawSegment, uint32_t toOffset, + uint32_t count, uint32_t* writeCount); + + RefPtr<IObserver> mObserver; + Data mData; + nsresult mStatus; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_MemoryDownloader_h__ diff --git a/netwerk/base/NetUtil.sys.mjs b/netwerk/base/NetUtil.sys.mjs new file mode 100644 index 0000000000..679c9979a7 --- /dev/null +++ b/netwerk/base/NetUtil.sys.mjs @@ -0,0 +1,453 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- + * vim: sw=4 ts=4 sts=4 et filetype=javascript + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Necko utilities + */ + +// ////////////////////////////////////////////////////////////////////////////// +// // Constants + +const PR_UINT32_MAX = 0xffffffff; + +const BinaryInputStream = Components.Constructor( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +// ////////////////////////////////////////////////////////////////////////////// +// // NetUtil Object + +export var NetUtil = { + /** + * Function to perform simple async copying from aSource (an input stream) + * to aSink (an output stream). The copy will happen on some background + * thread. Both streams will be closed when the copy completes. + * + * @param aSource + * The input stream to read from + * @param aSink + * The output stream to write to + * @param aCallback [optional] + * A function that will be called at copy completion with a single + * argument: the nsresult status code for the copy operation. + * + * @return An nsIRequest representing the copy operation (for example, this + * can be used to cancel the copying). The consumer can ignore the + * return value if desired. + */ + asyncCopy: function NetUtil_asyncCopy(aSource, aSink, aCallback = null) { + if (!aSource || !aSink) { + let exception = new Components.Exception( + "Must have a source and a sink", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + // make a stream copier + var copier = Cc[ + "@mozilla.org/network/async-stream-copier;1" + ].createInstance(Ci.nsIAsyncStreamCopier2); + copier.init( + aSource, + aSink, + null /* Default event target */, + 0 /* Default length */, + true, + true /* Auto-close */ + ); + + var observer; + if (aCallback) { + observer = { + onStartRequest(aRequest) {}, + onStopRequest(aRequest, aStatusCode) { + aCallback(aStatusCode); + }, + }; + } else { + observer = null; + } + + // start the copying + copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null); + return copier; + }, + + /** + * Asynchronously opens a source and fetches the response. While the fetch + * is asynchronous, I/O may happen on the main thread. When reading from + * a local file, prefer using IOUtils methods instead. + * + * @param aSource + * This argument can be one of the following: + * - An options object that will be passed to NetUtil.newChannel. + * - An existing nsIChannel. + * - An existing nsIInputStream. + * Using an nsIURI, nsIFile, or string spec directly is deprecated. + * @param aCallback + * The callback function that will be notified upon completion. It + * will get these arguments: + * 1) An nsIInputStream containing the data from aSource, if any. + * 2) The status code from opening the source. + * 3) Reference to the nsIRequest. + */ + asyncFetch: function NetUtil_asyncFetch(aSource, aCallback) { + if (!aSource || !aCallback) { + let exception = new Components.Exception( + "Must have a source and a callback", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + // Create a pipe that will create our output stream that we can use once + // we have gotten all the data. + let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); + pipe.init(true, true, 0, PR_UINT32_MAX, null); + + // Create a listener that will give data to the pipe's output stream. + let listener = Cc[ + "@mozilla.org/network/simple-stream-listener;1" + ].createInstance(Ci.nsISimpleStreamListener); + listener.init(pipe.outputStream, { + onStartRequest(aRequest) {}, + onStopRequest(aRequest, aStatusCode) { + pipe.outputStream.close(); + aCallback(pipe.inputStream, aStatusCode, aRequest); + }, + }); + + // Input streams are handled slightly differently from everything else. + if (aSource instanceof Ci.nsIInputStream) { + let pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance( + Ci.nsIInputStreamPump + ); + pump.init(aSource, 0, 0, true); + pump.asyncRead(listener, null); + return; + } + + let channel = aSource; + if (!(channel instanceof Ci.nsIChannel)) { + channel = this.newChannel(aSource); + } + + try { + channel.asyncOpen(listener); + } catch (e) { + let exception = new Components.Exception( + "Failed to open input source '" + channel.originalURI.spec + "'", + e.result, + Components.stack.caller, + aSource, + e + ); + throw exception; + } + }, + + /** + * Constructs a new URI for the given spec, character set, and base URI, or + * an nsIFile. + * + * @param aTarget + * The string spec for the desired URI or an nsIFile. + * @param aOriginCharset [optional] + * The character set for the URI. Only used if aTarget is not an + * nsIFile. + * @param aBaseURI [optional] + * The base URI for the spec. Only used if aTarget is not an + * nsIFile. + * + * @return an nsIURI object. + */ + newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI) { + if (!aTarget) { + let exception = new Components.Exception( + "Must have a non-null string spec or nsIFile object", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (aTarget instanceof Ci.nsIFile) { + return Services.io.newFileURI(aTarget); + } + + return Services.io.newURI(aTarget, aOriginCharset, aBaseURI); + }, + + /** + * Constructs a new channel for the given source. + * + * Keep in mind that URIs coming from a webpage should *never* use the + * systemPrincipal as the loadingPrincipal. + * + * @param aWhatToLoad + * This argument used to be a string spec for the desired URI, an + * nsIURI, or an nsIFile. Now it should be an options object with + * the following properties: + * { + * uri: + * The full URI spec string, nsIURI or nsIFile to create the + * channel for. + * Note that this cannot be an nsIFile if you have to specify a + * non-default charset or base URI. Call NetUtil.newURI first if + * you need to construct an URI using those options. + * loadingNode: + * loadingPrincipal: + * triggeringPrincipal: + * securityFlags: + * contentPolicyType: + * These will be used as values for the nsILoadInfo object on the + * created channel. For details, see nsILoadInfo in nsILoadInfo.idl + * loadUsingSystemPrincipal: + * Set this to true to use the system principal as + * loadingPrincipal. This must be omitted if loadingPrincipal or + * loadingNode are present. + * This should be used with care as it skips security checks. + * } + * @return an nsIChannel object. + */ + newChannel: function NetUtil_newChannel(aWhatToLoad) { + // Make sure the API is called using only the options object. + if (typeof aWhatToLoad != "object" || arguments.length != 1) { + throw new Components.Exception( + "newChannel requires a single object argument", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + let { + uri, + loadingNode, + loadingPrincipal, + loadUsingSystemPrincipal, + triggeringPrincipal, + securityFlags, + contentPolicyType, + } = aWhatToLoad; + + if (!uri) { + throw new Components.Exception( + "newChannel requires the 'uri' property on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (typeof uri == "string" || uri instanceof Ci.nsIFile) { + uri = this.newURI(uri); + } + + if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) { + throw new Components.Exception( + "newChannel requires at least one of the 'loadingNode'," + + " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" + + " properties on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (loadUsingSystemPrincipal === true) { + if (loadingNode || loadingPrincipal) { + throw new Components.Exception( + "newChannel does not accept 'loadUsingSystemPrincipal'" + + " if the 'loadingNode' or 'loadingPrincipal' properties" + + " are present on the options object.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + loadingPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + } else if (loadUsingSystemPrincipal !== undefined) { + throw new Components.Exception( + "newChannel requires the 'loadUsingSystemPrincipal'" + + " property on the options object to be 'true' or 'undefined'.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + + if (securityFlags === undefined) { + if (!loadUsingSystemPrincipal) { + throw new Components.Exception( + "newChannel requires the 'securityFlags' property on" + + " the options object unless loading from system principal.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; + } + + if (contentPolicyType === undefined) { + if (!loadUsingSystemPrincipal) { + throw new Components.Exception( + "newChannel requires the 'contentPolicyType' property on" + + " the options object unless loading from system principal.", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + } + contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER; + } + + let channel = Services.io.newChannelFromURI( + uri, + loadingNode || null, + loadingPrincipal || null, + triggeringPrincipal || null, + securityFlags, + contentPolicyType + ); + if (loadUsingSystemPrincipal) { + channel.loadInfo.allowDeprecatedSystemRequests = true; + } + return channel; + }, + + newWebTransport: function NetUtil_newWebTransport() { + return Services.io.newWebTransport(); + }, + + /** + * Reads aCount bytes from aInputStream into a string. + * + * @param aInputStream + * The input stream to read from. + * @param aCount + * The number of bytes to read from the stream. + * @param aOptions [optional] + * charset + * The character encoding of stream data. + * replacement + * The character to replace unknown byte sequences. + * If unset, it causes an exceptions to be thrown. + * + * @return the bytes from the input stream in string form. + * + * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream. + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would + * block the calling thread (non-blocking mode only). + * @throws NS_ERROR_FAILURE if there are not enough bytes available to read + * aCount amount of data. + * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences + */ + readInputStreamToString: function NetUtil_readInputStreamToString( + aInputStream, + aCount, + aOptions + ) { + if (!(aInputStream instanceof Ci.nsIInputStream)) { + let exception = new Components.Exception( + "First argument should be an nsIInputStream", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (!aCount) { + let exception = new Components.Exception( + "Non-zero amount of bytes must be specified", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (aOptions && "charset" in aOptions) { + let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance( + Ci.nsIConverterInputStream + ); + try { + // When replacement is set, the character that is unknown sequence + // replaces with aOptions.replacement character. + if (!("replacement" in aOptions)) { + // aOptions.replacement isn't set. + // If input stream has unknown sequences for aOptions.charset, + // throw NS_ERROR_ILLEGAL_INPUT. + aOptions.replacement = 0; + } + + cis.init(aInputStream, aOptions.charset, aCount, aOptions.replacement); + let str = {}; + cis.readString(-1, str); + cis.close(); + return str.value; + } catch (e) { + // Adjust the stack so it throws at the caller's location. + throw new Components.Exception( + e.message, + e.result, + Components.stack.caller, + e.data + ); + } + } + + let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(aInputStream); + try { + return sis.readBytes(aCount); + } catch (e) { + // Adjust the stack so it throws at the caller's location. + throw new Components.Exception( + e.message, + e.result, + Components.stack.caller, + e.data + ); + } + }, + + /** + * Reads aCount bytes from aInputStream into a string. + * + * @param {nsIInputStream} aInputStream + * The input stream to read from. + * @param {integer} [aCount = aInputStream.available()] + * The number of bytes to read from the stream. + * + * @return the bytes from the input stream in string form. + * + * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream. + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would + * block the calling thread (non-blocking mode only). + * @throws NS_ERROR_FAILURE if there are not enough bytes available to read + * aCount amount of data. + */ + readInputStream(aInputStream, aCount) { + if (!(aInputStream instanceof Ci.nsIInputStream)) { + let exception = new Components.Exception( + "First argument should be an nsIInputStream", + Cr.NS_ERROR_INVALID_ARG, + Components.stack.caller + ); + throw exception; + } + + if (!aCount) { + aCount = aInputStream.available(); + } + + let stream = new BinaryInputStream(aInputStream); + let result = new ArrayBuffer(aCount); + stream.readArrayBuffer(result.byteLength, result); + return result; + }, +}; diff --git a/netwerk/base/NetworkConnectivityService.cpp b/netwerk/base/NetworkConnectivityService.cpp new file mode 100644 index 0000000000..1e126742ce --- /dev/null +++ b/netwerk/base/NetworkConnectivityService.cpp @@ -0,0 +1,554 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DNSUtils.h" +#include "NetworkConnectivityService.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/net/SocketProcessParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "nsCOMPtr.h" +#include "nsIChannel.h" +#include "nsIOService.h" +#include "nsICancelable.h" +#include "xpcpublic.h" +#include "nsSocketTransport2.h" +#include "nsIHttpChannelInternal.h" +#include "nsINetworkLinkService.h" +#include "mozilla/StaticPrefs_network.h" + +static mozilla::LazyLogModule gNCSLog("NetworkConnectivityService"); +#undef LOG +#define LOG(args) MOZ_LOG(gNCSLog, mozilla::LogLevel::Debug, args) + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(NetworkConnectivityService, nsIDNSListener, nsIObserver, + nsINetworkConnectivityService, nsIStreamListener) + +static StaticRefPtr<NetworkConnectivityService> gConnService; + +NetworkConnectivityService::NetworkConnectivityService() + : mDNSv4(UNKNOWN), + mDNSv6(UNKNOWN), + mIPv4(UNKNOWN), + mIPv6(UNKNOWN), + mNAT64(UNKNOWN), + mLock("nat64prefixes") {} + +// static +already_AddRefed<NetworkConnectivityService> +NetworkConnectivityService::GetSingleton() { + if (gConnService) { + return do_AddRef(gConnService); + } + + RefPtr<NetworkConnectivityService> service = new NetworkConnectivityService(); + service->Init(); + + gConnService = std::move(service); + ClearOnShutdown(&gConnService); + return do_AddRef(gConnService); +} + +nsresult NetworkConnectivityService::Init() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); + observerService->AddObserver(this, "network:captive-portal-connectivity", + false); + + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::GetDNSv4(ConnectivityState* aState) { + NS_ENSURE_ARG(aState); + *aState = mDNSv4; + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::GetDNSv6(ConnectivityState* aState) { + NS_ENSURE_ARG(aState); + *aState = mDNSv6; + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::GetIPv4(ConnectivityState* aState) { + NS_ENSURE_ARG(aState); + *aState = mIPv4; + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::GetIPv6(ConnectivityState* aState) { + NS_ENSURE_ARG(aState); + *aState = mIPv6; + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::GetNAT64(ConnectivityState* aState) { + NS_ENSURE_ARG(aState); + *aState = mNAT64; + return NS_OK; +} + +already_AddRefed<AddrInfo> NetworkConnectivityService::MapNAT64IPs( + AddrInfo* aNewRRSet) { + // Add prefixes only if there are no IPv6 addresses. + // Expect that if aNewRRSet has IPv6 addresses, they must come + // before IPv4 addresses. + if (aNewRRSet->Addresses().IsEmpty() || + aNewRRSet->Addresses()[0].raw.family == PR_AF_INET6) { + return do_AddRef(aNewRRSet); + } + + // Currently we only add prefixes to the first IP's clones. + uint32_t ip = aNewRRSet->Addresses()[0].inet.ip; + nsTArray<NetAddr> addresses = aNewRRSet->Addresses().Clone(); + + { + MutexAutoLock lock(mLock); + for (const auto& prefix : mNAT64Prefixes) { + NetAddr addr = NetAddr(prefix); + + // Copy the IPv4 address to the end + addr.inet6.ip.u32[3] = ip; + + // If we have both IPv4 and NAT64, we be could insourcing NAT64 + // to avoid double NAT and improve performance. However, this + // breaks WebRTC, so we push it to the back. + addresses.AppendElement(addr); + } + } + + auto builder = aNewRRSet->Build(); + builder.SetAddresses(std::move(addresses)); + return builder.Finish(); +} + +// Returns true if a prefix was read and saved to the argument +static inline bool NAT64PrefixFromPref(NetAddr* prefix) { + nsAutoCString nat64PrefixPref; + + nsresult rv = Preferences::GetCString( + "network.connectivity-service.nat64-prefix", nat64PrefixPref); + return !(NS_FAILED(rv) || nat64PrefixPref.IsEmpty() || + NS_FAILED(prefix->InitFromString(nat64PrefixPref)) || + prefix->raw.family != PR_AF_INET6); +} + +static inline bool NAT64PrefixCompare(const NetAddr& prefix1, + const NetAddr& prefix2) { + // Compare the first 96 bits as 64 + 32 + return prefix1.inet6.ip.u64[0] == prefix2.inet6.ip.u64[0] && + prefix1.inet6.ip.u32[2] == prefix2.inet6.ip.u32[2]; +} + +void NetworkConnectivityService::PerformChecks() { + mDNSv4 = UNKNOWN; + mDNSv6 = UNKNOWN; + + mIPv4 = UNKNOWN; + mIPv6 = UNKNOWN; + + mNAT64 = UNKNOWN; + + { + MutexAutoLock lock(mLock); + mNAT64Prefixes.Clear(); + + // NAT64 checks might be disabled. + // Since We can't guarantee a DNS response, we should set up + // NAT64 manually now if needed. + + NetAddr priorityPrefix{}; + bool havePrefix = NAT64PrefixFromPref(&priorityPrefix); + if (havePrefix) { + mNAT64Prefixes.AppendElement(priorityPrefix); + mNAT64 = OK; + } + } + + RecheckDNS(); + RecheckIPConnectivity(); +} + +static inline void NotifyObservers(const char* aTopic) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + obs->NotifyObservers(nullptr, aTopic, nullptr); +} + +void NetworkConnectivityService::SaveNAT64Prefixes(nsIDNSRecord* aRecord) { + nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord); + MutexAutoLock lock(mLock); + mNAT64Prefixes.Clear(); + + NetAddr priorityPrefix{}; + bool havePrefix = NAT64PrefixFromPref(&priorityPrefix); + if (havePrefix) { + mNAT64 = OK; + mNAT64Prefixes.AppendElement(priorityPrefix); + } + + if (!rec) { + if (!havePrefix) { + mNAT64 = NOT_AVAILABLE; + } + return; + } + + mNAT64 = UNKNOWN; + NetAddr addr{}; + + // use port 80 as dummy value for NetAddr + while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) { + if (addr.raw.family != AF_INET6 || addr.IsIPAddrV4Mapped()) { + // These are not the kind of addresses we are looking for. + continue; + } + + // RFC 7050 does not require the embedded IPv4 to be + // at the end of IPv6. In practice, and as we assume, + // it is always at the end. + // The embedded IP must be 192.0.0.170 or 192.0.0.171 + + // Clear the last bit to compare with the next one. + addr.inet6.ip.u8[15] &= ~(uint32_t)1; + if ((addr.inet6.ip.u8[12] != 192) || (addr.inet6.ip.u8[13] != 0) || + (addr.inet6.ip.u8[14] != 0) || (addr.inet6.ip.u8[15] != 170)) { + continue; + } + + mNAT64Prefixes.AppendElement(addr); + } + + size_t length = mNAT64Prefixes.Length(); + if (length == 0) { + mNAT64 = NOT_AVAILABLE; + return; + } + + // Remove duplicates. Typically a DNS64 resolver sends every + // prefix twice with address with different last bits. We want + // a list of unique prefixes while reordering is not allowed. + // We must not handle the case with an element in-between + // two identical ones, which is never the case for a properly + // configured DNS64 resolver. + + NetAddr prev = mNAT64Prefixes[0]; + + for (size_t i = 1; i < length; i++) { + if (NAT64PrefixCompare(prev, mNAT64Prefixes[i])) { + mNAT64Prefixes.RemoveElementAt(i); + i--; + length--; + } else { + prev = mNAT64Prefixes[i]; + } + } + + // The prioritized address might also appear in the record we received. + + if (havePrefix) { + for (size_t i = 1; i < length; i++) { + if (NAT64PrefixCompare(priorityPrefix, mNAT64Prefixes[i])) { + mNAT64Prefixes.RemoveElementAt(i); + // It wouldn't appear more than once. + break; + } + } + } + + mNAT64 = OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::OnLookupComplete(nsICancelable* aRequest, + nsIDNSRecord* aRecord, + nsresult aStatus) { + ConnectivityState state = NS_SUCCEEDED(aStatus) ? OK : NOT_AVAILABLE; + + if (aRequest == mDNSv4Request) { + mDNSv4 = state; + mDNSv4Request = nullptr; + } else if (aRequest == mDNSv6Request) { + mDNSv6 = state; + mDNSv6Request = nullptr; + } else if (aRequest == mNAT64Request) { + mNAT64Request = nullptr; + SaveNAT64Prefixes(aRecord); + } + + if (!mDNSv4Request && !mDNSv6Request && !mNAT64Request) { + NotifyObservers("network:connectivity-service:dns-checks-complete"); + } + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::RecheckDNS() { + bool enabled = + Preferences::GetBool("network.connectivity-service.enabled", false); + if (!enabled) { + return NS_OK; + } + + if (nsIOService::UseSocketProcess()) { + SocketProcessParent* parent = SocketProcessParent::GetSingleton(); + if (parent) { + Unused << parent->SendRecheckDNS(); + } + } + + nsresult rv; + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + OriginAttributes attrs; + nsAutoCString host; + Preferences::GetCString("network.connectivity-service.DNSv4.domain", host); + + rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DISABLE_IPV6 | + nsIDNSService::RESOLVE_TRR_DISABLED_MODE, + nullptr, this, NS_GetCurrentThread(), attrs, + getter_AddRefs(mDNSv4Request)); + NS_ENSURE_SUCCESS(rv, rv); + + Preferences::GetCString("network.connectivity-service.DNSv6.domain", host); + rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DISABLE_IPV4 | + nsIDNSService::RESOLVE_TRR_DISABLED_MODE, + nullptr, this, NS_GetCurrentThread(), attrs, + getter_AddRefs(mDNSv6Request)); + NS_ENSURE_SUCCESS(rv, rv); + + if (StaticPrefs::network_connectivity_service_nat64_check()) { + rv = dns->AsyncResolveNative("ipv4only.arpa"_ns, + nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DISABLE_IPV4 | + nsIDNSService::RESOLVE_TRR_DISABLED_MODE, + nullptr, this, NS_GetCurrentThread(), attrs, + getter_AddRefs(mNAT64Request)); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; +} + +NS_IMETHODIMP +NetworkConnectivityService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "network:captive-portal-connectivity")) { + // Captive portal is cleared, so we redo the checks. + PerformChecks(); + } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + if (mDNSv4Request) { + mDNSv4Request->Cancel(NS_ERROR_ABORT); + mDNSv4Request = nullptr; + } + if (mDNSv6Request) { + mDNSv6Request->Cancel(NS_ERROR_ABORT); + mDNSv6Request = nullptr; + } + if (mNAT64Request) { + mNAT64Request->Cancel(NS_ERROR_ABORT); + mNAT64Request = nullptr; + } + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + observerService->RemoveObserver(this, + "network:captive-portal-connectivity"); + observerService->RemoveObserver(this, NS_NETWORK_LINK_TOPIC); + } else if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC) && + !NS_LITERAL_STRING_FROM_CSTRING(NS_NETWORK_LINK_DATA_UNKNOWN) + .Equals(aData)) { + PerformChecks(); + } + + return NS_OK; +} + +already_AddRefed<nsIChannel> NetworkConnectivityService::SetupIPCheckChannel( + bool ipv4) { + nsresult rv; + nsAutoCString url; + + if (ipv4) { + rv = Preferences::GetCString("network.connectivity-service.IPv4.url", url); + } else { + rv = Preferences::GetCString("network.connectivity-service.IPv6.url", url); + } + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), url); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIChannel> channel; + if (XRE_IsSocketProcess()) { + rv = DNSUtils::CreateChannelHelper(uri, getter_AddRefs(channel)); + if (NS_FAILED(rv)) { + return nullptr; + } + channel->SetLoadFlags( + nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache + nsIRequest::INHIBIT_CACHING | // don't write the response to cache + nsIRequest::LOAD_ANONYMOUS); + } else { + rv = NS_NewChannel( + getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // nsICookieJarSettings + nullptr, // aPerformanceStorage + nullptr, // aLoadGroup + nullptr, + nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache + nsIRequest::INHIBIT_CACHING | // don't write the response to cache + nsIRequest::LOAD_ANONYMOUS); // prevent privacy leaks + NS_ENSURE_SUCCESS(rv, nullptr); + + { + // Prevent HTTPS-Only Mode from upgrading the OCSP request. + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); + httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT; + loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); + + // allow deprecated HTTP request from SystemPrincipal + loadInfo->SetAllowDeprecatedSystemRequests(true); + } + } + + rv = channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(channel); + NS_ENSURE_TRUE(internalChan, nullptr); + + if (ipv4) { + internalChan->SetIPv6Disabled(); + } else { + internalChan->SetIPv4Disabled(); + } + + return channel.forget(); +} + +NS_IMETHODIMP +NetworkConnectivityService::RecheckIPConnectivity() { + bool enabled = + Preferences::GetBool("network.connectivity-service.enabled", false); + if (!enabled) { + return NS_OK; + } + + if (nsIOService::UseSocketProcess()) { + SocketProcessParent* parent = SocketProcessParent::GetSingleton(); + if (parent) { + Unused << parent->SendRecheckIPConnectivity(); + } + } + + if (xpc::AreNonLocalConnectionsDisabled() && + !Preferences::GetBool("network.captive-portal-service.testMode", false)) { + return NS_OK; + } + + if (mIPv4Channel) { + mIPv4Channel->Cancel(NS_ERROR_ABORT); + mIPv4Channel = nullptr; + } + if (mIPv6Channel) { + mIPv6Channel->Cancel(NS_ERROR_ABORT); + mIPv6Channel = nullptr; + } + + nsresult rv; + mHasNetworkId = false; + mCheckedNetworkId = false; + mIPv4Channel = SetupIPCheckChannel(/* ipv4 = */ true); + if (mIPv4Channel) { + rv = mIPv4Channel->AsyncOpen(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + mIPv6Channel = SetupIPCheckChannel(/* ipv4 = */ false); + if (mIPv6Channel) { + rv = mIPv6Channel->AsyncOpen(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::OnStartRequest(nsIRequest* aRequest) { + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + if (aStatusCode == NS_ERROR_ABORT) { + return NS_OK; + } + + ConnectivityState status = NS_FAILED(aStatusCode) ? NOT_AVAILABLE : OK; + + if (aRequest == mIPv4Channel) { + mIPv4 = status; + mIPv4Channel = nullptr; + + if (mIPv4 == nsINetworkConnectivityService::OK) { + Telemetry::AccumulateCategorical( + mHasNetworkId ? Telemetry::LABELS_NETWORK_ID_ONLINE::present + : Telemetry::LABELS_NETWORK_ID_ONLINE::absent); + LOG(("mHasNetworkId : %d\n", mHasNetworkId)); + } + } else if (aRequest == mIPv6Channel) { + mIPv6 = status; + mIPv6Channel = nullptr; + } + + if (!mIPv6Channel && !mIPv4Channel) { + NotifyObservers("network:connectivity-service:ip-checks-complete"); + } + + return NS_OK; +} + +NS_IMETHODIMP +NetworkConnectivityService::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) { + nsAutoCString data; + + // We perform this check here, instead of doing it in OnStopRequest in case + // a network down event occurs after the data has arrived but before we fire + // OnStopRequest. That would cause us to report a missing networkID, even + // though it was not empty while receiving data. + if (aRequest == mIPv4Channel && !mCheckedNetworkId) { + nsCOMPtr<nsINetworkLinkService> nls = + do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID); + nsAutoCString networkId; + if (nls) { + nls->GetNetworkID(networkId); + } + mHasNetworkId = !networkId.IsEmpty(); + mCheckedNetworkId = true; + } + + Unused << NS_ReadInputStreamToString(aInputStream, data, aCount); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/NetworkConnectivityService.h b/netwerk/base/NetworkConnectivityService.h new file mode 100644 index 0000000000..6315fb192b --- /dev/null +++ b/netwerk/base/NetworkConnectivityService.h @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NetworkConnectivityService_h_ +#define NetworkConnectivityService_h_ + +#include "nsINetworkConnectivityService.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsIDNSListener.h" +#include "nsIStreamListener.h" +#include "mozilla/net/DNS.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +class NetworkConnectivityService : public nsINetworkConnectivityService, + public nsIObserver, + public nsIDNSListener, + public nsIStreamListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSINETWORKCONNECTIVITYSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSIDNSLISTENER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + already_AddRefed<AddrInfo> MapNAT64IPs(AddrInfo* aNewRRSet); + + static already_AddRefed<NetworkConnectivityService> GetSingleton(); + + private: + NetworkConnectivityService(); + virtual ~NetworkConnectivityService() = default; + + nsresult Init(); + // Calls all the check methods + void PerformChecks(); + + void SaveNAT64Prefixes(nsIDNSRecord* aRecord); + + already_AddRefed<nsIChannel> SetupIPCheckChannel(bool ipv4); + + // Will be set to OK if the DNS request returned in IP of this type, + // NOT_AVAILABLE if that type of resolution is not available + // UNKNOWN if the check wasn't performed + Atomic<ConnectivityState, Relaxed> mDNSv4; + Atomic<ConnectivityState, Relaxed> mDNSv6; + + Atomic<ConnectivityState, Relaxed> mIPv4; + Atomic<ConnectivityState, Relaxed> mIPv6; + + Atomic<ConnectivityState, Relaxed> mNAT64; + + nsTArray<NetAddr> mNAT64Prefixes; + + nsCOMPtr<nsICancelable> mDNSv4Request; + nsCOMPtr<nsICancelable> mDNSv6Request; + nsCOMPtr<nsICancelable> mNAT64Request; + + nsCOMPtr<nsIChannel> mIPv4Channel; + nsCOMPtr<nsIChannel> mIPv6Channel; + + bool mCheckedNetworkId = false; + bool mHasNetworkId = false; + + Mutex mLock MOZ_UNANNOTATED; +}; + +} // namespace net +} // namespace mozilla + +#endif // NetworkConnectivityService_h_ diff --git a/netwerk/base/NetworkDataCountLayer.cpp b/netwerk/base/NetworkDataCountLayer.cpp new file mode 100644 index 0000000000..bee8bd1ee5 --- /dev/null +++ b/netwerk/base/NetworkDataCountLayer.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NetworkDataCountLayer.h" +#include "nsSocketTransportService2.h" +#include "prmem.h" +#include "prio.h" + +namespace mozilla { +namespace net { + +static PRDescIdentity sNetworkDataCountLayerIdentity; +static PRIOMethods sNetworkDataCountLayerMethods; +static PRIOMethods* sNetworkDataCountLayerMethodsPtr = nullptr; + +class NetworkDataCountSecret { + public: + NetworkDataCountSecret() = default; + + uint64_t mSentBytes = 0; + uint64_t mReceivedBytes = 0; +}; + +static PRInt32 NetworkDataCountSend(PRFileDesc* fd, const void* buf, + PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sNetworkDataCountLayerIdentity); + + NetworkDataCountSecret* secret = + reinterpret_cast<NetworkDataCountSecret*>(fd->secret); + + PRInt32 rv = + (fd->lower->methods->send)(fd->lower, buf, amount, flags, timeout); + if (rv > 0) { + secret->mSentBytes += rv; + } + return rv; +} + +static PRInt32 NetworkDataCountWrite(PRFileDesc* fd, const void* buf, + PRInt32 amount) { + return NetworkDataCountSend(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT); +} + +static PRInt32 NetworkDataCountRecv(PRFileDesc* fd, void* buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout) { + MOZ_RELEASE_ASSERT(fd->identity == sNetworkDataCountLayerIdentity); + + NetworkDataCountSecret* secret = + reinterpret_cast<NetworkDataCountSecret*>(fd->secret); + + PRInt32 rv = + (fd->lower->methods->recv)(fd->lower, buf, amount, flags, timeout); + if (rv > 0) { + secret->mReceivedBytes += rv; + } + return rv; +} + +static PRInt32 NetworkDataCountRead(PRFileDesc* fd, void* buf, PRInt32 amount) { + return NetworkDataCountRecv(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT); +} + +static PRStatus NetworkDataCountClose(PRFileDesc* fd) { + if (!fd) { + return PR_FAILURE; + } + + PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER); + + MOZ_RELEASE_ASSERT(layer && layer->identity == sNetworkDataCountLayerIdentity, + "NetworkDataCount Layer not on top of stack"); + + NetworkDataCountSecret* secret = + reinterpret_cast<NetworkDataCountSecret*>(layer->secret); + layer->secret = nullptr; + layer->dtor(layer); + delete secret; + return fd->methods->close(fd); +} + +nsresult AttachNetworkDataCountLayer(PRFileDesc* fd) { + if (!sNetworkDataCountLayerMethodsPtr) { + sNetworkDataCountLayerIdentity = + PR_GetUniqueIdentity("NetworkDataCount Layer"); + sNetworkDataCountLayerMethods = *PR_GetDefaultIOMethods(); + sNetworkDataCountLayerMethods.send = NetworkDataCountSend; + sNetworkDataCountLayerMethods.write = NetworkDataCountWrite; + sNetworkDataCountLayerMethods.recv = NetworkDataCountRecv; + sNetworkDataCountLayerMethods.read = NetworkDataCountRead; + sNetworkDataCountLayerMethods.close = NetworkDataCountClose; + sNetworkDataCountLayerMethodsPtr = &sNetworkDataCountLayerMethods; + } + + PRFileDesc* layer = PR_CreateIOLayerStub(sNetworkDataCountLayerIdentity, + sNetworkDataCountLayerMethodsPtr); + + if (!layer) { + return NS_ERROR_FAILURE; + } + + NetworkDataCountSecret* secret = new NetworkDataCountSecret(); + + layer->secret = reinterpret_cast<PRFilePrivate*>(secret); + + PRStatus status = PR_PushIOLayer(fd, PR_NSPR_IO_LAYER, layer); + + if (status == PR_FAILURE) { + delete secret; + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void NetworkDataCountSent(PRFileDesc* fd, uint64_t& sentBytes) { + PRFileDesc* ndcFd = PR_GetIdentitiesLayer(fd, sNetworkDataCountLayerIdentity); + MOZ_RELEASE_ASSERT(ndcFd); + + NetworkDataCountSecret* secret = + reinterpret_cast<NetworkDataCountSecret*>(ndcFd->secret); + sentBytes = secret->mSentBytes; +} + +void NetworkDataCountReceived(PRFileDesc* fd, uint64_t& receivedBytes) { + PRFileDesc* ndcFd = PR_GetIdentitiesLayer(fd, sNetworkDataCountLayerIdentity); + MOZ_RELEASE_ASSERT(ndcFd); + + NetworkDataCountSecret* secret = + reinterpret_cast<NetworkDataCountSecret*>(ndcFd->secret); + receivedBytes = secret->mReceivedBytes; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/NetworkDataCountLayer.h b/netwerk/base/NetworkDataCountLayer.h new file mode 100644 index 0000000000..a5a0d8f0dc --- /dev/null +++ b/netwerk/base/NetworkDataCountLayer.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NetworkDataCountLayer_h__ +#define NetworkDataCountLayer_h__ + +#include "prerror.h" +#include "prio.h" +#include "ErrorList.h" + +namespace mozilla { +namespace net { + +nsresult AttachNetworkDataCountLayer(PRFileDesc* fd); + +// Get amount of sent bytes. +void NetworkDataCountSent(PRFileDesc* fd, uint64_t& sentBytes); + +// Get amount of received bytes. +void NetworkDataCountReceived(PRFileDesc* fd, uint64_t& receivedBytes); + +} // namespace net +} // namespace mozilla + +#endif // NetworkDataCountLayer_h__ diff --git a/netwerk/base/NetworkInfoServiceImpl.h b/netwerk/base/NetworkInfoServiceImpl.h new file mode 100644 index 0000000000..218f31cb0c --- /dev/null +++ b/netwerk/base/NetworkInfoServiceImpl.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "nsTHashMap.h" + +namespace mozilla { +namespace net { + +using AddrMapType = nsTHashMap<nsCStringHashKey, nsCString>; + +nsresult DoListAddresses(AddrMapType& aAddrMap); + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/PollableEvent.cpp b/netwerk/base/PollableEvent.cpp new file mode 100644 index 0000000000..a99d6d88f5 --- /dev/null +++ b/netwerk/base/PollableEvent.cpp @@ -0,0 +1,399 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSocketTransportService2.h" +#include "PollableEvent.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "mozilla/net/DNS.h" +#include "prerror.h" +#include "prio.h" +#include "private/pprio.h" +#include "prnetdb.h" + +#ifdef XP_WIN +# include "ShutdownLayer.h" +#else +# include <fcntl.h> +# define USEPIPE 1 +#endif + +namespace mozilla { +namespace net { + +#ifndef USEPIPE +static PRDescIdentity sPollableEventLayerIdentity; +static PRIOMethods sPollableEventLayerMethods; +static PRIOMethods* sPollableEventLayerMethodsPtr = nullptr; + +static void LazyInitSocket() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (sPollableEventLayerMethodsPtr) { + return; + } + sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer"); + sPollableEventLayerMethods = *PR_GetDefaultIOMethods(); + sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods; +} + +static bool NewTCPSocketPair(PRFileDesc* fd[], bool aSetRecvBuff) { + // this is a replacement for PR_NewTCPSocketPair that manually + // sets the recv buffer to 64K. A windows bug (1248358) + // can result in using an incompatible rwin and window + // scale option on localhost pipes if not set before connect. + + SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n", + aSetRecvBuff ? "with" : "without")); + + PRFileDesc* listener = nullptr; + PRFileDesc* writer = nullptr; + PRFileDesc* reader = nullptr; + PRSocketOptionData recvBufferOpt; + recvBufferOpt.option = PR_SockOpt_RecvBufferSize; + recvBufferOpt.value.recv_buffer_size = 65535; + + PRSocketOptionData nodelayOpt; + nodelayOpt.option = PR_SockOpt_NoDelay; + nodelayOpt.value.no_delay = true; + + PRSocketOptionData noblockOpt; + noblockOpt.option = PR_SockOpt_Nonblocking; + noblockOpt.value.non_blocking = true; + + listener = PR_OpenTCPSocket(PR_AF_INET); + if (!listener) { + goto failed; + } + + if (aSetRecvBuff) { + PR_SetSocketOption(listener, &recvBufferOpt); + } + PR_SetSocketOption(listener, &nodelayOpt); + + PRNetAddr listenAddr; + memset(&listenAddr, 0, sizeof(listenAddr)); + if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) || + (PR_Bind(listener, &listenAddr) == PR_FAILURE) || + (PR_GetSockName(listener, &listenAddr) == + PR_FAILURE) || // learn the dynamic port + (PR_Listen(listener, 5) == PR_FAILURE)) { + goto failed; + } + + writer = PR_OpenTCPSocket(PR_AF_INET); + if (!writer) { + goto failed; + } + if (aSetRecvBuff) { + PR_SetSocketOption(writer, &recvBufferOpt); + } + PR_SetSocketOption(writer, &nodelayOpt); + PR_SetSocketOption(writer, &noblockOpt); + PRNetAddr writerAddr; + if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port), + &writerAddr) == PR_FAILURE) { + goto failed; + } + + if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) { + if ((PR_GetError() != PR_IN_PROGRESS_ERROR) || + (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) { + goto failed; + } + } + PR_SetFDInheritable(writer, false); + + reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200)); + if (!reader) { + goto failed; + } + PR_SetFDInheritable(reader, false); + if (aSetRecvBuff) { + PR_SetSocketOption(reader, &recvBufferOpt); + } + PR_SetSocketOption(reader, &nodelayOpt); + PR_SetSocketOption(reader, &noblockOpt); + PR_Close(listener); + + fd[0] = reader; + fd[1] = writer; + return true; + +failed: + if (listener) { + PR_Close(listener); + } + if (reader) { + PR_Close(reader); + } + if (writer) { + PR_Close(writer); + } + return false; +} + +#endif + +PollableEvent::PollableEvent() + +{ + MOZ_COUNT_CTOR(PollableEvent); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + // create pair of prfiledesc that can be used as a poll()ble + // signal. on windows use a localhost socket pair, and on + // unix use a pipe. +#ifdef USEPIPE + SOCKET_LOG(("PollableEvent() using pipe\n")); + if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) { + // make the pipe non blocking. NSPR asserts at + // trying to use SockOpt here + PROsfd fd = PR_FileDesc2NativeHandle(mReadFD); + int flags = fcntl(fd, F_GETFL, 0); + (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); + fd = PR_FileDesc2NativeHandle(mWriteFD); + flags = fcntl(fd, F_GETFL, 0); + (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } else { + mReadFD = nullptr; + mWriteFD = nullptr; + SOCKET_LOG(("PollableEvent() pipe failed\n")); + } +#else + SOCKET_LOG(("PollableEvent() using socket pair\n")); + PRFileDesc* fd[2]; + LazyInitSocket(); + + // Try with a increased recv buffer first (bug 1248358). + if (NewTCPSocketPair(fd, true)) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + // If the previous fails try without recv buffer increase (bug 1305436). + } else if (NewTCPSocketPair(fd, false)) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + // If both fail, try the old version. + } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) { + mReadFD = fd[0]; + mWriteFD = fd[1]; + + PRSocketOptionData socket_opt; + DebugOnly<PRStatus> status; + socket_opt.option = PR_SockOpt_NoDelay; + socket_opt.value.no_delay = true; + PR_SetSocketOption(mWriteFD, &socket_opt); + PR_SetSocketOption(mReadFD, &socket_opt); + socket_opt.option = PR_SockOpt_Nonblocking; + socket_opt.value.non_blocking = true; + status = PR_SetSocketOption(mWriteFD, &socket_opt); + MOZ_ASSERT(status == PR_SUCCESS); + status = PR_SetSocketOption(mReadFD, &socket_opt); + MOZ_ASSERT(status == PR_SUCCESS); + } + + if (mReadFD && mWriteFD) { + // compatibility with LSPs such as McAfee that assume a NSPR + // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a + // nop. + PRFileDesc* topLayer = PR_CreateIOLayerStub(sPollableEventLayerIdentity, + sPollableEventLayerMethodsPtr); + if (topLayer) { + if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) { + topLayer->dtor(topLayer); + } else { + SOCKET_LOG(("PollableEvent() nspr layer ok\n")); + mReadFD = topLayer; + } + } + + } else { + SOCKET_LOG(("PollableEvent() socketpair failed\n")); + } +#endif + + if (mReadFD && mWriteFD) { + // prime the system to deal with races invovled in [dc]tor cycle + SOCKET_LOG(("PollableEvent() ctor ok\n")); + mSignaled = true; + MarkFirstSignalTimestamp(); + PR_Write(mWriteFD, "I", 1); + } +} + +PollableEvent::~PollableEvent() { + MOZ_COUNT_DTOR(PollableEvent); + if (mWriteFD) { +#if defined(XP_WIN) + AttachShutdownLayer(mWriteFD); +#endif + PR_Close(mWriteFD); + } + if (mReadFD) { +#if defined(XP_WIN) + AttachShutdownLayer(mReadFD); +#endif + PR_Close(mReadFD); + } +} + +// we do not record signals on the socket thread +// because the socket thread can reliably look at its +// own runnable queue before selecting a poll time +// this is the "service the network without blocking" comment in +// nsSocketTransportService2.cpp +bool PollableEvent::Signal() { + SOCKET_LOG(("PollableEvent::Signal\n")); + + if (!mWriteFD) { + SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n")); + return false; + } +#ifndef XP_WIN + // On windows poll can hang and this became worse when we introduced the + // patch for bug 698882 (see also bug 1292181), therefore we reverted the + // behavior on windows to be as before bug 698882, e.g. write to the socket + // also if an event dispatch is on the socket thread and writing to the + // socket for each event. See bug 1292181. + if (OnSocketThread()) { + SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n")); + return true; + } +#endif + +#ifndef XP_WIN + // To wake up the poll writing once is enough, but for Windows that can cause + // hangs so we will write for every event. + // For non-Windows systems it is enough to write just once. + if (mSignaled) { + return true; + } +#endif + + if (!mSignaled) { + mSignaled = true; + MarkFirstSignalTimestamp(); + } + + int32_t status = PR_Write(mWriteFD, "M", 1); + SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status)); + if (status != 1) { + NS_WARNING("PollableEvent::Signal Failed\n"); + SOCKET_LOG(("PollableEvent::Signal Failed\n")); + mSignaled = false; + mWriteFailed = true; + } else { + mWriteFailed = false; + } + return (status == 1); +} + +bool PollableEvent::Clear() { + // necessary because of the "dont signal on socket thread" optimization + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + SOCKET_LOG(("PollableEvent::Clear\n")); + + if (!mFirstSignalAfterClear.IsNull()) { + SOCKET_LOG(("PollableEvent::Clear time to signal %ums", + (uint32_t)(TimeStamp::NowLoRes() - mFirstSignalAfterClear) + .ToMilliseconds())); + } + + mFirstSignalAfterClear = TimeStamp(); + mSignalTimestampAdjusted = false; + mSignaled = false; + + if (!mReadFD) { + SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n")); + return false; + } + + char buf[2048]; + int32_t status; +#ifdef XP_WIN + // On Windows we are writing to the socket for each event, to be sure that we + // do not have any deadlock read from the socket as much as we can. + while (true) { + status = PR_Read(mReadFD, buf, 2048); + SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status)); + if (status == 0) { + SOCKET_LOG(("PollableEvent::Clear EOF!\n")); + return false; + } + if (status < 0) { + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) { + return true; + } else { + SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code)); + return false; + } + } + } +#else + status = PR_Read(mReadFD, buf, 2048); + SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status)); + + if (status == 1) { + return true; + } + if (status == 0) { + SOCKET_LOG(("PollableEvent::Clear EOF!\n")); + return false; + } + if (status > 1) { + MOZ_ASSERT(false); + SOCKET_LOG(("PollableEvent::Clear Unexpected events\n")); + Clear(); + return true; + } + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) { + return true; + } + SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code)); + return false; +#endif // XP_WIN +} + +void PollableEvent::MarkFirstSignalTimestamp() { + if (mFirstSignalAfterClear.IsNull()) { + SOCKET_LOG(("PollableEvent::MarkFirstSignalTimestamp")); + mFirstSignalAfterClear = TimeStamp::NowLoRes(); + } +} + +void PollableEvent::AdjustFirstSignalTimestamp() { + if (!mSignalTimestampAdjusted && !mFirstSignalAfterClear.IsNull()) { + SOCKET_LOG(("PollableEvent::AdjustFirstSignalTimestamp")); + mFirstSignalAfterClear = TimeStamp::NowLoRes(); + mSignalTimestampAdjusted = true; + } +} + +bool PollableEvent::IsSignallingAlive(TimeDuration const& timeout) { + if (mWriteFailed) { + return false; + } + +#ifdef DEBUG + // The timeout would be just a disturbance in a debug build. + return true; +#else + if (!mSignaled || mFirstSignalAfterClear.IsNull() || + timeout == TimeDuration()) { + return true; + } + + TimeDuration delay = (TimeStamp::NowLoRes() - mFirstSignalAfterClear); + bool timedOut = delay > timeout; + + return !timedOut; +#endif // DEBUG +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/PollableEvent.h b/netwerk/base/PollableEvent.h new file mode 100644 index 0000000000..6a17cd909d --- /dev/null +++ b/netwerk/base/PollableEvent.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef PollableEvent_h__ +#define PollableEvent_h__ + +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" + +struct PRFileDesc; + +namespace mozilla { +namespace net { + +// class must be called locked +class PollableEvent { + public: + PollableEvent(); + ~PollableEvent(); + + // Signal/Clear return false only if they fail + bool Signal(); + // This is called only when we get non-null out_flags for the socket pair + bool Clear(); + bool Valid() { return mWriteFD && mReadFD; } + + // We want to detect if writing to one of the socket pair sockets takes + // too long to be received by the other socket from the pair. + // Hence, we remember the timestamp of the earliest write by a call to + // MarkFirstSignalTimestamp() from Signal(). After waking up from poll() + // we check how long it took get the 'readable' signal on the socket pair. + void MarkFirstSignalTimestamp(); + // Called right before we enter poll() to exclude any possible delay between + // the earlist call to Signal() and entering poll() caused by processing + // of events dispatched to the socket transport thread. + void AdjustFirstSignalTimestamp(); + // This returns false on following conditions: + // - PR_Write has failed + // - no out_flags were signalled on the socket pair for too long after + // the earliest Signal() + bool IsSignallingAlive(TimeDuration const& timeout); + + PRFileDesc* PollableFD() { return mReadFD; } + + private: + PRFileDesc* mWriteFD{nullptr}; + PRFileDesc* mReadFD{nullptr}; + bool mSignaled{false}; + // true when PR_Write to the socket pair has failed (status < 1) + bool mWriteFailed{false}; + // Set true after AdjustFirstSignalTimestamp() was called + // Set false after Clear() was called + // Ensures shifting the timestamp before entering poll() only once + // between Clear()'ings. + bool mSignalTimestampAdjusted{false}; + // Timestamp of the first call to Signal() (or time we enter poll()) + // that happened after the last Clear() call + TimeStamp mFirstSignalAfterClear; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp new file mode 100644 index 0000000000..de19d0e06e --- /dev/null +++ b/netwerk/base/Predictor.cpp @@ -0,0 +1,2438 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> + +#include "Predictor.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsICacheStorage.h" +#include "nsICachingChannel.h" +#include "nsICancelable.h" +#include "nsIChannel.h" +#include "nsContentUtils.h" +#include "nsIDNSService.h" +#include "mozilla/dom/Document.h" +#include "nsIFile.h" +#include "nsIHttpChannel.h" +#include "nsIInputStream.h" +#include "nsILoadContext.h" +#include "nsILoadContextInfo.h" +#include "nsILoadGroup.h" +#include "nsINetworkPredictorVerifier.h" +#include "nsIObserverService.h" +#include "nsISpeculativeConnect.h" +#include "nsITimer.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "mozilla/Logging.h" + +#include "mozilla/OriginAttributes.h" +#include "mozilla/Preferences.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Telemetry.h" + +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/net/NeckoParent.h" + +#include "LoadContextInfo.h" +#include "mozilla/ipc/URIUtils.h" +#include "SerializedLoadContext.h" +#include "mozilla/net/NeckoChild.h" + +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ClearOnShutdown.h" + +#include "CacheControlParser.h" +#include "ReferrerInfo.h" + +using namespace mozilla; + +namespace mozilla { +namespace net { + +Predictor* Predictor::sSelf = nullptr; + +static LazyLogModule gPredictorLog("NetworkPredictor"); + +#define PREDICTOR_LOG(args) \ + MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args) + +#define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC) + +// All these time values are in sec +static const uint32_t ONE_DAY = 86400U; +static const uint32_t ONE_WEEK = 7U * ONE_DAY; +static const uint32_t ONE_MONTH = 30U * ONE_DAY; +static const uint32_t ONE_YEAR = 365U * ONE_DAY; + +// Version of metadata entries we expect +static const uint32_t METADATA_VERSION = 1; + +// Flags available in entries +// FLAG_PREFETCHABLE - we have determined that this item is eligible for +// prefetch +static const uint32_t FLAG_PREFETCHABLE = 1 << 0; + +// We save 12 bits in the "flags" section of our metadata for actual flags, the +// rest are to keep track of a rolling count of which loads a resource has been +// used on to determine if we can prefetch that resource or not; +static const uint8_t kRollingLoadOffset = 12; +static const int32_t kMaxPrefetchRollingLoadCount = 20; +static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1); + +// ID Extensions for cache entries +#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin" + +// Get the full origin (scheme, host, port) out of a URI (maybe should be part +// of nsIURI instead?) +static nsresult ExtractOrigin(nsIURI* uri, nsIURI** originUri) { + nsAutoCString s; + nsresult rv = nsContentUtils::GetWebExposedOriginSerialization(uri, s); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewURI(originUri, s); +} + +// All URIs we get passed *must* be http or https if they're not null. This +// helps ensure that. +static bool IsNullOrHttp(nsIURI* uri) { + if (!uri) { + return true; + } + + return uri->SchemeIs("http") || uri->SchemeIs("https"); +} + +// Listener for the speculative DNS requests we'll fire off, which just ignores +// the result (since we're just trying to warm the cache). This also exists to +// reduce round-trips to the main thread, by being something threadsafe the +// Predictor can use. + +NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener); + +NS_IMETHODIMP +Predictor::DNSListener::OnLookupComplete(nsICancelable* request, + nsIDNSRecord* rec, nsresult status) { + return NS_OK; +} + +// Class to proxy important information from the initial predictor call through +// the cache API and back into the internals of the predictor. We can't use the +// predictor itself, as it may have multiple actions in-flight, and each action +// has different parameters. +NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback); + +Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason, + nsIURI* targetURI, nsIURI* sourceURI, + nsINetworkPredictorVerifier* verifier, + Predictor* predictor) + : mFullUri(fullUri), + mPredict(predict), + mTargetURI(targetURI), + mSourceURI(sourceURI), + mVerifier(verifier), + mStackCount(0), + mPredictor(predictor) { + mStartTime = TimeStamp::Now(); + if (mPredict) { + mPredictReason = reason.mPredict; + } else { + mLearnReason = reason.mLearn; + } +} + +Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason, + nsIURI* targetURI, nsIURI* sourceURI, + nsINetworkPredictorVerifier* verifier, + Predictor* predictor, uint8_t stackCount) + : mFullUri(fullUri), + mPredict(predict), + mTargetURI(targetURI), + mSourceURI(sourceURI), + mVerifier(verifier), + mStackCount(stackCount), + mPredictor(predictor) { + mStartTime = TimeStamp::Now(); + if (mPredict) { + mPredictReason = reason.mPredict; + } else { + mLearnReason = reason.mLearn; + } +} + +NS_IMETHODIMP +Predictor::Action::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) { + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Action::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew, + nsresult result) { + MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!"); + + nsAutoCString targetURI, sourceURI; + mTargetURI->GetAsciiSpec(targetURI); + if (mSourceURI) { + mSourceURI->GetAsciiSpec(sourceURI); + } + PREDICTOR_LOG( + ("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d " + "mPredictReason=%d mLearnReason=%d mTargetURI=%s " + "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08" PRIx32, + this, entry, mFullUri, mPredict, mPredictReason, mLearnReason, + targetURI.get(), sourceURI.get(), mStackCount, isNew, + static_cast<uint32_t>(result))); + if (NS_FAILED(result)) { + PREDICTOR_LOG( + ("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08" PRIX32 + "). Aborting.", + this, static_cast<uint32_t>(result))); + return NS_OK; + } + Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME, mStartTime); + if (mPredict) { + bool predicted = + mPredictor->PredictInternal(mPredictReason, entry, isNew, mFullUri, + mTargetURI, mVerifier, mStackCount); + Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREDICT_WORK_TIME, + mStartTime); + if (predicted) { + Telemetry::AccumulateTimeDelta( + Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime); + } else { + Telemetry::AccumulateTimeDelta( + Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime); + } + } else { + mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI, + mSourceURI); + Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_LEARN_WORK_TIME, + mStartTime); + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(Predictor, nsINetworkPredictor, nsIObserver, + nsISpeculativeConnectionOverrider, nsIInterfaceRequestor, + nsICacheEntryMetaDataVisitor, nsINetworkPredictorVerifier) + +Predictor::Predictor() + +{ + MOZ_ASSERT(!sSelf, "multiple Predictor instances!"); + sSelf = this; +} + +Predictor::~Predictor() { + if (mInitialized) Shutdown(); + + sSelf = nullptr; +} + +// Predictor::nsIObserver + +nsresult Predictor::InstallObserver() { + MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread"); + + nsresult rv = NS_OK; + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_NOT_AVAILABLE; + } + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +void Predictor::RemoveObserver() { + MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread"); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } +} + +NS_IMETHODIMP +Predictor::Observe(nsISupports* subject, const char* topic, + const char16_t* data_unicode) { + nsresult rv = NS_OK; + MOZ_ASSERT(NS_IsMainThread(), + "Predictor observing something off main thread!"); + + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) { + Shutdown(); + } + + return rv; +} + +// Predictor::nsISpeculativeConnectionOverrider + +NS_IMETHODIMP +Predictor::GetIgnoreIdle(bool* ignoreIdle) { + *ignoreIdle = true; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::GetParallelSpeculativeConnectLimit( + uint32_t* parallelSpeculativeConnectLimit) { + *parallelSpeculativeConnectLimit = 6; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::GetIsFromPredictor(bool* isFromPredictor) { + *isFromPredictor = true; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::GetAllow1918(bool* allow1918) { + *allow1918 = false; + return NS_OK; +} + +// Predictor::nsIInterfaceRequestor + +NS_IMETHODIMP +Predictor::GetInterface(const nsIID& iid, void** result) { + return QueryInterface(iid, result); +} + +// Predictor::nsICacheEntryMetaDataVisitor + +#define SEEN_META_DATA "predictor::seen" +#define RESOURCE_META_DATA "predictor::resource-count" +#define META_DATA_PREFIX "predictor::" + +static bool IsURIMetadataElement(const char* key) { + return StringBeginsWith(nsDependentCString(key), + nsLiteralCString(META_DATA_PREFIX)) && + !nsLiteralCString(SEEN_META_DATA).Equals(key) && + !nsLiteralCString(RESOURCE_META_DATA).Equals(key); +} + +nsresult Predictor::OnMetaDataElement(const char* asciiKey, + const char* asciiValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsURIMetadataElement(asciiKey)) { + // This isn't a bit of metadata we care about + return NS_OK; + } + + nsCString key, value; + key.AssignASCII(asciiKey); + value.AssignASCII(asciiValue); + mKeysToOperateOn.AppendElement(key); + mValuesToOperateOn.AppendElement(value); + + return NS_OK; +} + +// Predictor::nsINetworkPredictor + +nsresult Predictor::Init() { + MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild()); + + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "Predictor::Init called off the main thread!"); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + + rv = InstallObserver(); + NS_ENSURE_SUCCESS(rv, rv); + + mLastStartupTime = mStartupTime = NOW_IN_SECONDS(); + + if (!mDNSListener) { + mDNSListener = new DNSListener(); + } + + mCacheStorageService = + do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewURI(getter_AddRefs(mStartupURI), "predictor://startup"); + NS_ENSURE_SUCCESS(rv, rv); + + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mInitialized = true; + + return rv; +} + +namespace { +class PredictorLearnRunnable final : public Runnable { + public: + PredictorLearnRunnable(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, + const OriginAttributes& oa) + : Runnable("PredictorLearnRunnable"), + mTargetURI(targetURI), + mSourceURI(sourceURI), + mReason(reason), + mOA(oa) { + MOZ_DIAGNOSTIC_ASSERT(targetURI, "Must have a target URI"); + } + + ~PredictorLearnRunnable() = default; + + NS_IMETHOD Run() override { + if (!gNeckoChild) { + // This may have gone away between when this runnable was dispatched and + // when it actually runs, so let's be safe here, even though we asserted + // earlier. + PREDICTOR_LOG(("predictor::learn (async) gNeckoChild went away")); + return NS_OK; + } + + PREDICTOR_LOG(("predictor::learn (async) forwarding to parent")); + gNeckoChild->SendPredLearn(mTargetURI, mSourceURI, mReason, mOA); + + return NS_OK; + } + + private: + nsCOMPtr<nsIURI> mTargetURI; + nsCOMPtr<nsIURI> mSourceURI; + PredictorLearnReason mReason; + const OriginAttributes mOA; +}; + +} // namespace + +void Predictor::Shutdown() { + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!"); + return; + } + + RemoveObserver(); + + mInitialized = false; +} + +nsresult Predictor::Create(const nsIID& aIID, void** aResult) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + RefPtr<Predictor> svc = new Predictor(); + if (IsNeckoChild()) { + NeckoChild::InitNeckoChild(); + + // Child threads only need to be call into the public interface methods + // so we don't bother with initialization + return svc->QueryInterface(aIID, aResult); + } + + rv = svc->Init(); + if (NS_FAILED(rv)) { + PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop")); + } + + // We treat init failure the same as the service being disabled, since this + // is all an optimization anyway. No need to freak people out. That's why we + // gladly continue on QI'ing here. + rv = svc->QueryInterface(aIID, aResult); + + return rv; +} + +NS_IMETHODIMP +Predictor::Predict(nsIURI* targetURI, nsIURI* sourceURI, + PredictorPredictReason reason, + JS::Handle<JS::Value> originAttributes, + nsINetworkPredictorVerifier* verifier, JSContext* aCx) { + OriginAttributes attrs; + + if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + return PredictNative(targetURI, sourceURI, reason, attrs, verifier); +} + +// Called from the main thread to initiate predictive actions +NS_IMETHODIMP +Predictor::PredictNative(nsIURI* targetURI, nsIURI* sourceURI, + PredictorPredictReason reason, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread(), + "Predictor interface methods must be called on the main thread"); + + PREDICTOR_LOG(("Predictor::Predict")); + + if (IsNeckoChild()) { + MOZ_DIAGNOSTIC_ASSERT(gNeckoChild); + + PREDICTOR_LOG((" called on child process")); + // If two different threads are predicting concurently, this will be + // overwritten. Thankfully, we only use this in tests, which will + // overwrite mVerifier perhaps multiple times for each individual test; + // however, within each test, the multiple predict calls should have the + // same verifier. + if (verifier) { + PREDICTOR_LOG((" was given a verifier")); + mChildVerifier = verifier; + } + PREDICTOR_LOG((" forwarding to parent process")); + gNeckoChild->SendPredPredict(targetURI, sourceURI, reason, originAttributes, + verifier); + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!StaticPrefs::network_predictor_enabled()) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + if (originAttributes.mPrivateBrowsingId > 0) { + // Don't want to do anything in PB mode + PREDICTOR_LOG((" in PB mode")); + return NS_OK; + } + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + // Nothing we can do for non-HTTP[S] schemes + PREDICTOR_LOG((" got non-http[s] URI")); + return NS_OK; + } + + // Ensure we've been given the appropriate arguments for the kind of + // prediction we're being asked to do + nsCOMPtr<nsIURI> uriKey = targetURI; + nsCOMPtr<nsIURI> originKey; + switch (reason) { + case nsINetworkPredictor::PREDICT_LINK: + if (!targetURI || !sourceURI) { + PREDICTOR_LOG((" link invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + // Link hover is a special case where we can predict without hitting the + // db, so let's go ahead and fire off that prediction here. + PredictForLink(targetURI, sourceURI, originAttributes, verifier); + return NS_OK; + case nsINetworkPredictor::PREDICT_LOAD: + if (!targetURI || sourceURI) { + PREDICTOR_LOG((" load invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + break; + case nsINetworkPredictor::PREDICT_STARTUP: + if (targetURI || sourceURI) { + PREDICTOR_LOG((" startup invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + uriKey = mStartupURI; + originKey = mStartupURI; + break; + default: + PREDICTOR_LOG((" invalid reason")); + return NS_ERROR_INVALID_ARG; + } + + Predictor::Reason argReason{}; + argReason.mPredict = reason; + + // First we open the regular cache entry, to ensure we don't gum up the works + // waiting on the less-important predictor-only cache entry + RefPtr<Predictor::Action> uriAction = new Predictor::Action( + Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, argReason, + targetURI, nullptr, verifier, this); + nsAutoCString uriKeyStr; + uriKey->GetAsciiSpec(uriKeyStr); + PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr.get(), + reason, uriAction.get())); + + nsCOMPtr<nsICacheStorage> cacheDiskStorage; + + RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes); + + nsresult rv = mCacheStorageService->DiskCacheStorage( + lci, getter_AddRefs(cacheDiskStorage)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t openFlags = + nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED; + cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, openFlags, uriAction); + + // Now we do the origin-only (and therefore predictor-only) entry + nsCOMPtr<nsIURI> targetOrigin; + rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin)); + NS_ENSURE_SUCCESS(rv, rv); + if (!originKey) { + originKey = targetOrigin; + } + + RefPtr<Predictor::Action> originAction = new Predictor::Action( + Predictor::Action::IS_ORIGIN, Predictor::Action::DO_PREDICT, argReason, + targetOrigin, nullptr, verifier, this); + nsAutoCString originKeyStr; + originKey->GetAsciiSpec(originKeyStr); + PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p", + originKeyStr.get(), reason, originAction.get())); + openFlags = nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + cacheDiskStorage->AsyncOpenURI(originKey, + nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION), + openFlags, originAction); + + PREDICTOR_LOG((" predict returning")); + return NS_OK; +} + +bool Predictor::PredictInternal(PredictorPredictReason reason, + nsICacheEntry* entry, bool isNew, bool fullUri, + nsIURI* targetURI, + nsINetworkPredictorVerifier* verifier, + uint8_t stackCount) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictInternal")); + bool rv = false; + + nsCOMPtr<nsILoadContextInfo> lci; + entry->GetLoadContextInfo(getter_AddRefs(lci)); + + if (!lci) { + return rv; + } + + if (reason == nsINetworkPredictor::PREDICT_LOAD) { + MaybeLearnForStartup(targetURI, fullUri, *lci->OriginAttributesPtr()); + } + + if (isNew) { + // nothing else we can do here + PREDICTOR_LOG((" new entry")); + return rv; + } + + switch (reason) { + case nsINetworkPredictor::PREDICT_LOAD: + rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier); + break; + case nsINetworkPredictor::PREDICT_STARTUP: + rv = PredictForStartup(entry, fullUri, verifier); + break; + default: + PREDICTOR_LOG((" invalid reason")); + MOZ_ASSERT(false, "Got unexpected value for prediction reason"); + } + + return rv; +} + +void Predictor::PredictForLink(nsIURI* targetURI, nsIURI* sourceURI, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForLink")); + if (!mSpeculativeService) { + PREDICTOR_LOG((" missing speculative service")); + return; + } + + if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) { + if (sourceURI->SchemeIs("https")) { + // We don't want to predict from an HTTPS page, to avoid info leakage + PREDICTOR_LOG((" Not predicting for link hover - on an SSL page")); + return; + } + } + + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateContentPrincipal(targetURI, originAttributes); + + mSpeculativeService->SpeculativeConnect(targetURI, principal, nullptr, false); + if (verifier) { + PREDICTOR_LOG((" sending verification")); + verifier->OnPredictPreconnect(targetURI); + } +} + +// This is the driver for prediction based on a new pageload. +static const uint8_t MAX_PAGELOAD_DEPTH = 10; +bool Predictor::PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI, + uint8_t stackCount, bool fullUri, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForPageload")); + + if (stackCount > MAX_PAGELOAD_DEPTH) { + PREDICTOR_LOG((" exceeded recursion depth!")); + return false; + } + + uint32_t lastLoad; + nsresult rv = entry->GetLastFetched(&lastLoad); + NS_ENSURE_SUCCESS(rv, false); + + int32_t globalDegradation = CalculateGlobalDegradation(lastLoad); + PREDICTOR_LOG((" globalDegradation = %d", globalDegradation)); + + uint32_t loadCount; + rv = entry->GetFetchCount(&loadCount); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr<nsILoadContextInfo> lci; + + rv = entry->GetLoadContextInfo(getter_AddRefs(lci)); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr<nsIURI> redirectURI; + if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation, + getter_AddRefs(redirectURI))) { + mPreconnects.AppendElement(redirectURI); + Predictor::Reason reason{}; + reason.mPredict = nsINetworkPredictor::PREDICT_LOAD; + RefPtr<Predictor::Action> redirectAction = new Predictor::Action( + Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, reason, + redirectURI, nullptr, verifier, this, stackCount + 1); + nsAutoCString redirectUriString; + redirectURI->GetAsciiSpec(redirectUriString); + + nsCOMPtr<nsICacheStorage> cacheDiskStorage; + + rv = mCacheStorageService->DiskCacheStorage( + lci, getter_AddRefs(cacheDiskStorage)); + NS_ENSURE_SUCCESS(rv, false); + + PREDICTOR_LOG((" Predict redirect uri=%s action=%p", + redirectUriString.get(), redirectAction.get())); + uint32_t openFlags = + nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED; + cacheDiskStorage->AsyncOpenURI(redirectURI, ""_ns, openFlags, + redirectAction); + return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier); + } + + CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation, + fullUri); + + return RunPredictions(targetURI, *lci->OriginAttributesPtr(), verifier); +} + +// This is the driver for predicting at browser startup time based on pages that +// have previously been loaded close to startup. +bool Predictor::PredictForStartup(nsICacheEntry* entry, bool fullUri, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForStartup")); + + nsCOMPtr<nsILoadContextInfo> lci; + + nsresult rv = entry->GetLoadContextInfo(getter_AddRefs(lci)); + NS_ENSURE_SUCCESS(rv, false); + + int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime); + CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount, + globalDegradation, fullUri); + return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier); +} + +// This calculates how much to degrade our confidence in our data based on +// the last time this top-level resource was loaded. This "global degradation" +// applies to *all* subresources we have associated with the top-level +// resource. This will be in addition to any reduction in confidence we have +// associated with a particular subresource. +int32_t Predictor::CalculateGlobalDegradation(uint32_t lastLoad) { + MOZ_ASSERT(NS_IsMainThread()); + + int32_t globalDegradation; + uint32_t delta = NOW_IN_SECONDS() - lastLoad; + if (delta < ONE_DAY) { + globalDegradation = StaticPrefs::network_predictor_page_degradation_day(); + } else if (delta < ONE_WEEK) { + globalDegradation = StaticPrefs::network_predictor_page_degradation_week(); + } else if (delta < ONE_MONTH) { + globalDegradation = StaticPrefs::network_predictor_page_degradation_month(); + } else if (delta < ONE_YEAR) { + globalDegradation = StaticPrefs::network_predictor_page_degradation_year(); + } else { + globalDegradation = StaticPrefs::network_predictor_page_degradation_max(); + } + + Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION, + globalDegradation); + return globalDegradation; +} + +// This calculates our overall confidence that a particular subresource will be +// loaded as part of a top-level load. +// @param hitCount - the number of times we have loaded this subresource as part +// of this top-level load +// @param hitsPossible - the number of times we have performed this top-level +// load +// @param lastHit - the timestamp of the last time we loaded this subresource as +// part of this top-level load +// @param lastPossible - the timestamp of the last time we performed this +// top-level load +// @param globalDegradation - the degradation for this top-level load as +// determined by CalculateGlobalDegradation +int32_t Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible, + uint32_t lastHit, uint32_t lastPossible, + int32_t globalDegradation) { + MOZ_ASSERT(NS_IsMainThread()); + + Telemetry::AutoCounter<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED> + predictionsCalculated; + ++predictionsCalculated; + + if (!hitsPossible) { + return 0; + } + + int32_t baseConfidence = (hitCount * 100) / hitsPossible; + int32_t maxConfidence = 100; + int32_t confidenceDegradation = 0; + + if (lastHit < lastPossible) { + // We didn't load this subresource the last time this top-level load was + // performed, so let's not bother preconnecting (at the very least). + maxConfidence = + StaticPrefs::network_predictor_preconnect_min_confidence() - 1; + + // Now calculate how much we want to degrade our confidence based on how + // long it's been between the last time we did this top-level load and the + // last time this top-level load included this subresource. + PRTime delta = lastPossible - lastHit; + if (delta == 0) { + confidenceDegradation = 0; + } else if (delta < ONE_DAY) { + confidenceDegradation = + StaticPrefs::network_predictor_subresource_degradation_day(); + } else if (delta < ONE_WEEK) { + confidenceDegradation = + StaticPrefs::network_predictor_subresource_degradation_week(); + } else if (delta < ONE_MONTH) { + confidenceDegradation = + StaticPrefs::network_predictor_subresource_degradation_month(); + } else if (delta < ONE_YEAR) { + confidenceDegradation = + StaticPrefs::network_predictor_subresource_degradation_year(); + } else { + confidenceDegradation = + StaticPrefs::network_predictor_subresource_degradation_max(); + maxConfidence = 0; + } + } + + // Calculate our confidence and clamp it to between 0 and maxConfidence + // (<= 100) + int32_t confidence = + baseConfidence - confidenceDegradation - globalDegradation; + confidence = std::max(confidence, 0); + confidence = std::min(confidence, maxConfidence); + + Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence); + Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION, + confidenceDegradation); + Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence); + return confidence; +} + +static void MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit, + const uint32_t flags, nsCString& newValue) { + newValue.Truncate(); + newValue.AppendInt(METADATA_VERSION); + newValue.Append(','); + newValue.AppendInt(hitCount); + newValue.Append(','); + newValue.AppendInt(lastHit); + newValue.Append(','); + newValue.AppendInt(flags); +} + +// On every page load, the rolling window gets shifted by one bit, leaving the +// lowest bit at 0, to indicate that the subresource in question has not been +// seen on the most recent page load. If, at some point later during the page +// load, the subresource is seen again, we will then set the lowest bit to 1. +// This is how we keep track of how many of the last n pageloads (for n <= 20) a +// particular subresource has been seen. The rolling window is kept in the upper +// 20 bits of the flags element of the metadata. This saves 12 bits for regular +// old flags. +void Predictor::UpdateRollingLoadCount(nsICacheEntry* entry, + const uint32_t flags, const char* key, + const uint32_t hitCount, + const uint32_t lastHit) { + // Extract just the rolling load count from the flags, shift it to clear the + // lowest bit, and put the new value with the existing flags. + uint32_t rollingLoadCount = flags & ~kFlagsMask; + rollingLoadCount <<= 1; + uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount; + + // Finally, update the metadata on the cache entry. + nsAutoCString newValue; + MakeMetadataEntry(hitCount, lastHit, newFlags, newValue); + entry->SetMetaDataElement(key, newValue.BeginReading()); +} + +uint32_t Predictor::ClampedPrefetchRollingLoadCount() { + int32_t n = StaticPrefs::network_predictor_prefetch_rolling_load_count(); + if (n < 0) { + return 0; + } + if (n > kMaxPrefetchRollingLoadCount) { + return kMaxPrefetchRollingLoadCount; + } + return n; +} + +void Predictor::CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer, + uint32_t lastLoad, uint32_t loadCount, + int32_t globalDegradation, bool fullUri) { + MOZ_ASSERT(NS_IsMainThread()); + + // Since the visitor gets called under a cache lock, all we do there is get + // copies of the keys/values we care about, and then do the real work here + entry->VisitMetaData(this); + nsTArray<nsCString> keysToOperateOn = std::move(mKeysToOperateOn), + valuesToOperateOn = std::move(mValuesToOperateOn); + + MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length()); + for (size_t i = 0; i < keysToOperateOn.Length(); ++i) { + const char* key = keysToOperateOn[i].BeginReading(); + const char* value = valuesToOperateOn[i].BeginReading(); + + nsCString uri; + uint32_t hitCount, lastHit, flags; + if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) { + // This failed, get rid of it so we don't waste space + entry->SetMetaDataElement(key, nullptr); + continue; + } + + int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit, + lastLoad, globalDegradation); + if (fullUri) { + UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit); + } + PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key, + value, confidence)); + PrefetchIgnoreReason reason = PREFETCH_OK; + if (!fullUri) { + // Not full URI - don't prefetch! No sense in it! + PREDICTOR_LOG((" forcing non-cacheability - not full URI")); + if (flags & FLAG_PREFETCHABLE) { + // This only applies if we had somehow otherwise marked this + // prefetchable. + reason = NOT_FULL_URI; + } + flags &= ~FLAG_PREFETCHABLE; + } else if (!referrer) { + // No referrer means we can't prefetch, so pretend it's non-cacheable, + // no matter what. + PREDICTOR_LOG((" forcing non-cacheability - no referrer")); + if (flags & FLAG_PREFETCHABLE) { + // This only applies if we had somehow otherwise marked this + // prefetchable. + reason = NO_REFERRER; + } + flags &= ~FLAG_PREFETCHABLE; + } else { + uint32_t expectedRollingLoadCount = + (1 << ClampedPrefetchRollingLoadCount()) - 1; + expectedRollingLoadCount <<= kRollingLoadOffset; + if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) { + PREDICTOR_LOG((" forcing non-cacheability - missed a load")); + if (flags & FLAG_PREFETCHABLE) { + // This only applies if we had somehow otherwise marked this + // prefetchable. + reason = MISSED_A_LOAD; + } + flags &= ~FLAG_PREFETCHABLE; + } + } + + PREDICTOR_LOG((" setting up prediction")); + SetupPrediction(confidence, flags, uri, reason); + } +} + +// (Maybe) adds a predictive action to the prediction runner, based on our +// calculated confidence for the subresource in question. +void Predictor::SetupPrediction(int32_t confidence, uint32_t flags, + const nsCString& uri, + PrefetchIgnoreReason earlyReason) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv = NS_OK; + PREDICTOR_LOG( + ("SetupPrediction enable-prefetch=%d prefetch-min-confidence=%d " + "preconnect-min-confidence=%d preresolve-min-confidence=%d " + "flags=%d confidence=%d uri=%s", + StaticPrefs::network_predictor_enable_prefetch(), + StaticPrefs::network_predictor_prefetch_min_confidence(), + StaticPrefs::network_predictor_preconnect_min_confidence(), + StaticPrefs::network_predictor_preresolve_min_confidence(), flags, + confidence, uri.get())); + + bool prefetchOk = !!(flags & FLAG_PREFETCHABLE); + PrefetchIgnoreReason reason = earlyReason; + if (prefetchOk && !StaticPrefs::network_predictor_enable_prefetch()) { + prefetchOk = false; + reason = PREFETCH_DISABLED; + } else if (prefetchOk && !ClampedPrefetchRollingLoadCount() && + confidence < + StaticPrefs::network_predictor_prefetch_min_confidence()) { + prefetchOk = false; + if (!ClampedPrefetchRollingLoadCount()) { + reason = PREFETCH_DISABLED_VIA_COUNT; + } else { + reason = CONFIDENCE_TOO_LOW; + } + } + + // prefetchOk == false and reason == PREFETCH_OK indicates that the reason + // we aren't prefetching this item is because it was marked un-prefetchable in + // our metadata. We already have separate telemetry on that decision, so we + // aren't going to accumulate more here. Right now we only care about why + // something we had marked prefetchable isn't being prefetched. + if (!prefetchOk && reason != PREFETCH_OK) { + Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_IGNORE_REASON, reason); + } + + if (prefetchOk) { + nsCOMPtr<nsIURI> prefetchURI; + rv = NS_NewURI(getter_AddRefs(prefetchURI), uri); + if (NS_SUCCEEDED(rv)) { + mPrefetches.AppendElement(prefetchURI); + } + } else if (confidence >= + StaticPrefs::network_predictor_preconnect_min_confidence()) { + nsCOMPtr<nsIURI> preconnectURI; + rv = NS_NewURI(getter_AddRefs(preconnectURI), uri); + if (NS_SUCCEEDED(rv)) { + mPreconnects.AppendElement(preconnectURI); + } + } else if (confidence >= + StaticPrefs::network_predictor_preresolve_min_confidence()) { + nsCOMPtr<nsIURI> preresolveURI; + rv = NS_NewURI(getter_AddRefs(preresolveURI), uri); + if (NS_SUCCEEDED(rv)) { + mPreresolves.AppendElement(preresolveURI); + } + } + + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" NS_NewURI returned 0x%" PRIx32, static_cast<uint32_t>(rv))); + } +} + +nsresult Predictor::Prefetch(nsIURI* uri, nsIURI* referrer, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + nsAutoCString strUri, strReferrer; + uri->GetAsciiSpec(strUri); + referrer->GetAsciiSpec(strReferrer); + PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p", + strUri.get(), strReferrer.get(), verifier)); + nsCOMPtr<nsIChannel> channel; + nsresult rv = NS_NewChannel( + getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */ + nullptr, /* aPerformanceStorage */ + nullptr, /* aLoadGroup */ + nullptr, /* aCallbacks */ + nsIRequest::LOAD_BACKGROUND); + + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" NS_NewChannel failed rv=0x%" PRIX32, static_cast<uint32_t>(rv))); + return rv; + } + + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + rv = loadInfo->SetOriginAttributes(originAttributes); + + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" Set originAttributes into loadInfo failed rv=0x%" PRIX32, + static_cast<uint32_t>(rv))); + return rv; + } + + nsCOMPtr<nsIHttpChannel> httpChannel; + httpChannel = do_QueryInterface(channel); + if (!httpChannel) { + PREDICTOR_LOG((" Could not get HTTP Channel from new channel!")); + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(referrer); + rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo); + NS_ENSURE_SUCCESS(rv, rv); + // XXX - set a header here to indicate this is a prefetch? + + nsCOMPtr<nsIStreamListener> listener = + new PrefetchListener(verifier, uri, this); + PREDICTOR_LOG((" calling AsyncOpen listener=%p channel=%p", listener.get(), + channel.get())); + rv = channel->AsyncOpen(listener); + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" AsyncOpen failed rv=0x%" PRIX32, static_cast<uint32_t>(rv))); + } + + return rv; +} + +// Runs predictions that have been set up. +bool Predictor::RunPredictions(nsIURI* referrer, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread"); + + PREDICTOR_LOG(("Predictor::RunPredictions")); + + bool predicted = false; + uint32_t len, i; + + nsTArray<nsCOMPtr<nsIURI>> prefetches = std::move(mPrefetches), + preconnects = std::move(mPreconnects), + preresolves = std::move(mPreresolves); + + Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS> + totalPredictions; + Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES> totalPrefetches; + Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS> + totalPreconnects; + Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES> + totalPreresolves; + + len = prefetches.Length(); + for (i = 0; i < len; ++i) { + PREDICTOR_LOG((" doing prefetch")); + nsCOMPtr<nsIURI> uri = prefetches[i]; + if (NS_SUCCEEDED(Prefetch(uri, referrer, originAttributes, verifier))) { + ++totalPredictions; + ++totalPrefetches; + predicted = true; + } + } + + len = preconnects.Length(); + for (i = 0; i < len; ++i) { + PREDICTOR_LOG((" doing preconnect")); + nsCOMPtr<nsIURI> uri = preconnects[i]; + ++totalPredictions; + ++totalPreconnects; + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateContentPrincipal(uri, originAttributes); + mSpeculativeService->SpeculativeConnect(uri, principal, this, false); + predicted = true; + if (verifier) { + PREDICTOR_LOG((" sending preconnect verification")); + verifier->OnPredictPreconnect(uri); + } + } + + len = preresolves.Length(); + for (i = 0; i < len; ++i) { + nsCOMPtr<nsIURI> uri = preresolves[i]; + ++totalPredictions; + ++totalPreresolves; + nsAutoCString hostname; + uri->GetAsciiHost(hostname); + PREDICTOR_LOG((" doing preresolve %s", hostname.get())); + nsCOMPtr<nsICancelable> tmpCancelable; + mDnsService->AsyncResolveNative( + hostname, nsIDNSService::RESOLVE_TYPE_DEFAULT, + (nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + nsIDNSService::RESOLVE_SPECULATE), + nullptr, mDNSListener, nullptr, originAttributes, + getter_AddRefs(tmpCancelable)); + + // Fetch HTTPS RR if needed. + if (StaticPrefs::network_dns_upgrade_with_https_rr() || + StaticPrefs::network_dns_use_https_rr_as_altsvc()) { + mDnsService->AsyncResolveNative( + hostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, + (nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + nsIDNSService::RESOLVE_SPECULATE), + nullptr, mDNSListener, nullptr, originAttributes, + getter_AddRefs(tmpCancelable)); + } + + predicted = true; + if (verifier) { + PREDICTOR_LOG((" sending preresolve verification")); + verifier->OnPredictDNS(uri); + } + } + + return predicted; +} + +// Find out if a top-level page is likely to redirect. +bool Predictor::WouldRedirect(nsICacheEntry* entry, uint32_t loadCount, + uint32_t lastLoad, int32_t globalDegradation, + nsIURI** redirectURI) { + // TODO - not doing redirects for first go around + MOZ_ASSERT(NS_IsMainThread()); + + return false; +} + +NS_IMETHODIMP +Predictor::Learn(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, + JS::Handle<JS::Value> originAttributes, JSContext* aCx) { + OriginAttributes attrs; + + if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + return LearnNative(targetURI, sourceURI, reason, attrs); +} + +// Called from the main thread to update the database +NS_IMETHODIMP +Predictor::LearnNative(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, + const OriginAttributes& originAttributes) { + MOZ_ASSERT(NS_IsMainThread(), + "Predictor interface methods must be called on the main thread"); + + PREDICTOR_LOG(("Predictor::Learn")); + + if (IsNeckoChild()) { + MOZ_DIAGNOSTIC_ASSERT(gNeckoChild); + + PREDICTOR_LOG((" called on child process")); + + RefPtr<PredictorLearnRunnable> runnable = new PredictorLearnRunnable( + targetURI, sourceURI, reason, originAttributes); + SchedulerGroup::Dispatch(runnable.forget()); + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!StaticPrefs::network_predictor_enabled()) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + if (originAttributes.mPrivateBrowsingId > 0) { + // Don't want to do anything in PB mode + PREDICTOR_LOG((" in PB mode")); + return NS_OK; + } + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + PREDICTOR_LOG((" got non-HTTP[S] URI")); + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIURI> targetOrigin; + nsCOMPtr<nsIURI> sourceOrigin; + nsCOMPtr<nsIURI> uriKey; + nsCOMPtr<nsIURI> originKey; + nsresult rv; + + switch (reason) { + case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL: + if (!targetURI || sourceURI) { + PREDICTOR_LOG((" load toplevel invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin)); + NS_ENSURE_SUCCESS(rv, rv); + uriKey = targetURI; + originKey = targetOrigin; + break; + case nsINetworkPredictor::LEARN_STARTUP: + if (!targetURI || sourceURI) { + PREDICTOR_LOG((" startup invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin)); + NS_ENSURE_SUCCESS(rv, rv); + uriKey = mStartupURI; + originKey = mStartupURI; + break; + case nsINetworkPredictor::LEARN_LOAD_REDIRECT: + case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE: + if (!targetURI || !sourceURI) { + PREDICTOR_LOG((" redirect/subresource invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin)); + NS_ENSURE_SUCCESS(rv, rv); + rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin)); + NS_ENSURE_SUCCESS(rv, rv); + uriKey = sourceURI; + originKey = sourceOrigin; + break; + default: + PREDICTOR_LOG((" invalid reason")); + return NS_ERROR_INVALID_ARG; + } + + Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts; + ++learnAttempts; + + Predictor::Reason argReason{}; + argReason.mLearn = reason; + + // We always open the full uri (general cache) entry first, so we don't gum up + // the works waiting on predictor-only entries to open + RefPtr<Predictor::Action> uriAction = new Predictor::Action( + Predictor::Action::IS_FULL_URI, Predictor::Action::DO_LEARN, argReason, + targetURI, sourceURI, nullptr, this); + nsAutoCString uriKeyStr, targetUriStr, sourceUriStr; + uriKey->GetAsciiSpec(uriKeyStr); + targetURI->GetAsciiSpec(targetUriStr); + if (sourceURI) { + sourceURI->GetAsciiSpec(sourceUriStr); + } + PREDICTOR_LOG( + (" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d " + "action=%p", + uriKeyStr.get(), targetUriStr.get(), sourceUriStr.get(), reason, + uriAction.get())); + + nsCOMPtr<nsICacheStorage> cacheDiskStorage; + + RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes); + + rv = mCacheStorageService->DiskCacheStorage(lci, + getter_AddRefs(cacheDiskStorage)); + NS_ENSURE_SUCCESS(rv, rv); + + // For learning full URI things, we *always* open readonly and secretly, as we + // rely on actual pageloads to update the entry's metadata for us. + uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) { + // Learning for toplevel we want to open the full uri entry priority, since + // it's likely this entry will be used soon anyway, and we want this to be + // opened ASAP. + uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY; + } + cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, uriOpenFlags, uriAction); + + // Now we open the origin-only (and therefore predictor-only) entry + RefPtr<Predictor::Action> originAction = new Predictor::Action( + Predictor::Action::IS_ORIGIN, Predictor::Action::DO_LEARN, argReason, + targetOrigin, sourceOrigin, nullptr, this); + nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr; + originKey->GetAsciiSpec(originKeyStr); + targetOrigin->GetAsciiSpec(targetOriginStr); + if (sourceOrigin) { + sourceOrigin->GetAsciiSpec(sourceOriginStr); + } + PREDICTOR_LOG( + (" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d " + "action=%p", + originKeyStr.get(), targetOriginStr.get(), sourceOriginStr.get(), reason, + originAction.get())); + uint32_t originOpenFlags; + if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) { + // This is the only case when we want to update the 'last used' metadata on + // the cache entry we're getting. This only applies to predictor-specific + // entries. + originOpenFlags = + nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED; + } else { + originOpenFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + } + cacheDiskStorage->AsyncOpenURI(originKey, + nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION), + originOpenFlags, originAction); + + PREDICTOR_LOG(("Predictor::Learn returning")); + return NS_OK; +} + +void Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry, + bool isNew, bool fullUri, nsIURI* targetURI, + nsIURI* sourceURI) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::LearnInternal")); + + nsCString junk; + if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL && + NS_FAILED( + entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) { + // This is an origin-only entry that we haven't seen before. Let's mark it + // as seen. + PREDICTOR_LOG((" marking new origin entry as seen")); + nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1"); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" failed to mark origin entry seen")); + return; + } + + // Need to ensure someone else can get to the entry if necessary + entry->MetaDataReady(); + return; + } + + switch (reason) { + case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL: + // This case only exists to be used during tests - code outside the + // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL. + // The predictor xpcshell test needs this branch, however, because we + // have no real page loads in xpcshell, and this is how we fake it up + // so that all the work that normally happens behind the scenes in a + // page load can be done for testing purposes. + if (fullUri && StaticPrefs::network_predictor_doing_tests()) { + PREDICTOR_LOG( + (" WARNING - updating rolling load count. " + "If you see this outside tests, you did it wrong")); + + // Since the visitor gets called under a cache lock, all we do there is + // get copies of the keys/values we care about, and then do the real + // work here + entry->VisitMetaData(this); + nsTArray<nsCString> keysToOperateOn = std::move(mKeysToOperateOn), + valuesToOperateOn = std::move(mValuesToOperateOn); + + MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length()); + for (size_t i = 0; i < keysToOperateOn.Length(); ++i) { + const char* key = keysToOperateOn[i].BeginReading(); + const char* value = valuesToOperateOn[i].BeginReading(); + + nsCString uri; + uint32_t hitCount, lastHit, flags; + if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) { + // This failed, get rid of it so we don't waste space + entry->SetMetaDataElement(key, nullptr); + continue; + } + UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit); + } + } else { + PREDICTOR_LOG((" nothing to do for toplevel")); + } + break; + case nsINetworkPredictor::LEARN_LOAD_REDIRECT: + if (fullUri) { + LearnForRedirect(entry, targetURI); + } + break; + case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE: + LearnForSubresource(entry, targetURI); + break; + case nsINetworkPredictor::LEARN_STARTUP: + LearnForStartup(entry, targetURI); + break; + default: + PREDICTOR_LOG((" unexpected reason value")); + MOZ_ASSERT(false, "Got unexpected value for learn reason!"); + } +} + +NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor) + +NS_IMETHODIMP +Predictor::SpaceCleaner::OnMetaDataElement(const char* key, const char* value) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsURIMetadataElement(key)) { + // This isn't a bit of metadata we care about + return NS_OK; + } + + nsCString uri; + uint32_t hitCount, lastHit, flags; + bool ok = + mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags); + + if (!ok) { + // Couldn't parse this one, just get rid of it + nsCString nsKey; + nsKey.AssignASCII(key); + mLongKeysToDelete.AppendElement(nsKey); + return NS_OK; + } + + uint32_t uriLength = uri.Length(); + if (uriLength > StaticPrefs::network_predictor_max_uri_length()) { + // Default to getting rid of URIs that are too long and were put in before + // we had our limit on URI length, in order to free up some space. + nsCString nsKey; + nsKey.AssignASCII(key); + mLongKeysToDelete.AppendElement(nsKey); + return NS_OK; + } + + if (!mLRUKeyToDelete || lastHit < mLRUStamp) { + mLRUKeyToDelete = key; + mLRUStamp = lastHit; + } + + return NS_OK; +} + +void Predictor::SpaceCleaner::Finalize(nsICacheEntry* entry) { + MOZ_ASSERT(NS_IsMainThread()); + + if (mLRUKeyToDelete) { + entry->SetMetaDataElement(mLRUKeyToDelete, nullptr); + } + + for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) { + entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr); + } +} + +// Called when a subresource has been hit from a top-level load. Uses the two +// helper functions above to update the database appropriately. +void Predictor::LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::LearnForSubresource")); + + uint32_t lastLoad; + nsresult rv = entry->GetLastFetched(&lastLoad); + NS_ENSURE_SUCCESS_VOID(rv); + + uint32_t loadCount; + rv = entry->GetFetchCount(&loadCount); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCString key; + key.AssignLiteral(META_DATA_PREFIX); + nsCString uri; + targetURI->GetAsciiSpec(uri); + key.Append(uri); + if (uri.Length() > StaticPrefs::network_predictor_max_uri_length()) { + // We do this to conserve space/prevent OOMs + PREDICTOR_LOG((" uri too long!")); + entry->SetMetaDataElement(key.BeginReading(), nullptr); + return; + } + + nsCString value; + rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value)); + + uint32_t hitCount, lastHit, flags; + bool isNewResource = + (NS_FAILED(rv) || + !ParseMetaDataEntry(key.BeginReading(), value.BeginReading(), uri, + hitCount, lastHit, flags)); + + int32_t resourceCount = 0; + if (isNewResource) { + // This is a new addition + PREDICTOR_LOG((" new resource")); + nsCString s; + rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s)); + if (NS_SUCCEEDED(rv)) { + resourceCount = atoi(s.BeginReading()); + } + if (resourceCount >= + StaticPrefs::network_predictor_max_resources_per_entry()) { + RefPtr<Predictor::SpaceCleaner> cleaner = + new Predictor::SpaceCleaner(this); + entry->VisitMetaData(cleaner); + cleaner->Finalize(entry); + } else { + ++resourceCount; + } + nsAutoCString count; + count.AppendInt(resourceCount); + rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading()); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" failed to update resource count")); + return; + } + hitCount = 1; + flags = 0; + } else { + PREDICTOR_LOG((" existing resource")); + hitCount = std::min(hitCount + 1, loadCount); + } + + // Update the rolling load count to mark this sub-resource as seen on the + // most-recent pageload so it can be eligible for prefetch (assuming all + // the other stars align). + flags |= (1 << kRollingLoadOffset); + + nsCString newValue; + MakeMetadataEntry(hitCount, lastLoad, flags, newValue); + rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading()); + PREDICTOR_LOG( + (" SetMetaDataElement -> 0x%08" PRIX32, static_cast<uint32_t>(rv))); + if (NS_FAILED(rv) && isNewResource) { + // Roll back the increment to the resource count we made above. + PREDICTOR_LOG((" rolling back resource count update")); + --resourceCount; + if (resourceCount == 0) { + entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr); + } else { + nsAutoCString count; + count.AppendInt(resourceCount); + entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading()); + } + } +} + +// This is called when a top-level loaded ended up redirecting to a different +// URI so we can keep track of that fact. +void Predictor::LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI) { + MOZ_ASSERT(NS_IsMainThread()); + + // TODO - not doing redirects for first go around + PREDICTOR_LOG(("Predictor::LearnForRedirect")); +} + +// This will add a page to our list of startup pages if it's being loaded +// before our startup window has expired. +void Predictor::MaybeLearnForStartup(nsIURI* uri, bool fullUri, + const OriginAttributes& originAttributes) { + MOZ_ASSERT(NS_IsMainThread()); + + // TODO - not doing startup for first go around + PREDICTOR_LOG(("Predictor::MaybeLearnForStartup")); +} + +// Add information about a top-level load to our list of startup pages +void Predictor::LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI) { + MOZ_ASSERT(NS_IsMainThread()); + + // These actually do the same set of work, just on different entries, so we + // can pass through to get the real work done here + PREDICTOR_LOG(("Predictor::LearnForStartup")); + LearnForSubresource(entry, targetURI); +} + +bool Predictor::ParseMetaDataEntry(const char* key, const char* value, + nsCString& uri, uint32_t& hitCount, + uint32_t& lastHit, uint32_t& flags) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG( + ("Predictor::ParseMetaDataEntry key=%s value=%s", key ? key : "", value)); + + const char* comma = strchr(value, ','); + if (!comma) { + PREDICTOR_LOG((" could not find first comma")); + return false; + } + + uint32_t version = static_cast<uint32_t>(atoi(value)); + PREDICTOR_LOG((" version -> %u", version)); + + if (version != METADATA_VERSION) { + PREDICTOR_LOG( + (" metadata version mismatch %u != %u", version, METADATA_VERSION)); + return false; + } + + value = comma + 1; + comma = strchr(value, ','); + if (!comma) { + PREDICTOR_LOG((" could not find second comma")); + return false; + } + + hitCount = static_cast<uint32_t>(atoi(value)); + PREDICTOR_LOG((" hitCount -> %u", hitCount)); + + value = comma + 1; + comma = strchr(value, ','); + if (!comma) { + PREDICTOR_LOG((" could not find third comma")); + return false; + } + + lastHit = static_cast<uint32_t>(atoi(value)); + PREDICTOR_LOG((" lastHit -> %u", lastHit)); + + value = comma + 1; + flags = static_cast<uint32_t>(atoi(value)); + PREDICTOR_LOG((" flags -> %u", flags)); + + if (key) { + const char* uriStart = key + (sizeof(META_DATA_PREFIX) - 1); + uri.AssignASCII(uriStart); + PREDICTOR_LOG((" uri -> %s", uriStart)); + } else { + uri.Truncate(); + } + + return true; +} + +NS_IMETHODIMP +Predictor::Reset() { + MOZ_ASSERT(NS_IsMainThread(), + "Predictor interface methods must be called on the main thread"); + + PREDICTOR_LOG(("Predictor::Reset")); + + if (IsNeckoChild()) { + MOZ_DIAGNOSTIC_ASSERT(gNeckoChild); + + PREDICTOR_LOG((" forwarding to parent process")); + gNeckoChild->SendPredReset(); + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!StaticPrefs::network_predictor_enabled()) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + RefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this); + PREDICTOR_LOG((" created a resetter")); + mCacheStorageService->AsyncVisitAllStorages(reset, true); + PREDICTOR_LOG((" Cache async launched, returning now")); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(Predictor::Resetter, nsICacheEntryOpenCallback, + nsICacheEntryMetaDataVisitor, nsICacheStorageVisitor); + +Predictor::Resetter::Resetter(Predictor* predictor) + : mEntriesToVisit(0), mPredictor(predictor) {} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) { + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew, + nsresult result) { + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_FAILED(result)) { + // This can happen when we've tried to open an entry that doesn't exist for + // some non-reset operation, and then get reset shortly thereafter (as + // happens in some of our tests). + --mEntriesToVisit; + if (!mEntriesToVisit) { + Complete(); + } + return NS_OK; + } + + entry->VisitMetaData(this); + nsTArray<nsCString> keysToDelete = std::move(mKeysToDelete); + + for (size_t i = 0; i < keysToDelete.Length(); ++i) { + const char* key = keysToDelete[i].BeginReading(); + entry->SetMetaDataElement(key, nullptr); + } + + --mEntriesToVisit; + if (!mEntriesToVisit) { + Complete(); + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnMetaDataElement(const char* asciiKey, + const char* asciiValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!StringBeginsWith(nsDependentCString(asciiKey), + nsLiteralCString(META_DATA_PREFIX))) { + // Not a metadata entry we care about, carry on + return NS_OK; + } + + nsCString key; + key.AssignASCII(asciiKey); + mKeysToDelete.AppendElement(key); + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount, + uint64_t consumption, uint64_t capacity, + nsIFile* diskDirectory) { + MOZ_ASSERT(NS_IsMainThread()); + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryInfo(nsIURI* uri, const nsACString& idEnhance, + int64_t dataSize, int64_t altDataSize, + uint32_t fetchCount, + uint32_t lastModifiedTime, + uint32_t expirationTime, bool aPinned, + nsILoadContextInfo* aInfo) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + // The predictor will only ever touch entries with no idEnhance ("") or an + // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that + // don't match that to avoid doing extra work. + if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) { + // This is an entry we own, so we can just doom it entirely + nsCOMPtr<nsICacheStorage> cacheDiskStorage; + + rv = mPredictor->mCacheStorageService->DiskCacheStorage( + aInfo, getter_AddRefs(cacheDiskStorage)); + + NS_ENSURE_SUCCESS(rv, rv); + cacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr); + } else if (idEnhance.IsEmpty()) { + // This is an entry we don't own, so we have to be a little more careful and + // just get rid of our own metadata entries. Append it to an array of things + // to operate on and then do the operations later so we don't end up calling + // Complete() multiple times/too soon. + ++mEntriesToVisit; + mURIsToVisit.AppendElement(uri); + mInfosToVisit.AppendElement(aInfo); + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryVisitCompleted() { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + nsTArray<nsCOMPtr<nsIURI>> urisToVisit = std::move(mURIsToVisit); + + MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length()); + + nsTArray<nsCOMPtr<nsILoadContextInfo>> infosToVisit = + std::move(mInfosToVisit); + + MOZ_ASSERT(mEntriesToVisit == infosToVisit.Length()); + + if (!mEntriesToVisit) { + Complete(); + return NS_OK; + } + + uint32_t entriesToVisit = urisToVisit.Length(); + for (uint32_t i = 0; i < entriesToVisit; ++i) { + nsCString u; + nsCOMPtr<nsICacheStorage> cacheDiskStorage; + + rv = mPredictor->mCacheStorageService->DiskCacheStorage( + infosToVisit[i], getter_AddRefs(cacheDiskStorage)); + NS_ENSURE_SUCCESS(rv, rv); + + urisToVisit[i]->GetAsciiSpec(u); + rv = cacheDiskStorage->AsyncOpenURI( + urisToVisit[i], ""_ns, + nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED, + this); + if (NS_FAILED(rv)) { + mEntriesToVisit--; + if (!mEntriesToVisit) { + Complete(); + return NS_OK; + } + } + } + + return NS_OK; +} + +void Predictor::Resetter::Complete() { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (!obs) { + PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!")); + return; + } + + obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr); +} + +// Helper functions to make using the predictor easier from native code + +static StaticRefPtr<nsINetworkPredictor> sPredictor; + +static nsresult EnsureGlobalPredictor(nsINetworkPredictor** aPredictor) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!sPredictor) { + nsresult rv; + nsCOMPtr<nsINetworkPredictor> predictor = + do_GetService("@mozilla.org/network/predictor;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + sPredictor = predictor; + ClearOnShutdown(&sPredictor); + } + + nsCOMPtr<nsINetworkPredictor> predictor = sPredictor.get(); + predictor.forget(aPredictor); + return NS_OK; +} + +nsresult PredictorPredict(nsIURI* targetURI, nsIURI* sourceURI, + PredictorPredictReason reason, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr<nsINetworkPredictor> predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + return predictor->PredictNative(targetURI, sourceURI, reason, + originAttributes, verifier); +} + +nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, + const OriginAttributes& originAttributes) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr<nsINetworkPredictor> predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes); +} + +nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, nsILoadGroup* loadGroup) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr<nsINetworkPredictor> predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILoadContext> loadContext; + OriginAttributes originAttributes; + + if (loadGroup) { + nsCOMPtr<nsIInterfaceRequestor> callbacks; + loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) { + loadContext = do_GetInterface(callbacks); + + if (loadContext) { + loadContext->GetOriginAttributes(originAttributes); + } + } + } + + return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes); +} + +nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI, + PredictorLearnReason reason, dom::Document* document) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr<nsINetworkPredictor> predictor; + nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + OriginAttributes originAttributes; + + if (document) { + nsCOMPtr<nsIPrincipal> docPrincipal = document->NodePrincipal(); + + if (docPrincipal) { + originAttributes = docPrincipal->OriginAttributesRef(); + } + } + + return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes); +} + +nsresult PredictorLearnRedirect(nsIURI* targetURI, nsIChannel* channel, + const OriginAttributes& originAttributes) { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIURI> sourceURI; + nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI)); + NS_ENSURE_SUCCESS(rv, rv); + + bool sameUri; + rv = targetURI->Equals(sourceURI, &sameUri); + NS_ENSURE_SUCCESS(rv, rv); + + if (sameUri) { + return NS_OK; + } + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_OK; + } + + nsCOMPtr<nsINetworkPredictor> predictor; + rv = EnsureGlobalPredictor(getter_AddRefs(predictor)); + NS_ENSURE_SUCCESS(rv, rv); + + return predictor->LearnNative(targetURI, sourceURI, + nsINetworkPredictor::LEARN_LOAD_REDIRECT, + originAttributes); +} + +// nsINetworkPredictorVerifier + +/** + * Call through to the child's verifier (only during tests) + */ +NS_IMETHODIMP +Predictor::OnPredictPrefetch(nsIURI* aURI, uint32_t httpStatus) { + if (IsNeckoChild()) { + if (mChildVerifier) { + // Ideally, we'd assert here. But since we're slowly moving towards a + // world where we have multiple child processes, and only one child + // process will be likely to have a verifier, we have to play it safer. + return mChildVerifier->OnPredictPrefetch(aURI, httpStatus); + } + return NS_OK; + } + + MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null"); + + for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictPrefetch(aURI, httpStatus)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::OnPredictPreconnect(nsIURI* aURI) { + if (IsNeckoChild()) { + if (mChildVerifier) { + // Ideally, we'd assert here. But since we're slowly moving towards a + // world where we have multiple child processes, and only one child + // process will be likely to have a verifier, we have to play it safer. + return mChildVerifier->OnPredictPreconnect(aURI); + } + return NS_OK; + } + + MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null"); + + for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictPreconnect(aURI)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::OnPredictDNS(nsIURI* aURI) { + if (IsNeckoChild()) { + if (mChildVerifier) { + // Ideally, we'd assert here. But since we're slowly moving towards a + // world where we have multiple child processes, and only one child + // process will be likely to have a verifier, we have to play it safer. + return mChildVerifier->OnPredictDNS(aURI); + } + return NS_OK; + } + + MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null"); + + for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictDNS(aURI)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + return NS_OK; +} + +// Predictor::PrefetchListener +// nsISupports +NS_IMPL_ISUPPORTS(Predictor::PrefetchListener, nsIStreamListener, + nsIRequestObserver) + +// nsIRequestObserver +NS_IMETHODIMP +Predictor::PrefetchListener::OnStartRequest(nsIRequest* aRequest) { + mStartTime = TimeStamp::Now(); + return NS_OK; +} + +NS_IMETHODIMP +Predictor::PrefetchListener::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%" PRIX32, this, + static_cast<uint32_t>(aStatusCode))); + NS_ENSURE_ARG(aRequest); + if (NS_FAILED(aStatusCode)) { + return aStatusCode; + } + Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME, + mStartTime); + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); + if (!httpChannel) { + PREDICTOR_LOG((" Could not get HTTP Channel!")); + return NS_ERROR_UNEXPECTED; + } + nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(httpChannel); + if (!cachingChannel) { + PREDICTOR_LOG((" Could not get caching channel!")); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + uint32_t httpStatus; + rv = httpChannel->GetResponseStatus(&httpStatus); + if (NS_SUCCEEDED(rv) && httpStatus == 200) { + rv = cachingChannel->ForceCacheEntryValidFor( + StaticPrefs::network_predictor_prefetch_force_valid_for()); + PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%" PRIX32, + StaticPrefs::network_predictor_prefetch_force_valid_for(), + static_cast<uint32_t>(rv))); + } else { + rv = cachingChannel->ForceCacheEntryValidFor(0); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Not200); + PREDICTOR_LOG((" removing any forced validity rv=%" PRIX32, + static_cast<uint32_t>(rv))); + } + + nsAutoCString reqName; + rv = aRequest->GetName(reqName); + if (NS_FAILED(rv)) { + reqName.AssignLiteral("<unknown>"); + } + + PREDICTOR_LOG((" request %s status %u", reqName.get(), httpStatus)); + + if (mVerifier) { + mVerifier->OnPredictPrefetch(mURI, httpStatus); + } + + return rv; +} + +// nsIStreamListener +NS_IMETHODIMP +Predictor::PrefetchListener::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, + const uint32_t aCount) { + uint32_t result; + return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, + &result); +} + +// Miscellaneous Predictor + +void Predictor::UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI, + uint32_t httpStatus, + nsHttpRequestHead& requestHead, + nsHttpResponseHead* responseHead, + nsILoadContextInfo* lci, bool isTracking) { + MOZ_ASSERT(NS_IsMainThread()); + + if (lci && lci->IsPrivate()) { + PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring")); + return; + } + + if (!sourceURI || !targetURI) { + PREDICTOR_LOG( + ("Predictor::UpdateCacheability missing source or target uri")); + return; + } + + if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) { + PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri")); + return; + } + + RefPtr<Predictor> self = sSelf; + if (self) { + nsAutoCString method; + requestHead.Method(method); + + nsAutoCString vary; + Unused << responseHead->GetHeader(nsHttp::Vary, vary); + + nsAutoCString cacheControlHeader; + Unused << responseHead->GetHeader(nsHttp::Cache_Control, + cacheControlHeader); + CacheControlParser cacheControl(cacheControlHeader); + + self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, method, + *lci->OriginAttributesPtr(), isTracking, + !vary.IsEmpty(), cacheControl.NoStore()); + } +} + +void Predictor::UpdateCacheabilityInternal( + nsIURI* sourceURI, nsIURI* targetURI, uint32_t httpStatus, + const nsCString& method, const OriginAttributes& originAttributes, + bool isTracking, bool couldVary, bool isNoStore) { + PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus)); + + nsresult rv; + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return; + } + + if (!StaticPrefs::network_predictor_enabled()) { + PREDICTOR_LOG((" not enabled")); + return; + } + + nsCOMPtr<nsICacheStorage> cacheDiskStorage; + + RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes); + + rv = mCacheStorageService->DiskCacheStorage(lci, + getter_AddRefs(cacheDiskStorage)); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" cannot get disk cache storage")); + return; + } + + uint32_t openFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + RefPtr<Predictor::CacheabilityAction> action = + new Predictor::CacheabilityAction(targetURI, httpStatus, method, + isTracking, couldVary, isNoStore, this); + nsAutoCString uri; + targetURI->GetAsciiSpec(uri); + PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get())); + cacheDiskStorage->AsyncOpenURI(sourceURI, ""_ns, openFlags, action); +} + +NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction, nsICacheEntryOpenCallback, + nsICacheEntryMetaDataVisitor); + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry* entry, + uint32_t* result) { + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +namespace { +enum PrefetchDecisionReason { + PREFETCHABLE, + STATUS_NOT_200, + METHOD_NOT_GET, + URL_HAS_QUERY_STRING, + RESOURCE_IS_TRACKING, + RESOURCE_COULD_VARY, + RESOURCE_IS_NO_STORE +}; +} + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry* entry, + bool isNew, + nsresult result) { + MOZ_ASSERT(NS_IsMainThread()); + // This is being opened read-only, so isNew should always be false + MOZ_ASSERT(!isNew); + + PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this)); + if (NS_FAILED(result)) { + // Nothing to do + PREDICTOR_LOG((" nothing to do result=%" PRIX32 " isNew=%d", + static_cast<uint32_t>(result), isNew)); + return NS_OK; + } + + nsCString strTargetURI; + nsresult rv = mTargetURI->GetAsciiSpec(strTargetURI); + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" GetAsciiSpec returned %" PRIx32, static_cast<uint32_t>(rv))); + return NS_OK; + } + + rv = entry->VisitMetaData(this); + if (NS_FAILED(rv)) { + PREDICTOR_LOG( + (" VisitMetaData returned %" PRIx32, static_cast<uint32_t>(rv))); + return NS_OK; + } + + nsTArray<nsCString> keysToCheck = std::move(mKeysToCheck), + valuesToCheck = std::move(mValuesToCheck); + + bool hasQueryString = false; + nsAutoCString query; + if (NS_SUCCEEDED(mTargetURI->GetQuery(query)) && !query.IsEmpty()) { + hasQueryString = true; + } + + MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length()); + for (size_t i = 0; i < keysToCheck.Length(); ++i) { + const char* key = keysToCheck[i].BeginReading(); + const char* value = valuesToCheck[i].BeginReading(); + nsCString uri; + uint32_t hitCount, lastHit, flags; + + if (!mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, + flags)) { + PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value)); + continue; + } + + if (strTargetURI.Equals(uri)) { + bool prefetchable = true; + PrefetchDecisionReason reason = PREFETCHABLE; + + if (mHttpStatus != 200) { + prefetchable = false; + reason = STATUS_NOT_200; + } else if (!mMethod.EqualsLiteral("GET")) { + prefetchable = false; + reason = METHOD_NOT_GET; + } else if (hasQueryString) { + prefetchable = false; + reason = URL_HAS_QUERY_STRING; + } else if (mIsTracking) { + prefetchable = false; + reason = RESOURCE_IS_TRACKING; + } else if (mCouldVary) { + prefetchable = false; + reason = RESOURCE_COULD_VARY; + } else if (mIsNoStore) { + // We don't set prefetchable = false yet, because we just want to know + // what kind of effect this would have on prefetching. + reason = RESOURCE_IS_NO_STORE; + } + + Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_DECISION_REASON, + reason); + + if (prefetchable) { + PREDICTOR_LOG((" marking %s cacheable", key)); + flags |= FLAG_PREFETCHABLE; + } else { + PREDICTOR_LOG((" marking %s uncacheable", key)); + flags &= ~FLAG_PREFETCHABLE; + } + nsCString newValue; + MakeMetadataEntry(hitCount, lastHit, flags, newValue); + entry->SetMetaDataElement(key, newValue.BeginReading()); + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnMetaDataElement(const char* asciiKey, + const char* asciiValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsURIMetadataElement(asciiKey)) { + return NS_OK; + } + + nsCString key, value; + key.AssignASCII(asciiKey); + value.AssignASCII(asciiValue); + mKeysToCheck.AppendElement(key); + mValuesToCheck.AppendElement(value); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/Predictor.h b/netwerk/base/Predictor.h new file mode 100644 index 0000000000..a16e09adad --- /dev/null +++ b/netwerk/base/Predictor.h @@ -0,0 +1,458 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_Predictor_h +#define mozilla_net_Predictor_h + +#include "nsINetworkPredictor.h" +#include "nsINetworkPredictorVerifier.h" + +#include "nsCOMPtr.h" +#include "nsICacheEntry.h" +#include "nsICacheEntryOpenCallback.h" +#include "nsICacheStorageService.h" +#include "nsICacheStorageVisitor.h" +#include "nsIDNSListener.h" +#include "nsIInterfaceRequestor.h" +#include "nsIObserver.h" +#include "nsISpeculativeConnect.h" +#include "nsIStreamListener.h" +#include "mozilla/RefPtr.h" +#include "nsString.h" +#include "nsTArray.h" + +#include "mozilla/TimeStamp.h" + +class nsICacheStorage; +class nsIDNSService; +class nsIIOService; +class nsILoadContextInfo; +class nsITimer; + +namespace mozilla { +namespace net { + +class nsHttpRequestHead; +class nsHttpResponseHead; + +class Predictor final : public nsINetworkPredictor, + public nsIObserver, + public nsISpeculativeConnectionOverrider, + public nsIInterfaceRequestor, + public nsICacheEntryMetaDataVisitor, + public nsINetworkPredictorVerifier { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSINETWORKPREDICTOR + NS_DECL_NSIOBSERVER + NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICACHEENTRYMETADATAVISITOR + NS_DECL_NSINETWORKPREDICTORVERIFIER + + Predictor(); + + nsresult Init(); + void Shutdown(); + static nsresult Create(const nsIID& iid, void** result); + + // Used to update whether a particular URI was cacheable or not. + // sourceURI and targetURI are the same as the arguments to Learn + // and httpStatus is the status code we got while loading targetURI. + static void UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI, + uint32_t httpStatus, + nsHttpRequestHead& requestHead, + nsHttpResponseHead* responseHead, + nsILoadContextInfo* lci, bool isTracking); + + private: + virtual ~Predictor(); + + // Stores callbacks for a child process predictor (for test purposes) + nsCOMPtr<nsINetworkPredictorVerifier> mChildVerifier; + + union Reason { + PredictorLearnReason mLearn; + PredictorPredictReason mPredict; + }; + + class DNSListener : public nsIDNSListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + DNSListener() = default; + + private: + virtual ~DNSListener() = default; + }; + + class Action : public nsICacheEntryOpenCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICACHEENTRYOPENCALLBACK + + Action(bool fullUri, bool predict, Reason reason, nsIURI* targetURI, + nsIURI* sourceURI, nsINetworkPredictorVerifier* verifier, + Predictor* predictor); + Action(bool fullUri, bool predict, Reason reason, nsIURI* targetURI, + nsIURI* sourceURI, nsINetworkPredictorVerifier* verifier, + Predictor* predictor, uint8_t stackCount); + + static const bool IS_FULL_URI = true; + static const bool IS_ORIGIN = false; + + static const bool DO_PREDICT = true; + static const bool DO_LEARN = false; + + private: + virtual ~Action() = default; + + bool mFullUri : 1; + bool mPredict : 1; + union { + PredictorPredictReason mPredictReason; + PredictorLearnReason mLearnReason; + }; + nsCOMPtr<nsIURI> mTargetURI; + nsCOMPtr<nsIURI> mSourceURI; + nsCOMPtr<nsINetworkPredictorVerifier> mVerifier; + TimeStamp mStartTime; + uint8_t mStackCount; + RefPtr<Predictor> mPredictor; + }; + + class CacheabilityAction : public nsICacheEntryOpenCallback, + public nsICacheEntryMetaDataVisitor { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICACHEENTRYOPENCALLBACK + NS_DECL_NSICACHEENTRYMETADATAVISITOR + + CacheabilityAction(nsIURI* targetURI, uint32_t httpStatus, + const nsCString& method, bool isTracking, bool couldVary, + bool isNoStore, Predictor* predictor) + : mTargetURI(targetURI), + mHttpStatus(httpStatus), + mMethod(method), + mIsTracking(isTracking), + mCouldVary(couldVary), + mIsNoStore(isNoStore), + mPredictor(predictor) {} + + private: + virtual ~CacheabilityAction() = default; + + nsCOMPtr<nsIURI> mTargetURI; + uint32_t mHttpStatus; + nsCString mMethod; + bool mIsTracking; + bool mCouldVary; + bool mIsNoStore; + RefPtr<Predictor> mPredictor; + nsTArray<nsCString> mKeysToCheck; + nsTArray<nsCString> mValuesToCheck; + }; + + class Resetter : public nsICacheEntryOpenCallback, + public nsICacheEntryMetaDataVisitor, + public nsICacheStorageVisitor { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICACHEENTRYOPENCALLBACK + NS_DECL_NSICACHEENTRYMETADATAVISITOR + NS_DECL_NSICACHESTORAGEVISITOR + + explicit Resetter(Predictor* predictor); + + private: + virtual ~Resetter() = default; + + void Complete(); + + uint32_t mEntriesToVisit; + nsTArray<nsCString> mKeysToDelete; + RefPtr<Predictor> mPredictor; + nsTArray<nsCOMPtr<nsIURI>> mURIsToVisit; + nsTArray<nsCOMPtr<nsILoadContextInfo>> mInfosToVisit; + }; + + class SpaceCleaner : public nsICacheEntryMetaDataVisitor { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICACHEENTRYMETADATAVISITOR + + explicit SpaceCleaner(Predictor* predictor) + : mLRUStamp(0), mLRUKeyToDelete(nullptr), mPredictor(predictor) {} + + void Finalize(nsICacheEntry* entry); + + private: + virtual ~SpaceCleaner() = default; + uint32_t mLRUStamp; + const char* mLRUKeyToDelete; + nsTArray<nsCString> mLongKeysToDelete; + RefPtr<Predictor> mPredictor; + }; + + class PrefetchListener : public nsIStreamListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + PrefetchListener(nsINetworkPredictorVerifier* verifier, nsIURI* uri, + Predictor* predictor) + : mVerifier(verifier), mURI(uri), mPredictor(predictor) {} + + private: + virtual ~PrefetchListener() = default; + + nsCOMPtr<nsINetworkPredictorVerifier> mVerifier; + nsCOMPtr<nsIURI> mURI; + RefPtr<Predictor> mPredictor; + TimeStamp mStartTime; + }; + + // Observer-related stuff + nsresult InstallObserver(); + void RemoveObserver(); + + // Service startup utilities + void MaybeCleanupOldDBFiles(); + + // The guts of prediction + + // This is the top-level driver for doing any prediction that needs + // information from the cache. Returns true if any predictions were queued up + // * reason - What kind of prediction this is/why this prediction is + // happening (pageload, startup) + // * entry - the cache entry with the information we need + // * isNew - whether or not the cache entry is brand new and empty + // * fullUri - whether we are doing predictions based on a full page URI, or + // just the origin of the page + // * targetURI - the URI that we are predicting based upon - IOW, the URI + // that is being loaded or being redirected to + // * verifier - used for testing to verify the expected predictions happen + // * stackCount - used to ensure we don't recurse too far trying to find the + // final redirection in a redirect chain + bool PredictInternal(PredictorPredictReason reason, nsICacheEntry* entry, + bool isNew, bool fullUri, nsIURI* targetURI, + nsINetworkPredictorVerifier* verifier, + uint8_t stackCount); + + // Used when predicting because the user's mouse hovered over a link + // * targetURI - the URI target of the link + // * sourceURI - the URI of the page on which the link appears + // * originAttributes - the originAttributes for this prediction + // * verifier - used for testing to verify the expected predictions happen + void PredictForLink(nsIURI* targetURI, nsIURI* sourceURI, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier); + + // Used when predicting because a page is being loaded (which may include + // being the target of a redirect). All arguments are the same as for + // PredictInternal. Returns true if any predictions were queued up. + bool PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI, + uint8_t stackCount, bool fullUri, + nsINetworkPredictorVerifier* verifier); + + // Used when predicting pages that will be used near browser startup. All + // arguments are the same as for PredictInternal. Returns true if any + // predictions were queued up. + bool PredictForStartup(nsICacheEntry* entry, bool fullUri, + nsINetworkPredictorVerifier* verifier); + + // Utilities related to prediction + + // Used to update our rolling load count (how many of the last n loads was a + // partular resource loaded on?) + // * entry - cache entry of page we're loading + // * flags - value that contains our rolling count as the top 20 bits (but + // we may use fewer than those 20 bits for calculations) + // * key - metadata key that we will update on entry + // * hitCount - part of the metadata we need to preserve + // * lastHit - part of the metadata we need to preserve + void UpdateRollingLoadCount(nsICacheEntry* entry, const uint32_t flags, + const char* key, const uint32_t hitCount, + const uint32_t lastHit); + + // Used to calculate how much to degrade our confidence for all resources + // on a particular page, because of how long ago the most recent load of that + // page was. Returns a value between 0 (very recent most recent load) and 100 + // (very distant most recent load) + // * lastLoad - time stamp of most recent load of a page + int32_t CalculateGlobalDegradation(uint32_t lastLoad); + + // Used to calculate how confident we are that a particular resource will be + // used. Returns a value between 0 (no confidence) and 100 (very confident) + // * hitCount - number of times this resource has been seen when loading + // this page + // * hitsPossible - number of times this page has been loaded + // * lastHit - timestamp of the last time this resource was seen when + // loading this page + // * lastPossible - timestamp of the last time this page was loaded + // * globalDegradation - value calculated by CalculateGlobalDegradation for + // this page + int32_t CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible, + uint32_t lastHit, uint32_t lastPossible, + int32_t globalDegradation); + + // Used to calculate all confidence values for all resources associated with a + // page. + // * entry - the cache entry with all necessary information about this page + // * referrer - the URI that we are loading (may be null) + // * lastLoad - timestamp of the last time this page was loaded + // * loadCount - number of times this page has been loaded + // * gloablDegradation - value calculated by CalculateGlobalDegradation for + // this page + // * fullUri - whether we're predicting for a full URI or origin-only + void CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer, + uint32_t lastLoad, uint32_t loadCount, + int32_t globalDegradation, bool fullUri); + + enum PrefetchIgnoreReason { + PREFETCH_OK, + NOT_FULL_URI, + NO_REFERRER, + MISSED_A_LOAD, + PREFETCH_DISABLED, + PREFETCH_DISABLED_VIA_COUNT, + CONFIDENCE_TOO_LOW + }; + + // Used to prepare any necessary prediction for a resource on a page + // * confidence - value calculated by CalculateConfidence for this resource + // * flags - the flags taken from the resource + // * uri - the ascii spec of the URI of the resource + void SetupPrediction(int32_t confidence, uint32_t flags, const nsCString& uri, + PrefetchIgnoreReason reason); + + // Used to kick off a prefetch from RunPredictions if necessary + // * uri - the URI to prefetch + // * referrer - the URI of the referring page + // * originAttributes - the originAttributes of this prefetch + // * verifier - used for testing to ensure the expected prefetch happens + nsresult Prefetch(nsIURI* uri, nsIURI* referrer, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier); + + // Used to actually perform any predictions set up via SetupPrediction. + // Returns true if any predictions were performed. + // * referrer - the URI we are predicting from + // * originAttributs - the originAttributes we are predicting from + // * verifier - used for testing to ensure the expected predictions happen + bool RunPredictions(nsIURI* referrer, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier); + + // Used to guess whether a page will redirect to another page or not. Returns + // true if a redirection is likely. + // * entry - cache entry with all necessary information about this page + // * loadCount - number of times this page has been loaded + // * lastLoad - timestamp of the last time this page was loaded + // * globalDegradation - value calculated by CalculateGlobalDegradation for + // this page + // * redirectURI - if this returns true, the URI that is likely to be + // redirected to, otherwise null + bool WouldRedirect(nsICacheEntry* entry, uint32_t loadCount, + uint32_t lastLoad, int32_t globalDegradation, + nsIURI** redirectURI); + + // The guts of learning information + + // This is the top-level driver for doing any updating of our information in + // the cache + // * reason - why this learn is happening (pageload, startup, redirect) + // * entry - the cache entry with the information we need + // * isNew - whether or not the cache entry is brand new and empty + // * fullUri - whether we are doing predictions based on a full page URI, or + // just the origin of the page + // * targetURI - the URI that we are adding to our data - most often a + // resource loaded by a page the user navigated to + // * sourceURI - the URI that caused targetURI to be loaded, usually the + // page the user navigated to + void LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry, + bool isNew, bool fullUri, nsIURI* targetURI, + nsIURI* sourceURI); + + // Used when learning about a resource loaded by a page + // * entry - the cache entry with information that needs updating + // * targetURI - the URI of the resource that was loaded by the page + void LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI); + + // Used when learning about a redirect from one page to another + // * entry - the cache entry of the page that was redirected from + // * targetURI - the URI of the redirect target + void LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI); + + // Used to learn about pages loaded close to browser startup. This results in + // LearnForStartup being called if we are, in fact, near browser startup + // * uri - the URI of a page that has been loaded (may not have been near + // browser startup) + // * fullUri - true if this is a full page uri, false if it's an origin + // * originAttributes - the originAttributes for this learning. + void MaybeLearnForStartup(nsIURI* uri, bool fullUri, + const OriginAttributes& originAttributes); + + // Used in conjunction with MaybeLearnForStartup to learn about pages loaded + // close to browser startup + // * entry - the cache entry that stores the startup page list + // * targetURI - the URI of a page that was loaded near browser startup + void LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI); + + // Used to parse the data we store in cache metadata + // * key - the cache metadata key + // * value - the cache metadata value + // * uri - (out) the ascii spec of the URI this metadata entry was about + // * hitCount - (out) the number of times this URI has been seen + // * lastHit - (out) timestamp of the last time this URI was seen + // * flags - (out) flags for this metadata entry + bool ParseMetaDataEntry(const char* key, const char* value, nsCString& uri, + uint32_t& hitCount, uint32_t& lastHit, + uint32_t& flags); + + // Used to update whether a particular URI was cacheable or not. + // sourceURI and targetURI are the same as the arguments to Learn + // and httpStatus is the status code we got while loading targetURI. + void UpdateCacheabilityInternal(nsIURI* sourceURI, nsIURI* targetURI, + uint32_t httpStatus, const nsCString& method, + const OriginAttributes& originAttributes, + bool isTracking, bool couldVary, + bool isNoStore); + + // Gets the pref value and clamps it within the acceptable range. + uint32_t ClampedPrefetchRollingLoadCount(); + + // Our state + bool mInitialized{false}; + + nsTArray<nsCString> mKeysToOperateOn; + nsTArray<nsCString> mValuesToOperateOn; + + nsCOMPtr<nsICacheStorageService> mCacheStorageService; + + nsCOMPtr<nsISpeculativeConnect> mSpeculativeService; + + nsCOMPtr<nsIURI> mStartupURI; + uint32_t mStartupTime{0}; + uint32_t mLastStartupTime{0}; + int32_t mStartupCount{1}; + + nsCOMPtr<nsIDNSService> mDnsService; + + RefPtr<DNSListener> mDNSListener; + + nsTArray<nsCOMPtr<nsIURI>> mPrefetches; + nsTArray<nsCOMPtr<nsIURI>> mPreconnects; + nsTArray<nsCOMPtr<nsIURI>> mPreresolves; + + static Predictor* sSelf; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_Predictor_h diff --git a/netwerk/base/PrivateBrowsingChannel.h b/netwerk/base/PrivateBrowsingChannel.h new file mode 100644 index 0000000000..a2e224f092 --- /dev/null +++ b/netwerk/base/PrivateBrowsingChannel.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_PrivateBrowsingChannel_h__ +#define mozilla_net_PrivateBrowsingChannel_h__ + +#include "nsIPrivateBrowsingChannel.h" +#include "nsCOMPtr.h" +#include "nsILoadGroup.h" +#include "nsILoadContext.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIInterfaceRequestor.h" +#include "nsNetUtil.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace net { + +template <class Channel> +class PrivateBrowsingChannel : public nsIPrivateBrowsingChannel { + public: + PrivateBrowsingChannel() + : mPrivateBrowsingOverriden(false), mPrivateBrowsing(false) {} + + NS_IMETHOD SetPrivate(bool aPrivate) override { + // Make sure that we don't have a load context + // This is a fatal error in debug builds, and a runtime error in release + // builds. + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(static_cast<Channel*>(this), loadContext); + MOZ_ASSERT(!loadContext); + if (loadContext) { + return NS_ERROR_FAILURE; + } + + mPrivateBrowsingOverriden = true; + mPrivateBrowsing = aPrivate; + return NS_OK; + } + + NS_IMETHOD GetIsChannelPrivate(bool* aResult) override { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPrivateBrowsing; + return NS_OK; + } + + NS_IMETHOD IsPrivateModeOverriden(bool* aValue, bool* aResult) override { + NS_ENSURE_ARG_POINTER(aValue); + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPrivateBrowsingOverriden; + if (mPrivateBrowsingOverriden) { + *aValue = mPrivateBrowsing; + } + return NS_OK; + } + + // Must be called every time the channel's callbacks or loadGroup is updated + void UpdatePrivateBrowsing() { + // once marked as private we never go un-private + if (mPrivateBrowsing) { + return; + } + + auto channel = static_cast<Channel*>(this); + + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(channel, loadContext); + if (loadContext) { + mPrivateBrowsing = loadContext->UsePrivateBrowsing(); + return; + } + + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + OriginAttributes attrs = loadInfo->GetOriginAttributes(); + mPrivateBrowsing = attrs.mPrivateBrowsingId > 0; + } + + bool CanSetCallbacks(nsIInterfaceRequestor* aCallbacks) const { + // Make sure that the private bit override flag is not set. + // This is a fatal error in debug builds, and a runtime error in release + // builds. + if (!aCallbacks) { + return true; + } + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(aCallbacks); + if (!loadContext) { + return true; + } + MOZ_ASSERT(!mPrivateBrowsingOverriden); + return !mPrivateBrowsingOverriden; + } + + bool CanSetLoadGroup(nsILoadGroup* aLoadGroup) const { + // Make sure that the private bit override flag is not set. + // This is a fatal error in debug builds, and a runtime error in release + // builds. + if (!aLoadGroup) { + return true; + } + nsCOMPtr<nsIInterfaceRequestor> callbacks; + aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + // From this point on, we just hand off the work to CanSetCallbacks, + // because the logic is exactly the same. + return CanSetCallbacks(callbacks); + } + + protected: + bool mPrivateBrowsingOverriden; + bool mPrivateBrowsing; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/ProtocolHandlerInfo.cpp b/netwerk/base/ProtocolHandlerInfo.cpp new file mode 100644 index 0000000000..432ff965ce --- /dev/null +++ b/netwerk/base/ProtocolHandlerInfo.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ProtocolHandlerInfo.h" +#include "StaticComponents.h" +#include "nsIProtocolHandler.h" + +namespace mozilla::net { + +uint32_t ProtocolHandlerInfo::StaticProtocolFlags() const { + uint32_t flags = mInner.match( + [&](const xpcom::StaticProtocolHandler* handler) { + return handler->mProtocolFlags; + }, + [&](const RuntimeProtocolHandler& handler) { + return handler.mProtocolFlags; + }); +#if !IS_ORIGIN_IS_FULL_SPEC_DEFINED + MOZ_RELEASE_ASSERT(!(flags & nsIProtocolHandler::ORIGIN_IS_FULL_SPEC), + "ORIGIN_IS_FULL_SPEC is unsupported but used"); +#endif + return flags; +} + +int32_t ProtocolHandlerInfo::DefaultPort() const { + return mInner.match( + [&](const xpcom::StaticProtocolHandler* handler) { + return handler->mDefaultPort; + }, + [&](const RuntimeProtocolHandler& handler) { + return handler.mDefaultPort; + }); +} + +nsresult ProtocolHandlerInfo::DynamicProtocolFlags(nsIURI* aURI, + uint32_t* aFlags) const { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + // If we're querying dynamic flags, we'll need to fetch the actual xpcom + // component in order to check them. + if (HasDynamicFlags()) { + nsCOMPtr<nsIProtocolHandler> handler = Handler(); + if (nsCOMPtr<nsIProtocolHandlerWithDynamicFlags> dynamic = + do_QueryInterface(handler)) { + nsresult rv = dynamic->GetFlagsForURI(aURI, aFlags); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_DIAGNOSTIC_ASSERT( + (StaticProtocolFlags() & ~nsIProtocolHandler::DYNAMIC_URI_FLAGS) == + (*aFlags & ~nsIProtocolHandler::DYNAMIC_URI_FLAGS), + "only DYNAMIC_URI_FLAGS may be changed by a " + "nsIProtocolHandlerWithDynamicFlags implementation"); + return NS_OK; + } + } + + // Otherwise, just check against static flags. + *aFlags = StaticProtocolFlags(); + return NS_OK; +} + +bool ProtocolHandlerInfo::HasDynamicFlags() const { + return mInner.match( + [&](const xpcom::StaticProtocolHandler* handler) { + return handler->mHasDynamicFlags; + }, + [&](const RuntimeProtocolHandler&) { return false; }); +} + +already_AddRefed<nsIProtocolHandler> ProtocolHandlerInfo::Handler() const { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIProtocolHandler> retval; + mInner.match( + [&](const xpcom::StaticProtocolHandler* handler) { + retval = handler->Module().GetService(); + }, + [&](const RuntimeProtocolHandler& handler) { + retval = handler.mHandler.get(); + }); + return retval.forget(); +} + +} // namespace mozilla::net diff --git a/netwerk/base/ProtocolHandlerInfo.h b/netwerk/base/ProtocolHandlerInfo.h new file mode 100644 index 0000000000..337dbddcfc --- /dev/null +++ b/netwerk/base/ProtocolHandlerInfo.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_ProtocolHandlerInfo_h +#define mozilla_net_ProtocolHandlerInfo_h + +#include "mozilla/Variant.h" +#include "nsProxyRelease.h" +#include "nsIProtocolHandler.h" + +namespace mozilla { +namespace xpcom { +struct StaticProtocolHandler; +} + +namespace net { + +struct RuntimeProtocolHandler { + nsMainThreadPtrHandle<nsIProtocolHandler> mHandler; + uint32_t mProtocolFlags; + int32_t mDefaultPort; +}; + +// Information about a specific protocol handler. +class ProtocolHandlerInfo { + public: + explicit ProtocolHandlerInfo(const xpcom::StaticProtocolHandler& aStatic) + : mInner(AsVariant(&aStatic)) {} + explicit ProtocolHandlerInfo(RuntimeProtocolHandler aDynamic) + : mInner(AsVariant(std::move(aDynamic))) {} + + // Returns the statically known protocol-specific flags. + // See `nsIProtocolHandler` for valid values. + uint32_t StaticProtocolFlags() const; + + // The port that this protocol normally uses. + // If a port does not make sense for the protocol (e.g., "about:") then -1 + // will be returned. + int32_t DefaultPort() const; + + // If true, `DynamicProtocolFlags()` may return a different value than + // `StaticProtocolFlags()` for flags in `DYNAMIC_URI_FLAGS`, due to a + // `nsIProtocolHandlerWithDynamicFlags` implementation. + bool HasDynamicFlags() const; + + // Like `StaticProtocolFlags()` but also checks + // `nsIProtocolHandlerWithDynamicFlags` for uri-specific flags. + // + // NOTE: Only safe to call from the main thread. + nsresult DynamicProtocolFlags(nsIURI* aURI, uint32_t* aFlags) const + MOZ_REQUIRES(sMainThreadCapability); + + // Get the main-thread-only nsIProtocolHandler instance. + already_AddRefed<nsIProtocolHandler> Handler() const + MOZ_REQUIRES(sMainThreadCapability); + + private: + Variant<const xpcom::StaticProtocolHandler*, RuntimeProtocolHandler> mInner; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ProtocolHandlerInfo_h diff --git a/netwerk/base/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp new file mode 100644 index 0000000000..a93d040a14 --- /dev/null +++ b/netwerk/base/ProxyAutoConfig.cpp @@ -0,0 +1,943 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ProxyAutoConfig.h" +#include "nsICancelable.h" +#include "nsIDNSListener.h" +#include "nsIDNSRecord.h" +#include "nsIDNSService.h" +#include "nsINamed.h" +#include "nsThreadUtils.h" +#include "nsIConsoleService.h" +#include "nsIURLParser.h" +#include "nsJSUtils.h" +#include "jsfriendapi.h" +#include "js/CallAndConstruct.h" // JS_CallFunctionName +#include "js/CompilationAndEvaluation.h" // JS::Compile +#include "js/ContextOptions.h" +#include "js/Initialization.h" +#include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_GetProperty +#include "js/PropertySpec.h" +#include "js/SourceText.h" // JS::Source{Ownership,Text} +#include "js/Utility.h" +#include "js/Warnings.h" // JS::SetWarningReporter +#include "prnetdb.h" +#include "nsITimer.h" +#include "mozilla/Atomics.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/net/DNS.h" +#include "mozilla/net/SocketProcessChild.h" +#include "mozilla/net/SocketProcessParent.h" +#include "mozilla/net/ProxyAutoConfigChild.h" +#include "mozilla/net/ProxyAutoConfigParent.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit +#include "nsServiceManagerUtils.h" +#include "nsNetCID.h" + +#if defined(XP_MACOSX) +# include "nsMacUtilsImpl.h" +#endif + +#include "XPCSelfHostedShmem.h" + +namespace mozilla { +namespace net { + +// These are some global helper symbols the PAC format requires that we provide +// that are initialized as part of the global javascript context used for PAC +// evaluations. Additionally dnsResolve(host) and myIpAddress() are supplied in +// the same context but are implemented as c++ helpers. alert(msg) is similarly +// defined. +// +// Per ProxyAutoConfig::Init, this data must be ASCII. + +static const char sAsciiPacUtils[] = +#include "ascii_pac_utils.inc" + ; + +// sRunning is defined for the helper functions only while the +// Javascript engine is running and the PAC object cannot be deleted +// or reset. +static Atomic<uint32_t, Relaxed>& RunningIndex() { + static Atomic<uint32_t, Relaxed> sRunningIndex(0xdeadbeef); + return sRunningIndex; +} +static ProxyAutoConfig* GetRunning() { + MOZ_ASSERT(RunningIndex() != 0xdeadbeef); + return static_cast<ProxyAutoConfig*>(PR_GetThreadPrivate(RunningIndex())); +} + +static void SetRunning(ProxyAutoConfig* arg) { + MOZ_ASSERT(RunningIndex() != 0xdeadbeef); + MOZ_DIAGNOSTIC_ASSERT_IF(!arg, GetRunning() != nullptr); + MOZ_DIAGNOSTIC_ASSERT_IF(arg, GetRunning() == nullptr); + PR_SetThreadPrivate(RunningIndex(), arg); +} + +// The PACResolver is used for dnsResolve() +class PACResolver final : public nsIDNSListener, + public nsITimerCallback, + public nsINamed { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit PACResolver(nsIEventTarget* aTarget) + : mStatus(NS_ERROR_FAILURE), + mMainThreadEventTarget(aTarget), + mMutex("PACResolver::Mutex") {} + + // nsIDNSListener + NS_IMETHOD OnLookupComplete(nsICancelable* request, nsIDNSRecord* record, + nsresult status) override { + nsCOMPtr<nsITimer> timer; + { + MutexAutoLock lock(mMutex); + timer.swap(mTimer); + mRequest = nullptr; + } + + if (timer) { + timer->Cancel(); + } + + mStatus = status; + mResponse = record; + return NS_OK; + } + + // nsITimerCallback + NS_IMETHOD Notify(nsITimer* timer) override { + nsCOMPtr<nsICancelable> request; + { + MutexAutoLock lock(mMutex); + request.swap(mRequest); + mTimer = nullptr; + } + if (request) { + request->Cancel(NS_ERROR_NET_TIMEOUT); + } + return NS_OK; + } + + // nsINamed + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("PACResolver"); + return NS_OK; + } + + nsresult mStatus; + nsCOMPtr<nsICancelable> mRequest; + nsCOMPtr<nsIDNSRecord> mResponse; + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsIEventTarget> mMainThreadEventTarget; + Mutex mMutex MOZ_UNANNOTATED; + + private: + ~PACResolver() = default; +}; +NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback, nsINamed) + +static void PACLogToConsole(nsString& aMessage) { + if (XRE_IsSocketProcess()) { + auto task = [message(aMessage)]() { + SocketProcessChild* child = SocketProcessChild::GetSingleton(); + if (child) { + Unused << child->SendOnConsoleMessage(message); + } + }; + if (NS_IsMainThread()) { + task(); + } else { + NS_DispatchToMainThread(NS_NewRunnableFunction("PACLogToConsole", task)); + } + return; + } + + nsCOMPtr<nsIConsoleService> consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!consoleService) return; + + consoleService->LogStringMessage(aMessage.get()); +} + +// Javascript errors and warnings are logged to the main error console +static void PACLogErrorOrWarning(const nsAString& aKind, + JSErrorReport* aReport) { + nsString formattedMessage(u"PAC Execution "_ns); + formattedMessage += aKind; + formattedMessage += u": "_ns; + if (aReport->message()) { + formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str())); + } + formattedMessage += u" ["_ns; + formattedMessage.Append(aReport->linebuf(), aReport->linebufLength()); + formattedMessage += u"]"_ns; + PACLogToConsole(formattedMessage); +} + +static void PACWarningReporter(JSContext* aCx, JSErrorReport* aReport) { + MOZ_ASSERT(aReport); + MOZ_ASSERT(aReport->isWarning()); + + PACLogErrorOrWarning(u"Warning"_ns, aReport); +} + +class MOZ_STACK_CLASS AutoPACErrorReporter { + JSContext* mCx; + + public: + explicit AutoPACErrorReporter(JSContext* aCx) : mCx(aCx) {} + ~AutoPACErrorReporter() { + if (!JS_IsExceptionPending(mCx)) { + return; + } + JS::ExceptionStack exnStack(mCx); + if (!JS::StealPendingExceptionStack(mCx, &exnStack)) { + return; + } + + JS::ErrorReportBuilder report(mCx); + if (!report.init(mCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) { + JS_ClearPendingException(mCx); + return; + } + + PACLogErrorOrWarning(u"Error"_ns, report.report()); + } +}; + +// timeout of 0 means the normal necko timeout strategy, otherwise the dns +// request will be canceled after aTimeout milliseconds +static bool PACResolve(const nsACString& aHostName, NetAddr* aNetAddr, + unsigned int aTimeout) { + if (!GetRunning()) { + NS_WARNING("PACResolve without a running ProxyAutoConfig object"); + return false; + } + + return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout); +} + +ProxyAutoConfig::ProxyAutoConfig() + +{ + MOZ_COUNT_CTOR(ProxyAutoConfig); +} + +bool ProxyAutoConfig::ResolveAddress(const nsACString& aHostName, + NetAddr* aNetAddr, unsigned int aTimeout) { + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + if (!dns) return false; + + RefPtr<PACResolver> helper = new PACResolver(mMainThreadEventTarget); + OriginAttributes attrs; + + // When the PAC script attempts to resolve a domain, we must make sure we + // don't use TRR, otherwise the TRR channel might also attempt to resolve + // a name and we'll have a deadlock. + nsIDNSService::DNSFlags flags = + nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + nsIDNSService::GetFlagsFromTRRMode(nsIRequest::TRR_DISABLED_MODE); + + if (NS_FAILED(dns->AsyncResolveNative( + aHostName, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags, nullptr, + helper, GetCurrentSerialEventTarget(), attrs, + getter_AddRefs(helper->mRequest)))) { + return false; + } + + if (aTimeout && helper->mRequest) { + if (!mTimer) mTimer = NS_NewTimer(); + if (mTimer) { + mTimer->SetTarget(mMainThreadEventTarget); + mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT); + helper->mTimer = mTimer; + } + } + + // Spin the event loop of the pac thread until lookup is complete. + // nsPACman is responsible for keeping a queue and only allowing + // one PAC execution at a time even when it is called re-entrantly. + SpinEventLoopUntil("ProxyAutoConfig::ResolveAddress"_ns, [&, helper, this]() { + if (!helper->mRequest) { + return true; + } + if (this->mShutdown) { + NS_WARNING("mShutdown set with PAC request not cancelled"); + MOZ_ASSERT(NS_FAILED(helper->mStatus)); + return true; + } + return false; + }); + + if (NS_FAILED(helper->mStatus)) { + return false; + } + + nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(helper->mResponse); + return !(!rec || NS_FAILED(rec->GetNextAddr(0, aNetAddr))); +} + +static bool PACResolveToString(const nsACString& aHostName, + nsCString& aDottedDecimal, + unsigned int aTimeout) { + NetAddr netAddr; + if (!PACResolve(aHostName, &netAddr, aTimeout)) return false; + + char dottedDecimal[128]; + if (!netAddr.ToStringBuffer(dottedDecimal, sizeof(dottedDecimal))) { + return false; + } + + aDottedDecimal.Assign(dottedDecimal); + return true; +} + +// dnsResolve(host) javascript implementation +static bool PACDnsResolve(JSContext* cx, unsigned int argc, JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (NS_IsMainThread()) { + NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?"); + return false; + } + + if (!args.requireAtLeast(cx, "dnsResolve", 1)) return false; + + // Previously we didn't check the type of the argument, so just converted it + // to string. A badly written PAC file oculd pass null or undefined here + // which could lead to odd results if there are any hosts called "null" + // on the network. See bug 1724345 comment 6. + if (!args[0].isString()) { + args.rval().setNull(); + return true; + } + + JS::Rooted<JSString*> arg1(cx); + arg1 = args[0].toString(); + + nsAutoJSString hostName; + nsAutoCString dottedDecimal; + + if (!hostName.init(cx, arg1)) return false; + if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) { + JSString* dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + args.rval().setString(dottedDecimalString); + } else { + args.rval().setNull(); + } + + return true; +} + +// myIpAddress() javascript implementation +static bool PACMyIpAddress(JSContext* cx, unsigned int argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (NS_IsMainThread()) { + NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?"); + return false; + } + + if (!GetRunning()) { + NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object"); + return false; + } + + return GetRunning()->MyIPAddress(args); +} + +// proxyAlert(msg) javascript implementation +static bool PACProxyAlert(JSContext* cx, unsigned int argc, JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "alert", 1)) return false; + + JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0])); + if (!arg1) return false; + + nsAutoJSString message; + if (!message.init(cx, arg1)) return false; + + nsAutoString alertMessage; + alertMessage.AssignLiteral(u"PAC-alert: "); + alertMessage.Append(message); + PACLogToConsole(alertMessage); + + args.rval().setUndefined(); /* return undefined */ + return true; +} + +static const JSFunctionSpec PACGlobalFunctions[] = { + JS_FN("dnsResolve", PACDnsResolve, 1, 0), + + // a global "var pacUseMultihomedDNS = true;" will change behavior + // of myIpAddress to actively use DNS + JS_FN("myIpAddress", PACMyIpAddress, 0, 0), + JS_FN("alert", PACProxyAlert, 1, 0), JS_FS_END}; + +// JSContextWrapper is a c++ object that manages the context for the JS engine +// used on the PAC thread. It is initialized and destroyed on the PAC thread. +class JSContextWrapper { + public: + static JSContextWrapper* Create(uint32_t aExtraHeapSize) { + JSContext* cx = JS_NewContext(JS::DefaultHeapMaxBytes + aExtraHeapSize); + if (NS_WARN_IF(!cx)) return nullptr; + + JS::ContextOptionsRef(cx).setDisableIon().setDisableEvalSecurityChecks(); + + JSContextWrapper* entry = new JSContextWrapper(cx); + if (NS_FAILED(entry->Init())) { + delete entry; + return nullptr; + } + + return entry; + } + + JSContext* Context() const { return mContext; } + + JSObject* Global() const { return mGlobal; } + + ~JSContextWrapper() { + mGlobal = nullptr; + + MOZ_COUNT_DTOR(JSContextWrapper); + + if (mContext) { + JS_DestroyContext(mContext); + } + } + + void SetOK() { mOK = true; } + + bool IsOK() { return mOK; } + + private: + JSContext* mContext; + JS::PersistentRooted<JSObject*> mGlobal; + bool mOK; + + static const JSClass sGlobalClass; + + explicit JSContextWrapper(JSContext* cx) + : mContext(cx), mGlobal(cx, nullptr), mOK(false) { + MOZ_COUNT_CTOR(JSContextWrapper); + } + + nsresult Init() { + /* + * Not setting this will cause JS_CHECK_RECURSION to report false + * positives + */ + JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024); + + JS::SetWarningReporter(mContext, PACWarningReporter); + + // When available, set the self-hosted shared memory to be read, so that + // we can decode the self-hosted content instead of parsing it. + { + auto& shm = xpc::SelfHostedShmem::GetSingleton(); + JS::SelfHostedCache selfHostedContent = shm.Content(); + + if (!JS::InitSelfHostedCode(mContext, selfHostedContent)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + JS::RealmOptions options; + options.creationOptions().setNewCompartmentInSystemZone(); + options.behaviors() + .setClampAndJitterTime(false) + .setReduceTimerPrecisionCallerType( + RTPCallerTypeToToken(RTPCallerType::Normal)); + mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr, + JS::DontFireOnNewGlobalHook, options); + if (!mGlobal) { + JS_ClearPendingException(mContext); + return NS_ERROR_OUT_OF_MEMORY; + } + JS::Rooted<JSObject*> global(mContext, mGlobal); + + JSAutoRealm ar(mContext, global); + AutoPACErrorReporter aper(mContext); + if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) { + return NS_ERROR_FAILURE; + } + + JS_FireOnNewGlobalObject(mContext, global); + + return NS_OK; + } +}; + +const JSClass JSContextWrapper::sGlobalClass = {"PACResolutionThreadGlobal", + JSCLASS_GLOBAL_FLAGS, + &JS::DefaultGlobalClassOps}; + +void ProxyAutoConfig::SetThreadLocalIndex(uint32_t index) { + RunningIndex() = index; +} + +nsresult ProxyAutoConfig::ConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, + bool aIncludePath, + uint32_t aExtraHeapSize, + nsISerialEventTarget* aEventTarget) { + mShutdown = false; // Shutdown needs to be called prior to destruction + + mPACURI = aPACURI; + + // The full PAC script data is the concatenation of 1) the various functions + // exposed to PAC scripts in |sAsciiPacUtils| and 2) the user-provided PAC + // script data. Historically this was single-byte Latin-1 text (usually just + // ASCII, but bug 296163 has a real-world Latin-1 example). We now support + // UTF-8 if the full data validates as UTF-8, before falling back to Latin-1. + // (Technically this is a breaking change: intentional Latin-1 scripts that + // happen to be valid UTF-8 may have different behavior. We assume such cases + // are vanishingly rare.) + // + // Supporting both UTF-8 and Latin-1 requires that the functions exposed to + // PAC scripts be both UTF-8- and Latin-1-compatible: that is, they must be + // ASCII. + mConcatenatedPACData = sAsciiPacUtils; + if (!mConcatenatedPACData.Append(aPACScriptData, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mIncludePath = aIncludePath; + mExtraHeapSize = aExtraHeapSize; + mMainThreadEventTarget = aEventTarget; + + if (!GetRunning()) return SetupJS(); + + mJSNeedsSetup = true; + return NS_OK; +} + +nsresult ProxyAutoConfig::SetupJS() { + mJSNeedsSetup = false; + MOZ_DIAGNOSTIC_ASSERT(!GetRunning(), "JIT is running"); + if (GetRunning()) { + return NS_ERROR_ALREADY_INITIALIZED; + } + +#if defined(XP_MACOSX) + nsMacUtilsImpl::EnableTCSMIfAvailable(); +#endif + + delete mJSContext; + mJSContext = nullptr; + + if (mConcatenatedPACData.IsEmpty()) return NS_ERROR_FAILURE; + + NS_GetCurrentThread()->SetCanInvokeJS(true); + + mJSContext = JSContextWrapper::Create(mExtraHeapSize); + if (!mJSContext) return NS_ERROR_FAILURE; + + JSContext* cx = mJSContext->Context(); + JSAutoRealm ar(cx, mJSContext->Global()); + AutoPACErrorReporter aper(cx); + + // check if this is a data: uri so that we don't spam the js console with + // huge meaningless strings. this is not on the main thread, so it can't + // use nsIURI scheme methods + bool isDataURI = + nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5); + + SetRunning(this); + + JS::Rooted<JSObject*> global(cx, mJSContext->Global()); + + auto CompilePACScript = [this](JSContext* cx) -> JSScript* { + JS::CompileOptions options(cx); + options.setSkipFilenameValidation(true); + options.setFileAndLine(this->mPACURI.get(), 1); + + // Per ProxyAutoConfig::Init, compile as UTF-8 if the full data is UTF-8, + // and otherwise inflate Latin-1 to UTF-16 and compile that. + const char* scriptData = this->mConcatenatedPACData.get(); + size_t scriptLength = this->mConcatenatedPACData.Length(); + if (mozilla::IsUtf8(mozilla::Span(scriptData, scriptLength))) { + JS::SourceText<Utf8Unit> srcBuf; + if (!srcBuf.init(cx, scriptData, scriptLength, + JS::SourceOwnership::Borrowed)) { + return nullptr; + } + + return JS::Compile(cx, options, srcBuf); + } + + // nsReadableUtils.h says that "ASCII" is a misnomer "for legacy reasons", + // and this handles not just ASCII but Latin-1 too. + NS_ConvertASCIItoUTF16 inflated(this->mConcatenatedPACData); + + JS::SourceText<char16_t> source; + if (!source.init(cx, inflated.get(), inflated.Length(), + JS::SourceOwnership::Borrowed)) { + return nullptr; + } + + return JS::Compile(cx, options, source); + }; + + JS::Rooted<JSScript*> script(cx, CompilePACScript(cx)); + if (!script || !JS_ExecuteScript(cx, script)) { + nsString alertMessage(u"PAC file failed to install from "_ns); + if (isDataURI) { + alertMessage += u"data: URI"_ns; + } else { + alertMessage += NS_ConvertUTF8toUTF16(mPACURI); + } + PACLogToConsole(alertMessage); + SetRunning(nullptr); + return NS_ERROR_FAILURE; + } + SetRunning(nullptr); + + mJSContext->SetOK(); + nsString alertMessage(u"PAC file installed from "_ns); + if (isDataURI) { + alertMessage += u"data: URI"_ns; + } else { + alertMessage += NS_ConvertUTF8toUTF16(mPACURI); + } + PACLogToConsole(alertMessage); + + // we don't need these now + mConcatenatedPACData.Truncate(); + mPACURI.Truncate(); + + return NS_OK; +} + +void ProxyAutoConfig::GetProxyForURIWithCallback( + const nsACString& aTestURI, const nsACString& aTestHost, + std::function<void(nsresult aStatus, const nsACString& aResult)>&& + aCallback) { + nsAutoCString result; + nsresult status = GetProxyForURI(aTestURI, aTestHost, result); + aCallback(status, result); +} + +nsresult ProxyAutoConfig::GetProxyForURI(const nsACString& aTestURI, + const nsACString& aTestHost, + nsACString& result) { + if (mJSNeedsSetup) SetupJS(); + + if (!mJSContext || !mJSContext->IsOK()) return NS_ERROR_NOT_AVAILABLE; + + JSContext* cx = mJSContext->Context(); + JSAutoRealm ar(cx, mJSContext->Global()); + AutoPACErrorReporter aper(cx); + + // the sRunning flag keeps a new PAC file from being installed + // while the event loop is spinning on a DNS function. Don't early return. + SetRunning(this); + mRunningHost = aTestHost; + + nsresult rv = NS_ERROR_FAILURE; + nsCString clensedURI(aTestURI); + + if (!mIncludePath) { + nsCOMPtr<nsIURLParser> urlParser = + do_GetService(NS_STDURLPARSER_CONTRACTID); + int32_t pathLen = 0; + if (urlParser) { + uint32_t schemePos; + int32_t schemeLen; + uint32_t authorityPos; + int32_t authorityLen; + uint32_t pathPos; + rv = urlParser->ParseURL(aTestURI.BeginReading(), aTestURI.Length(), + &schemePos, &schemeLen, &authorityPos, + &authorityLen, &pathPos, &pathLen); + } + if (NS_SUCCEEDED(rv)) { + if (pathLen) { + // cut off the path but leave the initial slash + pathLen--; + } + aTestURI.Left(clensedURI, aTestURI.Length() - pathLen); + } + } + + JS::Rooted<JSString*> uriString( + cx, + JS_NewStringCopyN(cx, clensedURI.BeginReading(), clensedURI.Length())); + JS::Rooted<JSString*> hostString( + cx, JS_NewStringCopyN(cx, aTestHost.BeginReading(), aTestHost.Length())); + + if (uriString && hostString) { + JS::RootedValueArray<2> args(cx); + args[0].setString(uriString); + args[1].setString(hostString); + + JS::Rooted<JS::Value> rval(cx); + JS::Rooted<JSObject*> global(cx, mJSContext->Global()); + bool ok = JS_CallFunctionName(cx, global, "FindProxyForURL", args, &rval); + + if (ok && rval.isString()) { + nsAutoJSString pacString; + if (pacString.init(cx, rval.toString())) { + CopyUTF16toUTF8(pacString, result); + rv = NS_OK; + } + } + } + + mRunningHost.Truncate(); + SetRunning(nullptr); + return rv; +} + +void ProxyAutoConfig::GC() { + if (!mJSContext || !mJSContext->IsOK()) return; + + JSAutoRealm ar(mJSContext->Context(), mJSContext->Global()); + JS_MaybeGC(mJSContext->Context()); +} + +ProxyAutoConfig::~ProxyAutoConfig() { + MOZ_COUNT_DTOR(ProxyAutoConfig); + MOZ_ASSERT(mShutdown, "Shutdown must be called before dtor."); + NS_ASSERTION(!mJSContext, + "~ProxyAutoConfig leaking JS context that " + "should have been deleted on pac thread"); +} + +void ProxyAutoConfig::Shutdown() { + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread for shutdown"); + + if (NS_WARN_IF(GetRunning()) || mShutdown) { + return; + } + + mShutdown = true; + delete mJSContext; + mJSContext = nullptr; +} + +bool ProxyAutoConfig::SrcAddress(const NetAddr* remoteAddress, + nsCString& localAddress) { + PRFileDesc* fd; + fd = PR_OpenUDPSocket(remoteAddress->raw.family); + if (!fd) return false; + + PRNetAddr prRemoteAddress; + NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress); + if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) { + PR_Close(fd); + return false; + } + + PRNetAddr localName; + if (PR_GetSockName(fd, &localName) != PR_SUCCESS) { + PR_Close(fd); + return false; + } + + PR_Close(fd); + + char dottedDecimal[128]; + if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) != + PR_SUCCESS) { + return false; + } + + localAddress.Assign(dottedDecimal); + + return true; +} + +// hostName is run through a dns lookup and then a udp socket is connected +// to the result. If that all works, the local IP address of the socket is +// returned to the javascript caller and |*aResult| is set to true. Otherwise +// |*aResult| is set to false. +bool ProxyAutoConfig::MyIPAddressTryHost(const nsACString& hostName, + unsigned int timeout, + const JS::CallArgs& aArgs, + bool* aResult) { + *aResult = false; + + NetAddr remoteAddress; + nsAutoCString localDottedDecimal; + JSContext* cx = mJSContext->Context(); + + if (PACResolve(hostName, &remoteAddress, timeout) && + SrcAddress(&remoteAddress, localDottedDecimal)) { + JSString* dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + *aResult = true; + aArgs.rval().setString(dottedDecimalString); + } + return true; +} + +bool ProxyAutoConfig::MyIPAddress(const JS::CallArgs& aArgs) { + nsAutoCString remoteDottedDecimal; + nsAutoCString localDottedDecimal; + JSContext* cx = mJSContext->Context(); + JS::Rooted<JS::Value> v(cx); + JS::Rooted<JSObject*> global(cx, mJSContext->Global()); + + bool useMultihomedDNS = + JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) && + !v.isUndefined() && ToBoolean(v); + + // first, lookup the local address of a socket connected + // to the host of uri being resolved by the pac file. This is + // v6 safe.. but is the last step like that + bool rvalAssigned = false; + if (useMultihomedDNS) { + if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + } else { + // we can still do the fancy multi homing thing if the host is a literal + if (HostIsIPLiteral(mRunningHost) && + (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) || + rvalAssigned)) { + return rvalAssigned; + } + } + + // next, look for a route to a public internet address that doesn't need DNS. + // This is the google anycast dns address, but it doesn't matter if it + // remains operable (as we don't contact it) as long as the address stays + // in commonly routed IP address space. + remoteDottedDecimal.AssignLiteral("8.8.8.8"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // finally, use the old algorithm based on the local hostname + nsAutoCString hostName; + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + // without multihomedDNS use such a short timeout that we are basically + // just looking at the cache for raw dotted decimals + uint32_t timeout = useMultihomedDNS ? kTimeout : 1; + if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) && + PACResolveToString(hostName, localDottedDecimal, timeout)) { + JSString* dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + aArgs.rval().setString(dottedDecimalString); + return true; + } + + // next try a couple RFC 1918 variants.. maybe there is a + // local route + remoteDottedDecimal.AssignLiteral("192.168.0.1"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // more RFC 1918 + remoteDottedDecimal.AssignLiteral("10.0.0.1"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // who knows? let's fallback to localhost + localDottedDecimal.AssignLiteral("127.0.0.1"); + JSString* dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + aArgs.rval().setString(dottedDecimalString); + return true; +} + +RemoteProxyAutoConfig::RemoteProxyAutoConfig() = default; + +RemoteProxyAutoConfig::~RemoteProxyAutoConfig() = default; + +nsresult RemoteProxyAutoConfig::Init(nsIThread* aPACThread) { + MOZ_ASSERT(NS_IsMainThread()); + + SocketProcessParent* socketProcessParent = + SocketProcessParent::GetSingleton(); + if (!socketProcessParent) { + return NS_ERROR_NOT_AVAILABLE; + } + + ipc::Endpoint<PProxyAutoConfigParent> parent; + ipc::Endpoint<PProxyAutoConfigChild> child; + nsresult rv = PProxyAutoConfig::CreateEndpoints( + base::GetCurrentProcId(), socketProcessParent->OtherPid(), &parent, + &child); + if (NS_FAILED(rv)) { + return rv; + } + + Unused << socketProcessParent->SendInitProxyAutoConfigChild(std::move(child)); + mProxyAutoConfigParent = new ProxyAutoConfigParent(); + return aPACThread->Dispatch( + NS_NewRunnableFunction("ProxyAutoConfigParent::ProxyAutoConfigParent", + [proxyAutoConfigParent(mProxyAutoConfigParent), + endpoint{std::move(parent)}]() mutable { + proxyAutoConfigParent->Init(std::move(endpoint)); + })); +} + +nsresult RemoteProxyAutoConfig::ConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, + bool aIncludePath, + uint32_t aExtraHeapSize, + nsISerialEventTarget*) { + Unused << mProxyAutoConfigParent->SendConfigurePAC( + aPACURI, aPACScriptData, aIncludePath, aExtraHeapSize); + return NS_OK; +} + +void RemoteProxyAutoConfig::Shutdown() { mProxyAutoConfigParent->Close(); } + +void RemoteProxyAutoConfig::GC() { + // Do nothing. GC would be performed when there is not pending query in socket + // process. +} + +void RemoteProxyAutoConfig::GetProxyForURIWithCallback( + const nsACString& aTestURI, const nsACString& aTestHost, + std::function<void(nsresult aStatus, const nsACString& aResult)>&& + aCallback) { + if (!mProxyAutoConfigParent->CanSend()) { + return; + } + + mProxyAutoConfigParent->SendGetProxyForURI( + aTestURI, aTestHost, + [aCallback](std::tuple<nsresult, nsCString>&& aResult) { + auto [status, result] = aResult; + aCallback(status, result); + }, + [aCallback](mozilla::ipc::ResponseRejectReason&& aReason) { + aCallback(NS_ERROR_FAILURE, ""_ns); + }); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/ProxyAutoConfig.h b/netwerk/base/ProxyAutoConfig.h new file mode 100644 index 0000000000..e01450eadd --- /dev/null +++ b/netwerk/base/ProxyAutoConfig.h @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ProxyAutoConfig_h__ +#define ProxyAutoConfig_h__ + +#include <functional> +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class nsIEventTarget; +class nsITimer; +class nsIThread; +namespace JS { +class CallArgs; +} // namespace JS + +namespace mozilla { +namespace net { + +class JSContextWrapper; +class ProxyAutoConfigParent; +union NetAddr; + +class ProxyAutoConfigBase { + public: + virtual ~ProxyAutoConfigBase() = default; + virtual nsresult Init(nsIThread* aPACThread) { return NS_OK; } + virtual nsresult ConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, + bool aIncludePath, uint32_t aExtraHeapSize, + nsISerialEventTarget* aEventTarget) = 0; + virtual void SetThreadLocalIndex(uint32_t index) {} + virtual void Shutdown() = 0; + virtual void GC() = 0; + virtual void GetProxyForURIWithCallback( + const nsACString& aTestURI, const nsACString& aTestHost, + std::function<void(nsresult aStatus, const nsACString& aResult)>&& + aCallback) = 0; +}; + +// The ProxyAutoConfig class is meant to be created and run on a +// non main thread. It synchronously resolves PAC files by blocking that +// thread and running nested event loops. GetProxyForURI is not re-entrant. + +class ProxyAutoConfig : public ProxyAutoConfigBase { + public: + ProxyAutoConfig(); + virtual ~ProxyAutoConfig(); + + nsresult ConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, bool aIncludePath, + uint32_t aExtraHeapSize, + nsISerialEventTarget* aEventTarget) override; + void SetThreadLocalIndex(uint32_t index) override; + void Shutdown() override; + void GC() override; + bool MyIPAddress(const JS::CallArgs& aArgs); + bool ResolveAddress(const nsACString& aHostName, NetAddr* aNetAddr, + unsigned int aTimeout); + + /** + * Get the proxy string for the specified URI. The proxy string is + * given by the following: + * + * result = proxy-spec *( proxy-sep proxy-spec ) + * proxy-spec = direct-type | proxy-type LWS proxy-host [":" proxy-port] + * direct-type = "DIRECT" + * proxy-type = "PROXY" | "HTTP" | "HTTPS" | "SOCKS" | "SOCKS4" | "SOCKS5" + * proxy-sep = ";" LWS + * proxy-host = hostname | ipv4-address-literal + * proxy-port = <any 16-bit unsigned integer> + * LWS = *( SP | HT ) + * SP = <US-ASCII SP, space (32)> + * HT = <US-ASCII HT, horizontal-tab (9)> + * + * NOTE: direct-type and proxy-type are case insensitive + * NOTE: SOCKS implies SOCKS4 + * + * Examples: + * "PROXY proxy1.foo.com:8080; PROXY proxy2.foo.com:8080; DIRECT" + * "SOCKS socksproxy" + * "DIRECT" + * + * XXX add support for IPv6 address literals. + * XXX quote whatever the official standard is for PAC. + * + * @param aTestURI + * The URI as an ASCII string to test. + * @param aTestHost + * The ASCII hostname to test. + * + * @param result + * result string as defined above. + */ + nsresult GetProxyForURI(const nsACString& aTestURI, + const nsACString& aTestHost, nsACString& result); + + void GetProxyForURIWithCallback( + const nsACString& aTestURI, const nsACString& aTestHost, + std::function<void(nsresult aStatus, const nsACString& aResult)>&& + aCallback) override; + + private: + // allow 665ms for myipaddress dns queries. That's 95th percentile. + const static unsigned int kTimeout = 665; + + // used to compile the PAC file and setup the execution context + nsresult SetupJS(); + + bool SrcAddress(const NetAddr* remoteAddress, nsCString& localAddress); + bool MyIPAddressTryHost(const nsACString& hostName, unsigned int timeout, + const JS::CallArgs& aArgs, bool* aResult); + + JSContextWrapper* mJSContext{nullptr}; + bool mJSNeedsSetup{false}; + bool mShutdown{true}; + nsCString mConcatenatedPACData; + nsCString mPACURI; + bool mIncludePath{false}; + uint32_t mExtraHeapSize{0}; + nsCString mRunningHost; + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget; +}; + +class RemoteProxyAutoConfig : public ProxyAutoConfigBase { + public: + RemoteProxyAutoConfig(); + virtual ~RemoteProxyAutoConfig(); + + nsresult Init(nsIThread* aPACThread) override; + nsresult ConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, bool aIncludePath, + uint32_t aExtraHeapSize, + nsISerialEventTarget* aEventTarget) override; + void Shutdown() override; + void GC() override; + void GetProxyForURIWithCallback( + const nsACString& aTestURI, const nsACString& aTestHost, + std::function<void(nsresult aStatus, const nsACString& aResult)>&& + aCallback) override; + + private: + RefPtr<ProxyAutoConfigParent> mProxyAutoConfigParent; +}; + +} // namespace net +} // namespace mozilla + +#endif // ProxyAutoConfig_h__ diff --git a/netwerk/base/ProxyConfig.h b/netwerk/base/ProxyConfig.h new file mode 100644 index 0000000000..2deb1dd2a1 --- /dev/null +++ b/netwerk/base/ProxyConfig.h @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_netwerk_base_proxy_config_h +#define mozilla_netwerk_base_proxy_config_h + +#include <map> + +#include "nsCRT.h" +#include "nsString.h" +#include "nsTArray.h" + +// NOTE: This file is inspired by Chromium's code. +// https://source.chromium.org/chromium/chromium/src/+/main:net/proxy_resolution/proxy_config.h. + +namespace mozilla { +namespace net { + +// ProxyServer stores the {type, host, port} of a proxy server. +// ProxyServer is immutable. +class ProxyServer final { + public: + enum class ProxyType { + DIRECT = 0, + HTTP, + HTTPS, + SOCKS, + SOCKS4, + SOCKS5, + FTP, + // DEFAULT is a special type used on windows only. + DEFAULT + }; + + ProxyServer() = default; + + ProxyServer(ProxyType aType, const nsACString& aHost, int32_t aPort) + : mType(aType), mHost(aHost), mPort(aPort) {} + + const nsCString& Host() const { return mHost; } + + int32_t Port() const { return mPort; } + + ProxyType Type() const { return mType; } + + void ToHostAndPortStr(nsACString& aOutput) { + aOutput.Truncate(); + if (mType == ProxyType::DIRECT) { + return; + } + + aOutput.Assign(mHost); + if (mPort != -1) { + aOutput.Append(':'); + aOutput.AppendInt(mPort); + } + } + + bool operator==(const ProxyServer& aOther) const { + return mType == aOther.mType && mHost == aOther.mHost && + mPort == aOther.mPort; + } + + bool operator!=(const ProxyServer& aOther) const { + return !(*this == aOther); + } + + private: + ProxyType mType{ProxyType::DIRECT}; + nsCString mHost; + int32_t mPort{-1}; +}; + +// This class includes the information about proxy configuration. +// It contains enabled proxy servers, exception list, and the url of PAC +// script. +class ProxyConfig { + public: + struct ProxyRules { + ProxyRules() = default; + ~ProxyRules() = default; + + std::map<ProxyServer::ProxyType, ProxyServer> mProxyServers; + }; + + struct ProxyBypassRules { + ProxyBypassRules() = default; + ~ProxyBypassRules() = default; + + CopyableTArray<nsCString> mExceptions; + }; + + ProxyConfig() = default; + ProxyConfig(const ProxyConfig& config); + ~ProxyConfig() = default; + + ProxyRules& Rules() { return mRules; } + + const ProxyRules& Rules() const { return mRules; } + + ProxyBypassRules& ByPassRules() { return mBypassRules; } + + const ProxyBypassRules& ByPassRules() const { return mBypassRules; } + + void SetPACUrl(const nsACString& aUrl) { mPACUrl = aUrl; } + + const nsCString& PACUrl() const { return mPACUrl; } + + static ProxyServer::ProxyType ToProxyType(const char* aType) { + if (!aType) { + return ProxyServer::ProxyType::DIRECT; + } + + if (nsCRT::strcasecmp(aType, "http") == 0) { + return ProxyServer::ProxyType::HTTP; + } + if (nsCRT::strcasecmp(aType, "https") == 0) { + return ProxyServer::ProxyType::HTTPS; + } + if (nsCRT::strcasecmp(aType, "socks") == 0) { + return ProxyServer::ProxyType::SOCKS; + } + if (nsCRT::strcasecmp(aType, "socks4") == 0) { + return ProxyServer::ProxyType::SOCKS4; + } + if (nsCRT::strcasecmp(aType, "socks5") == 0) { + return ProxyServer::ProxyType::SOCKS5; + } + if (nsCRT::strcasecmp(aType, "ftp") == 0) { + return ProxyServer::ProxyType::FTP; + } + + return ProxyServer::ProxyType::DIRECT; + } + + static void SetProxyResult(const char* aType, const nsACString& aHostPort, + nsACString& aResult) { + aResult.AssignASCII(aType); + aResult.Append(' '); + aResult.Append(aHostPort); + } + + static void SetProxyResultDirect(nsACString& aResult) { + // For whatever reason, a proxy is not to be used. + aResult.AssignLiteral("DIRECT"); + } + + static void ProxyStrToResult(const nsACString& aSpecificProxy, + const nsACString& aDefaultProxy, + const nsACString& aSocksProxy, + nsACString& aResult) { + if (!aSpecificProxy.IsEmpty()) { + SetProxyResult("PROXY", aSpecificProxy, + aResult); // Protocol-specific proxy. + } else if (!aDefaultProxy.IsEmpty()) { + SetProxyResult("PROXY", aDefaultProxy, aResult); // Default proxy. + } else if (!aSocksProxy.IsEmpty()) { + SetProxyResult("SOCKS", aSocksProxy, aResult); // SOCKS proxy. + } else { + SetProxyResultDirect(aResult); // Direct connection. + } + } + + void GetProxyString(const nsACString& aScheme, nsACString& aResult) { + SetProxyResultDirect(aResult); + + nsAutoCString specificProxy; + nsAutoCString defaultProxy; + nsAutoCString socksProxy; + nsAutoCString prefix; + ToLowerCase(aScheme, prefix); + ProxyServer::ProxyType type = ProxyConfig::ToProxyType(prefix.get()); + for (auto& [key, value] : mRules.mProxyServers) { + // Break the loop if we found a specific proxy. + if (key == type) { + value.ToHostAndPortStr(specificProxy); + break; + } else if (key == ProxyServer::ProxyType::DEFAULT) { + value.ToHostAndPortStr(defaultProxy); + } else if (key == ProxyServer::ProxyType::SOCKS) { + value.ToHostAndPortStr(socksProxy); + } + } + + ProxyStrToResult(specificProxy, defaultProxy, socksProxy, aResult); + } + + private: + nsCString mPACUrl; + ProxyRules mRules; + ProxyBypassRules mBypassRules; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_netwerk_base_proxy_config_h diff --git a/netwerk/base/RedirectChannelRegistrar.cpp b/netwerk/base/RedirectChannelRegistrar.cpp new file mode 100644 index 0000000000..9aa687ed88 --- /dev/null +++ b/netwerk/base/RedirectChannelRegistrar.cpp @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RedirectChannelRegistrar.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace net { + +StaticRefPtr<RedirectChannelRegistrar> RedirectChannelRegistrar::gSingleton; + +NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar) + +RedirectChannelRegistrar::RedirectChannelRegistrar() + : mRealChannels(32), + mParentChannels(32), + mLock("RedirectChannelRegistrar") { + MOZ_ASSERT(!gSingleton); +} + +// static +already_AddRefed<nsIRedirectChannelRegistrar> +RedirectChannelRegistrar::GetOrCreate() { + MOZ_ASSERT(NS_IsMainThread()); + if (!gSingleton) { + gSingleton = new RedirectChannelRegistrar(); + ClearOnShutdown(&gSingleton); + } + return do_AddRef(gSingleton); +} + +NS_IMETHODIMP +RedirectChannelRegistrar::RegisterChannel(nsIChannel* channel, uint64_t id) { + MutexAutoLock lock(mLock); + + mRealChannels.InsertOrUpdate(id, channel); + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::GetRegisteredChannel(uint64_t id, + nsIChannel** _retval) { + MutexAutoLock lock(mLock); + + if (!mRealChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::LinkChannels(uint64_t id, nsIParentChannel* channel, + nsIChannel** _retval) { + MutexAutoLock lock(mLock); + + if (!mRealChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE; + + mParentChannels.InsertOrUpdate(id, channel); + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::GetParentChannel(uint64_t id, + nsIParentChannel** _retval) { + MutexAutoLock lock(mLock); + + if (!mParentChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::DeregisterChannels(uint64_t id) { + MutexAutoLock lock(mLock); + + mRealChannels.Remove(id); + mParentChannels.Remove(id); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/RedirectChannelRegistrar.h b/netwerk/base/RedirectChannelRegistrar.h new file mode 100644 index 0000000000..5041726043 --- /dev/null +++ b/netwerk/base/RedirectChannelRegistrar.h @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RedirectChannelRegistrar_h__ +#define RedirectChannelRegistrar_h__ + +#include "nsIRedirectChannelRegistrar.h" + +#include "nsIChannel.h" +#include "nsIParentChannel.h" +#include "nsInterfaceHashtable.h" +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace net { + +class RedirectChannelRegistrar final : public nsIRedirectChannelRegistrar { + NS_DECL_ISUPPORTS + NS_DECL_NSIREDIRECTCHANNELREGISTRAR + + RedirectChannelRegistrar(); + + private: + ~RedirectChannelRegistrar() = default; + + public: + // Singleton accessor + static already_AddRefed<nsIRedirectChannelRegistrar> GetOrCreate(); + + protected: + using ChannelHashtable = nsInterfaceHashtable<nsUint64HashKey, nsIChannel>; + using ParentChannelHashtable = + nsInterfaceHashtable<nsUint64HashKey, nsIParentChannel>; + + ChannelHashtable mRealChannels; + ParentChannelHashtable mParentChannels; + Mutex mLock MOZ_UNANNOTATED; + + static StaticRefPtr<RedirectChannelRegistrar> gSingleton; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/RequestContextService.cpp b/netwerk/base/RequestContextService.cpp new file mode 100644 index 0000000000..72331bdc1e --- /dev/null +++ b/netwerk/base/RequestContextService.cpp @@ -0,0 +1,622 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIDocShell.h" +#include "mozilla/dom/Document.h" +#include "nsComponentManagerUtils.h" +#include "nsIDocumentLoader.h" +#include "nsIObserverService.h" +#include "nsITimer.h" +#include "nsIXULRuntime.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "RequestContextService.h" + +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Logging.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" + +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/net/PSpdyPush.h" + +#include "../protocol/http/nsHttpHandler.h" + +namespace mozilla { +namespace net { + +LazyLogModule gRequestContextLog("RequestContext"); +#undef LOG +#define LOG(args) MOZ_LOG(gRequestContextLog, LogLevel::Info, args) + +static StaticRefPtr<RequestContextService> gSingleton; + +// This is used to prevent adding tail pending requests after shutdown +static bool sShutdown = false; + +// nsIRequestContext +class RequestContext final : public nsIRequestContext, + public nsITimerCallback, + public nsINamed { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTCONTEXT + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + explicit RequestContext(const uint64_t id); + + private: + virtual ~RequestContext(); + + void ProcessTailQueue(nsresult aResult); + // Reschedules the timer if needed + void ScheduleUnblock(); + // Hard-reschedules the timer + void RescheduleUntailTimer(TimeStamp const& now); + + uint64_t mID; + Atomic<uint32_t> mBlockingTransactionCount; + UniquePtr<SpdyPushCache> mSpdyCache; + + using PendingTailRequest = nsCOMPtr<nsIRequestTailUnblockCallback>; + // Number of known opened non-tailed requets + uint32_t mNonTailRequests; + // Queue of requests that have been tailed, when conditions are met + // we call each of them to unblock and drop the reference + nsTArray<PendingTailRequest> mTailQueue; + // Loosly scheduled timer, never scheduled further to the future than + // mUntailAt time + nsCOMPtr<nsITimer> mUntailTimer; + // Timestamp when the timer is expected to fire, + // always less than or equal to mUntailAt + TimeStamp mTimerScheduledAt; + // Timestamp when we want to actually untail queued requets based on + // the number of request count change in the past; iff this timestamp + // is set, we tail requests + TimeStamp mUntailAt; + + // Timestamp of the navigation start time, set to Now() in BeginLoad(). + // This is used to progressively lower the maximum delay time so that + // we can't get to a situation when a number of repetitive requests + // on the page causes forever tailing. + TimeStamp mBeginLoadTime; + + // This member is true only between DOMContentLoaded notification and + // next document load beginning for this request context. + // Top level request contexts are recycled. + bool mAfterDOMContentLoaded; +}; + +NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext, nsITimerCallback, nsINamed) + +RequestContext::RequestContext(const uint64_t aID) + : mID(aID), + mBlockingTransactionCount(0), + mNonTailRequests(0), + mAfterDOMContentLoaded(false) { + LOG(("RequestContext::RequestContext this=%p id=%" PRIx64, this, mID)); +} + +RequestContext::~RequestContext() { + MOZ_ASSERT(mTailQueue.Length() == 0); + + LOG(("RequestContext::~RequestContext this=%p blockers=%u", this, + static_cast<uint32_t>(mBlockingTransactionCount))); +} + +NS_IMETHODIMP +RequestContext::BeginLoad() { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("RequestContext::BeginLoad %p", this)); + + if (IsNeckoChild()) { + // Tailing is not supported on the child process + if (gNeckoChild) { + gNeckoChild->SendRequestContextLoadBegin(mID); + } + return NS_OK; + } + + mAfterDOMContentLoaded = false; + mBeginLoadTime = TimeStamp::NowLoRes(); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::DOMContentLoaded() { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("RequestContext::DOMContentLoaded %p", this)); + + if (IsNeckoChild()) { + // Tailing is not supported on the child process + if (gNeckoChild) { + gNeckoChild->SendRequestContextAfterDOMContentLoaded(mID); + } + return NS_OK; + } + + if (mAfterDOMContentLoaded) { + // There is a possibility of a duplicate notification + return NS_OK; + } + + mAfterDOMContentLoaded = true; + + // Conditions for the delay calculation has changed. + ScheduleUnblock(); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::GetBlockingTransactionCount( + uint32_t* aBlockingTransactionCount) { + NS_ENSURE_ARG_POINTER(aBlockingTransactionCount); + *aBlockingTransactionCount = mBlockingTransactionCount; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::AddBlockingTransaction() { + mBlockingTransactionCount++; + LOG(("RequestContext::AddBlockingTransaction this=%p blockers=%u", this, + static_cast<uint32_t>(mBlockingTransactionCount))); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::RemoveBlockingTransaction(uint32_t* outval) { + NS_ENSURE_ARG_POINTER(outval); + mBlockingTransactionCount--; + LOG(("RequestContext::RemoveBlockingTransaction this=%p blockers=%u", this, + static_cast<uint32_t>(mBlockingTransactionCount))); + *outval = mBlockingTransactionCount; + return NS_OK; +} + +SpdyPushCache* RequestContext::GetSpdyPushCache() { return mSpdyCache.get(); } + +void RequestContext::SetSpdyPushCache(SpdyPushCache* aSpdyPushCache) { + mSpdyCache = WrapUnique(aSpdyPushCache); +} + +uint64_t RequestContext::GetID() { return mID; } + +NS_IMETHODIMP +RequestContext::AddNonTailRequest() { + MOZ_ASSERT(NS_IsMainThread()); + + ++mNonTailRequests; + LOG(("RequestContext::AddNonTailRequest this=%p, cnt=%u", this, + mNonTailRequests)); + + ScheduleUnblock(); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::RemoveNonTailRequest() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mNonTailRequests > 0); + + LOG(("RequestContext::RemoveNonTailRequest this=%p, cnt=%u", this, + mNonTailRequests - 1)); + + --mNonTailRequests; + + ScheduleUnblock(); + return NS_OK; +} + +void RequestContext::ScheduleUnblock() { + MOZ_ASSERT(!IsNeckoChild()); + MOZ_ASSERT(NS_IsMainThread()); + + if (!gHttpHandler) { + return; + } + + uint32_t quantum = + gHttpHandler->TailBlockingDelayQuantum(mAfterDOMContentLoaded); + uint32_t delayMax = gHttpHandler->TailBlockingDelayMax(); + uint32_t totalMax = gHttpHandler->TailBlockingTotalMax(); + + if (!mBeginLoadTime.IsNull()) { + // We decrease the maximum delay progressively with the time since the page + // load begin. This seems like a reasonable and clear heuristic allowing us + // to start loading tailed requests in a deterministic time after the load + // has started. + + uint32_t sinceBeginLoad = static_cast<uint32_t>( + (TimeStamp::NowLoRes() - mBeginLoadTime).ToMilliseconds()); + uint32_t tillTotal = totalMax - std::min(sinceBeginLoad, totalMax); + uint32_t proportion = totalMax // values clamped between 0 and 60'000 + ? (delayMax * tillTotal) / totalMax + : 0; + delayMax = std::min(delayMax, proportion); + } + + CheckedInt<uint32_t> delay = quantum * mNonTailRequests; + + if (!mAfterDOMContentLoaded) { + // Before DOMContentLoaded notification we want to make sure that tailed + // requests don't start when there is a short delay during which we may + // not have any active requests on the page happening. + delay += quantum; + } + + if (!delay.isValid() || delay.value() > delayMax) { + delay = delayMax; + } + + LOG( + ("RequestContext::ScheduleUnblock this=%p non-tails=%u tail-queue=%zu " + "delay=%u after-DCL=%d", + this, mNonTailRequests, mTailQueue.Length(), delay.value(), + mAfterDOMContentLoaded)); + + TimeStamp now = TimeStamp::NowLoRes(); + mUntailAt = now + TimeDuration::FromMilliseconds(delay.value()); + + if (mTimerScheduledAt.IsNull() || mUntailAt < mTimerScheduledAt) { + LOG(("RequestContext %p timer would fire too late, rescheduling", this)); + RescheduleUntailTimer(now); + } +} + +void RequestContext::RescheduleUntailTimer(TimeStamp const& now) { + MOZ_ASSERT(mUntailAt >= now); + + if (mUntailTimer) { + mUntailTimer->Cancel(); + } + + if (!mTailQueue.Length()) { + mUntailTimer = nullptr; + mTimerScheduledAt = TimeStamp(); + return; + } + + TimeDuration interval = mUntailAt - now; + if (!mTimerScheduledAt.IsNull() && mUntailAt < mTimerScheduledAt) { + // When the number of untailed requests goes down, + // let's half the interval, since it's likely we would + // reschedule for a shorter time again very soon. + // This will likely save rescheduling this timer. + interval = interval / int64_t(2); + mTimerScheduledAt = mUntailAt - interval; + } else { + mTimerScheduledAt = mUntailAt; + } + + uint32_t delay = interval.ToMilliseconds(); + nsresult rv = + NS_NewTimerWithCallback(getter_AddRefs(mUntailTimer), this, delay, + nsITimer::TYPE_ONE_SHOT, nullptr); + if (NS_FAILED(rv)) { + NS_WARNING("Could not reschedule untail timer"); + } + + LOG(("RequestContext::RescheduleUntailTimer %p in %d", this, delay)); +} + +NS_IMETHODIMP +RequestContext::Notify(nsITimer* timer) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(timer == mUntailTimer); + MOZ_ASSERT(!mTimerScheduledAt.IsNull()); + MOZ_ASSERT(mTailQueue.Length()); + + mUntailTimer = nullptr; + + TimeStamp now = TimeStamp::NowLoRes(); + if (mUntailAt > mTimerScheduledAt && mUntailAt > now) { + LOG(("RequestContext %p timer fired too soon, rescheduling", this)); + RescheduleUntailTimer(now); + return NS_OK; + } + + // Must drop to allow re-engage of the timer + mTimerScheduledAt = TimeStamp(); + + ProcessTailQueue(NS_OK); + + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::GetName(nsACString& aName) { + aName.AssignLiteral("RequestContext"); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::IsContextTailBlocked(nsIRequestTailUnblockCallback* aRequest, + bool* aBlocked) { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("RequestContext::IsContextTailBlocked this=%p, request=%p, queued=%zu", + this, aRequest, mTailQueue.Length())); + + *aBlocked = false; + + if (sShutdown) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + if (mUntailAt.IsNull()) { + LOG((" untail time passed")); + return NS_OK; + } + + if (mAfterDOMContentLoaded && !mNonTailRequests) { + LOG((" after DOMContentLoaded and no untailed requests")); + return NS_OK; + } + + if (!gHttpHandler) { + // Xpcshell tests may not have http handler + LOG((" missing gHttpHandler?")); + return NS_OK; + } + + *aBlocked = true; + mTailQueue.AppendElement(aRequest); + + LOG((" request queued")); + + if (!mUntailTimer) { + ScheduleUnblock(); + } + + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::CancelTailedRequest(nsIRequestTailUnblockCallback* aRequest) { + MOZ_ASSERT(NS_IsMainThread()); + + bool removed = mTailQueue.RemoveElement(aRequest); + + LOG(("RequestContext::CancelTailedRequest %p req=%p removed=%d", this, + aRequest, removed)); + + // Stop untail timer if all tail requests are canceled. + if (removed && mTailQueue.IsEmpty()) { + if (mUntailTimer) { + mUntailTimer->Cancel(); + mUntailTimer = nullptr; + } + + // Must drop to allow re-engage of the timer + mTimerScheduledAt = TimeStamp(); + } + + return NS_OK; +} + +void RequestContext::ProcessTailQueue(nsresult aResult) { + LOG(("RequestContext::ProcessTailQueue this=%p, queued=%zu, rv=%" PRIx32, + this, mTailQueue.Length(), static_cast<uint32_t>(aResult))); + + if (mUntailTimer) { + mUntailTimer->Cancel(); + mUntailTimer = nullptr; + } + + // Must drop to stop tailing requests + mUntailAt = TimeStamp(); + + nsTArray<PendingTailRequest> queue = std::move(mTailQueue); + + for (const auto& request : queue) { + LOG((" untailing %p", request.get())); + request->OnTailUnblock(aResult); + } +} + +NS_IMETHODIMP +RequestContext::CancelTailPendingRequests(nsresult aResult) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(NS_FAILED(aResult)); + + ProcessTailQueue(aResult); + return NS_OK; +} + +// nsIRequestContextService +RequestContextService* RequestContextService::sSelf = nullptr; + +NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver) + +RequestContextService::RequestContextService() { + MOZ_ASSERT(!sSelf, "multiple rcs instances!"); + MOZ_ASSERT(NS_IsMainThread()); + sSelf = this; + + nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1"); + runtime->GetProcessID(&mRCIDNamespace); +} + +RequestContextService::~RequestContextService() { + MOZ_ASSERT(NS_IsMainThread()); + Shutdown(); + sSelf = nullptr; +} + +nsresult RequestContextService::Init() { + nsresult rv; + + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_NOT_AVAILABLE; + } + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + if (NS_FAILED(rv)) { + return rv; + } + obs->AddObserver(this, "content-document-interactive", false); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +void RequestContextService::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + // We need to do this to prevent the requests from being scheduled after + // shutdown. + for (const auto& data : mTable.Values()) { + data->CancelTailPendingRequests(NS_ERROR_ABORT); + } + mTable.Clear(); + sShutdown = true; +} + +/* static */ +already_AddRefed<nsIRequestContextService> +RequestContextService::GetOrCreate() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sShutdown) { + return nullptr; + } + + RefPtr<RequestContextService> svc; + if (gSingleton) { + svc = gSingleton; + } else { + svc = new RequestContextService(); + nsresult rv = svc->Init(); + NS_ENSURE_SUCCESS(rv, nullptr); + gSingleton = svc; + ClearOnShutdown(&gSingleton); + } + + return svc.forget(); +} + +NS_IMETHODIMP +RequestContextService::GetRequestContext(const uint64_t rcID, + nsIRequestContext** rc) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(rc); + *rc = nullptr; + + if (sShutdown) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + if (!rcID) { + return NS_ERROR_INVALID_ARG; + } + + *rc = do_AddRef(mTable.LookupOrInsertWith(rcID, [&] { + nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID); + return newSC; + })).take(); + + return NS_OK; +} + +NS_IMETHODIMP +RequestContextService::GetRequestContextFromLoadGroup(nsILoadGroup* aLoadGroup, + nsIRequestContext** rc) { + nsresult rv; + + uint64_t rcID; + rv = aLoadGroup->GetRequestContextID(&rcID); + if (NS_FAILED(rv)) { + return rv; + } + + return GetRequestContext(rcID, rc); +} + +NS_IMETHODIMP +RequestContextService::NewRequestContext(nsIRequestContext** rc) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(rc); + *rc = nullptr; + + if (sShutdown) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + uint64_t rcID = + ((static_cast<uint64_t>(mRCIDNamespace) << 32) & 0xFFFFFFFF00000000LL) | + mNextRCID++; + + nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID); + mTable.InsertOrUpdate(rcID, newSC); + newSC.swap(*rc); + + return NS_OK; +} + +NS_IMETHODIMP +RequestContextService::RemoveRequestContext(const uint64_t rcID) { + MOZ_ASSERT(NS_IsMainThread()); + mTable.Remove(rcID); + return NS_OK; +} + +NS_IMETHODIMP +RequestContextService::Observe(nsISupports* subject, const char* topic, + const char16_t* data_unicode) { + MOZ_ASSERT(NS_IsMainThread()); + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) { + Shutdown(); + return NS_OK; + } + + if (!strcmp("content-document-interactive", topic)) { + nsCOMPtr<dom::Document> document(do_QueryInterface(subject)); + MOZ_ASSERT(document); + // We want this be triggered also for iframes, since those track their + // own request context ids. + if (!document) { + return NS_OK; + } + nsIDocShell* ds = document->GetDocShell(); + // XML documents don't always have a docshell assigned + if (!ds) { + return NS_OK; + } + nsCOMPtr<nsIDocumentLoader> dl(do_QueryInterface(ds)); + if (!dl) { + return NS_OK; + } + nsCOMPtr<nsILoadGroup> lg; + dl->GetLoadGroup(getter_AddRefs(lg)); + if (!lg) { + return NS_OK; + } + nsCOMPtr<nsIRequestContext> rc; + GetRequestContextFromLoadGroup(lg, getter_AddRefs(rc)); + if (rc) { + rc->DOMContentLoaded(); + } + + return NS_OK; + } + + MOZ_ASSERT(false, "Unexpected observer topic"); + return NS_OK; +} + +} // namespace net +} // namespace mozilla + +#undef LOG diff --git a/netwerk/base/RequestContextService.h b/netwerk/base/RequestContextService.h new file mode 100644 index 0000000000..4aafdb8ed6 --- /dev/null +++ b/netwerk/base/RequestContextService.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla__net__RequestContextService_h +#define mozilla__net__RequestContextService_h + +#include "nsCOMPtr.h" +#include "nsInterfaceHashtable.h" +#include "nsIObserver.h" +#include "nsIRequestContext.h" + +namespace mozilla { +namespace net { + +class RequestContextService final : public nsIRequestContextService, + public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTCONTEXTSERVICE + NS_DECL_NSIOBSERVER + + static already_AddRefed<nsIRequestContextService> GetOrCreate(); + + private: + RequestContextService(); + virtual ~RequestContextService(); + + nsresult Init(); + void Shutdown(); + + static RequestContextService* sSelf; + + nsInterfaceHashtable<nsUint64HashKey, nsIRequestContext> mTable; + uint32_t mRCIDNamespace{0}; + uint32_t mNextRCID{1}; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla__net__RequestContextService_h diff --git a/netwerk/base/SSLTokensCache.cpp b/netwerk/base/SSLTokensCache.cpp new file mode 100644 index 0000000000..cf739814f5 --- /dev/null +++ b/netwerk/base/SSLTokensCache.cpp @@ -0,0 +1,568 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SSLTokensCache.h" + +#include "CertVerifier.h" +#include "CommonSocketControl.h" +#include "TransportSecurityInfo.h" +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "nsIOService.h" +#include "ssl.h" +#include "sslexp.h" + +namespace mozilla { +namespace net { + +static LazyLogModule gSSLTokensCacheLog("SSLTokensCache"); +#undef LOG +#define LOG(args) MOZ_LOG(gSSLTokensCacheLog, mozilla::LogLevel::Debug, args) +#undef LOG5_ENABLED +#define LOG5_ENABLED() \ + MOZ_LOG_TEST(mozilla::net::gSSLTokensCacheLog, mozilla::LogLevel::Verbose) + +class ExpirationComparator { + public: + bool Equals(SSLTokensCache::TokenCacheRecord* a, + SSLTokensCache::TokenCacheRecord* b) const { + return a->mExpirationTime == b->mExpirationTime; + } + bool LessThan(SSLTokensCache::TokenCacheRecord* a, + SSLTokensCache::TokenCacheRecord* b) const { + return a->mExpirationTime < b->mExpirationTime; + } +}; + +SessionCacheInfo SessionCacheInfo::Clone() const { + SessionCacheInfo result; + result.mEVStatus = mEVStatus; + result.mCertificateTransparencyStatus = mCertificateTransparencyStatus; + result.mServerCertBytes = mServerCertBytes.Clone(); + result.mSucceededCertChainBytes = + mSucceededCertChainBytes + ? Some(TransformIntoNewArray( + *mSucceededCertChainBytes, + [](const auto& element) { return element.Clone(); })) + : Nothing(); + result.mIsBuiltCertChainRootBuiltInRoot = mIsBuiltCertChainRootBuiltInRoot; + result.mOverridableErrorCategory = mOverridableErrorCategory; + result.mFailedCertChainBytes = + mFailedCertChainBytes + ? Some(TransformIntoNewArray( + *mFailedCertChainBytes, + [](const auto& element) { return element.Clone(); })) + : Nothing(); + return result; +} + +StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance; +StaticMutex SSLTokensCache::sLock; +uint64_t SSLTokensCache::sRecordId = 0; + +SSLTokensCache::TokenCacheRecord::~TokenCacheRecord() { + if (!gInstance) { + return; + } + + gInstance->OnRecordDestroyed(this); +} + +uint32_t SSLTokensCache::TokenCacheRecord::Size() const { + uint32_t size = mToken.Length() + sizeof(mSessionCacheInfo.mEVStatus) + + sizeof(mSessionCacheInfo.mCertificateTransparencyStatus) + + mSessionCacheInfo.mServerCertBytes.Length() + + sizeof(mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot) + + sizeof(mSessionCacheInfo.mOverridableErrorCategory); + if (mSessionCacheInfo.mSucceededCertChainBytes) { + for (const auto& cert : mSessionCacheInfo.mSucceededCertChainBytes.ref()) { + size += cert.Length(); + } + } + if (mSessionCacheInfo.mFailedCertChainBytes) { + for (const auto& cert : mSessionCacheInfo.mFailedCertChainBytes.ref()) { + size += cert.Length(); + } + } + return size; +} + +void SSLTokensCache::TokenCacheRecord::Reset() { + mToken.Clear(); + mExpirationTime = 0; + mSessionCacheInfo.mEVStatus = psm::EVStatus::NotEV; + mSessionCacheInfo.mCertificateTransparencyStatus = + nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE; + mSessionCacheInfo.mServerCertBytes.Clear(); + mSessionCacheInfo.mSucceededCertChainBytes.reset(); + mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot.reset(); + mSessionCacheInfo.mOverridableErrorCategory = + nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET; + mSessionCacheInfo.mFailedCertChainBytes.reset(); +} + +uint32_t SSLTokensCache::TokenCacheEntry::Size() const { + uint32_t size = 0; + for (const auto& rec : mRecords) { + size += rec->Size(); + } + return size; +} + +void SSLTokensCache::TokenCacheEntry::AddRecord( + UniquePtr<SSLTokensCache::TokenCacheRecord>&& aRecord, + nsTArray<TokenCacheRecord*>& aExpirationArray) { + if (mRecords.Length() == + StaticPrefs::network_ssl_tokens_cache_records_per_entry()) { + aExpirationArray.RemoveElement(mRecords[0].get()); + mRecords.RemoveElementAt(0); + } + + aExpirationArray.AppendElement(aRecord.get()); + for (int32_t i = mRecords.Length() - 1; i >= 0; --i) { + if (aRecord->mExpirationTime > mRecords[i]->mExpirationTime) { + mRecords.InsertElementAt(i + 1, std::move(aRecord)); + return; + } + } + mRecords.InsertElementAt(0, std::move(aRecord)); +} + +UniquePtr<SSLTokensCache::TokenCacheRecord> +SSLTokensCache::TokenCacheEntry::RemoveWithId(uint64_t aId) { + for (int32_t i = mRecords.Length() - 1; i >= 0; --i) { + if (mRecords[i]->mId == aId) { + UniquePtr<TokenCacheRecord> record = std::move(mRecords[i]); + mRecords.RemoveElementAt(i); + return record; + } + } + return nullptr; +} + +const UniquePtr<SSLTokensCache::TokenCacheRecord>& +SSLTokensCache::TokenCacheEntry::Get() { + return mRecords[0]; +} + +NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter) + +// static +nsresult SSLTokensCache::Init() { + StaticMutexAutoLock lock(sLock); + + // SSLTokensCache should be only used in parent process and socket process. + // Ideally, parent process should not use this when socket process is enabled. + // However, some xpcsehll tests may need to create and use sockets directly, + // so we still allow to use this in parent process no matter socket process is + // enabled or not. + if (!(XRE_IsSocketProcess() || XRE_IsParentProcess())) { + return NS_OK; + } + + MOZ_ASSERT(!gInstance); + + gInstance = new SSLTokensCache(); + + RegisterWeakMemoryReporter(gInstance); + + return NS_OK; +} + +// static +nsresult SSLTokensCache::Shutdown() { + StaticMutexAutoLock lock(sLock); + + if (!gInstance) { + return NS_ERROR_UNEXPECTED; + } + + UnregisterWeakMemoryReporter(gInstance); + + gInstance = nullptr; + + return NS_OK; +} + +SSLTokensCache::SSLTokensCache() { LOG(("SSLTokensCache::SSLTokensCache")); } + +SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); } + +// static +nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken, + uint32_t aTokenLen, + CommonSocketControl* aSocketControl) { + PRUint32 expirationTime; + SSLResumptionTokenInfo tokenInfo; + if (SSL_GetResumptionTokenInfo(aToken, aTokenLen, &tokenInfo, + sizeof(tokenInfo)) != SECSuccess) { + LOG((" cannot get expiration time from the token, NSS error %d", + PORT_GetError())); + return NS_ERROR_FAILURE; + } + + expirationTime = tokenInfo.expirationTime; + SSL_DestroyResumptionTokenInfo(&tokenInfo); + + return Put(aKey, aToken, aTokenLen, aSocketControl, expirationTime); +} + +// static +nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken, + uint32_t aTokenLen, + CommonSocketControl* aSocketControl, + PRUint32 aExpirationTime) { + StaticMutexAutoLock lock(sLock); + + LOG(("SSLTokensCache::Put [key=%s, tokenLen=%u]", + PromiseFlatCString(aKey).get(), aTokenLen)); + + if (!gInstance) { + LOG((" service not initialized")); + return NS_ERROR_NOT_INITIALIZED; + } + + if (!aSocketControl) { + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsITransportSecurityInfo> securityInfo; + nsresult rv = aSocketControl->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIX509Cert> cert; + securityInfo->GetServerCert(getter_AddRefs(cert)); + if (!cert) { + return NS_ERROR_FAILURE; + } + + nsTArray<uint8_t> certBytes; + rv = cert->GetRawDER(certBytes); + if (NS_FAILED(rv)) { + return rv; + } + + Maybe<nsTArray<nsTArray<uint8_t>>> succeededCertChainBytes; + nsTArray<RefPtr<nsIX509Cert>> succeededCertArray; + rv = securityInfo->GetSucceededCertChain(succeededCertArray); + if (NS_FAILED(rv)) { + return rv; + } + + Maybe<bool> isBuiltCertChainRootBuiltInRoot; + if (!succeededCertArray.IsEmpty()) { + succeededCertChainBytes.emplace(); + for (const auto& cert : succeededCertArray) { + nsTArray<uint8_t> rawCert; + nsresult rv = cert->GetRawDER(rawCert); + if (NS_FAILED(rv)) { + return rv; + } + succeededCertChainBytes->AppendElement(std::move(rawCert)); + } + + bool builtInRoot = false; + rv = securityInfo->GetIsBuiltCertChainRootBuiltInRoot(&builtInRoot); + if (NS_FAILED(rv)) { + return rv; + } + isBuiltCertChainRootBuiltInRoot.emplace(builtInRoot); + } + + bool isEV; + rv = securityInfo->GetIsExtendedValidation(&isEV); + if (NS_FAILED(rv)) { + return rv; + } + + uint16_t certificateTransparencyStatus; + rv = securityInfo->GetCertificateTransparencyStatus( + &certificateTransparencyStatus); + if (NS_FAILED(rv)) { + return rv; + } + + nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory; + rv = securityInfo->GetOverridableErrorCategory(&overridableErrorCategory); + if (NS_FAILED(rv)) { + return rv; + } + + Maybe<nsTArray<nsTArray<uint8_t>>> failedCertChainBytes; + nsTArray<RefPtr<nsIX509Cert>> failedCertArray; + rv = securityInfo->GetFailedCertChain(failedCertArray); + if (NS_FAILED(rv)) { + return rv; + } + if (!failedCertArray.IsEmpty()) { + failedCertChainBytes.emplace(); + for (const auto& cert : failedCertArray) { + nsTArray<uint8_t> rawCert; + nsresult rv = cert->GetRawDER(rawCert); + if (NS_FAILED(rv)) { + return rv; + } + failedCertChainBytes->AppendElement(std::move(rawCert)); + } + } + + auto makeUniqueRecord = [&]() { + auto rec = MakeUnique<TokenCacheRecord>(); + rec->mKey = aKey; + rec->mExpirationTime = aExpirationTime; + MOZ_ASSERT(rec->mToken.IsEmpty()); + rec->mToken.AppendElements(aToken, aTokenLen); + rec->mId = ++sRecordId; + rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes); + rec->mSessionCacheInfo.mSucceededCertChainBytes = + std::move(succeededCertChainBytes); + if (isEV) { + rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV; + } + rec->mSessionCacheInfo.mCertificateTransparencyStatus = + certificateTransparencyStatus; + rec->mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot = + std::move(isBuiltCertChainRootBuiltInRoot); + rec->mSessionCacheInfo.mOverridableErrorCategory = overridableErrorCategory; + rec->mSessionCacheInfo.mFailedCertChainBytes = + std::move(failedCertChainBytes); + return rec; + }; + + TokenCacheEntry* const cacheEntry = + gInstance->mTokenCacheRecords.WithEntryHandle(aKey, [&](auto&& entry) { + if (!entry) { + auto rec = makeUniqueRecord(); + auto cacheEntry = MakeUnique<TokenCacheEntry>(); + cacheEntry->AddRecord(std::move(rec), gInstance->mExpirationArray); + entry.Insert(std::move(cacheEntry)); + } else { + // To make sure the cache size is synced, we take away the size of + // whole entry and add it back later. + gInstance->mCacheSize -= entry.Data()->Size(); + entry.Data()->AddRecord(makeUniqueRecord(), + gInstance->mExpirationArray); + } + + return entry->get(); + }); + + gInstance->mCacheSize += cacheEntry->Size(); + + gInstance->LogStats(); + + gInstance->EvictIfNecessary(); + + return NS_OK; +} + +// static +nsresult SSLTokensCache::Get(const nsACString& aKey, nsTArray<uint8_t>& aToken, + SessionCacheInfo& aResult, uint64_t* aTokenId) { + StaticMutexAutoLock lock(sLock); + + LOG(("SSLTokensCache::Get [key=%s]", PromiseFlatCString(aKey).get())); + + if (!gInstance) { + LOG((" service not initialized")); + return NS_ERROR_NOT_INITIALIZED; + } + + return gInstance->GetLocked(aKey, aToken, aResult, aTokenId); +} + +nsresult SSLTokensCache::GetLocked(const nsACString& aKey, + nsTArray<uint8_t>& aToken, + SessionCacheInfo& aResult, + uint64_t* aTokenId) { + sLock.AssertCurrentThreadOwns(); + + TokenCacheEntry* cacheEntry = nullptr; + + if (mTokenCacheRecords.Get(aKey, &cacheEntry)) { + if (cacheEntry->RecordCount() == 0) { + MOZ_ASSERT(false, "Found a cacheEntry with no records"); + mTokenCacheRecords.Remove(aKey); + return NS_ERROR_NOT_AVAILABLE; + } + + const UniquePtr<TokenCacheRecord>& rec = cacheEntry->Get(); + aToken = rec->mToken.Clone(); + aResult = rec->mSessionCacheInfo.Clone(); + if (aTokenId) { + *aTokenId = rec->mId; + } + mCacheSize -= rec->Size(); + cacheEntry->RemoveWithId(rec->mId); + if (cacheEntry->RecordCount() == 0) { + mTokenCacheRecords.Remove(aKey); + } + return NS_OK; + } + + LOG((" token not found")); + return NS_ERROR_NOT_AVAILABLE; +} + +// static +nsresult SSLTokensCache::Remove(const nsACString& aKey, uint64_t aId) { + StaticMutexAutoLock lock(sLock); + + LOG(("SSLTokensCache::Remove [key=%s]", PromiseFlatCString(aKey).get())); + + if (!gInstance) { + LOG((" service not initialized")); + return NS_ERROR_NOT_INITIALIZED; + } + + return gInstance->RemoveLocked(aKey, aId); +} + +nsresult SSLTokensCache::RemoveLocked(const nsACString& aKey, uint64_t aId) { + sLock.AssertCurrentThreadOwns(); + + LOG(("SSLTokensCache::RemoveLocked [key=%s, id=%" PRIu64 "]", + PromiseFlatCString(aKey).get(), aId)); + + TokenCacheEntry* cacheEntry; + if (!mTokenCacheRecords.Get(aKey, &cacheEntry)) { + return NS_ERROR_NOT_AVAILABLE; + } + + UniquePtr<TokenCacheRecord> rec = cacheEntry->RemoveWithId(aId); + if (!rec) { + return NS_ERROR_NOT_AVAILABLE; + } + + mCacheSize -= rec->Size(); + if (cacheEntry->RecordCount() == 0) { + mTokenCacheRecords.Remove(aKey); + } + + // Release the record immediately, so mExpirationArray can be also updated. + rec = nullptr; + + LogStats(); + + return NS_OK; +} + +// static +nsresult SSLTokensCache::RemoveAll(const nsACString& aKey) { + StaticMutexAutoLock lock(sLock); + + LOG(("SSLTokensCache::RemoveAll [key=%s]", PromiseFlatCString(aKey).get())); + + if (!gInstance) { + LOG((" service not initialized")); + return NS_ERROR_NOT_INITIALIZED; + } + + return gInstance->RemovAllLocked(aKey); +} + +nsresult SSLTokensCache::RemovAllLocked(const nsACString& aKey) { + sLock.AssertCurrentThreadOwns(); + + LOG(("SSLTokensCache::RemovAllLocked [key=%s]", + PromiseFlatCString(aKey).get())); + + UniquePtr<TokenCacheEntry> cacheEntry; + if (!mTokenCacheRecords.Remove(aKey, &cacheEntry)) { + return NS_ERROR_NOT_AVAILABLE; + } + + mCacheSize -= cacheEntry->Size(); + cacheEntry = nullptr; + + LogStats(); + + return NS_OK; +} + +void SSLTokensCache::OnRecordDestroyed(TokenCacheRecord* aRec) { + mExpirationArray.RemoveElement(aRec); +} + +void SSLTokensCache::EvictIfNecessary() { + // kilobytes to bytes + uint32_t capacity = StaticPrefs::network_ssl_tokens_cache_capacity() << 10; + if (mCacheSize <= capacity) { + return; + } + + LOG(("SSLTokensCache::EvictIfNecessary - evicting")); + + mExpirationArray.Sort(ExpirationComparator()); + + while (mCacheSize > capacity && mExpirationArray.Length() > 0) { + DebugOnly<nsresult> rv = + RemoveLocked(mExpirationArray[0]->mKey, mExpirationArray[0]->mId); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "mExpirationArray and mTokenCacheRecords are out of sync!"); + } +} + +void SSLTokensCache::LogStats() { + if (!LOG5_ENABLED()) { + return; + } + LOG(("SSLTokensCache::LogStats [count=%zu, cacheSize=%u]", + mExpirationArray.Length(), mCacheSize)); + for (const auto& ent : mTokenCacheRecords.Values()) { + const UniquePtr<TokenCacheRecord>& rec = ent->Get(); + LOG(("key=%s count=%d", rec->mKey.get(), ent->RecordCount())); + } +} + +size_t SSLTokensCache::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t n = mallocSizeOf(this); + + n += mTokenCacheRecords.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mExpirationArray.ShallowSizeOfExcludingThis(mallocSizeOf); + + for (uint32_t i = 0; i < mExpirationArray.Length(); ++i) { + n += mallocSizeOf(mExpirationArray[i]); + n += mExpirationArray[i]->mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf); + n += mExpirationArray[i]->mToken.ShallowSizeOfExcludingThis(mallocSizeOf); + } + + return n; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(SSLTokensCacheMallocSizeOf) + +NS_IMETHODIMP +SSLTokensCache::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + StaticMutexAutoLock lock(sLock); + + MOZ_COLLECT_REPORT("explicit/network/ssl-tokens-cache", KIND_HEAP, + UNITS_BYTES, + SizeOfIncludingThis(SSLTokensCacheMallocSizeOf), + "Memory used for the SSL tokens cache."); + + return NS_OK; +} + +// static +void SSLTokensCache::Clear() { + LOG(("SSLTokensCache::Clear")); + + StaticMutexAutoLock lock(sLock); + if (!gInstance) { + LOG((" service not initialized")); + return; + } + + gInstance->mExpirationArray.Clear(); + gInstance->mTokenCacheRecords.Clear(); + gInstance->mCacheSize = 0; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/SSLTokensCache.h b/netwerk/base/SSLTokensCache.h new file mode 100644 index 0000000000..b335457526 --- /dev/null +++ b/netwerk/base/SSLTokensCache.h @@ -0,0 +1,123 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SSLTokensCache_h_ +#define SSLTokensCache_h_ + +#include "CertVerifier.h" // For EVStatus +#include "mozilla/Maybe.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPtr.h" +#include "nsClassHashtable.h" +#include "nsIMemoryReporter.h" +#include "nsITransportSecurityInfo.h" +#include "nsTArray.h" +#include "nsTHashMap.h" +#include "nsXULAppAPI.h" + +class CommonSocketControl; + +namespace mozilla { +namespace net { + +struct SessionCacheInfo { + SessionCacheInfo Clone() const; + + psm::EVStatus mEVStatus = psm::EVStatus::NotEV; + uint16_t mCertificateTransparencyStatus = + nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE; + nsTArray<uint8_t> mServerCertBytes; + Maybe<nsTArray<nsTArray<uint8_t>>> mSucceededCertChainBytes; + Maybe<bool> mIsBuiltCertChainRootBuiltInRoot; + nsITransportSecurityInfo::OverridableErrorCategory mOverridableErrorCategory; + Maybe<nsTArray<nsTArray<uint8_t>>> mFailedCertChainBytes; +}; + +class SSLTokensCache : public nsIMemoryReporter { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + + friend class ExpirationComparator; + + static nsresult Init(); + static nsresult Shutdown(); + + static nsresult Put(const nsACString& aKey, const uint8_t* aToken, + uint32_t aTokenLen, CommonSocketControl* aSocketControl); + static nsresult Put(const nsACString& aKey, const uint8_t* aToken, + uint32_t aTokenLen, CommonSocketControl* aSocketControl, + PRUint32 aExpirationTime); + static nsresult Get(const nsACString& aKey, nsTArray<uint8_t>& aToken, + SessionCacheInfo& aResult, uint64_t* aTokenId = nullptr); + static nsresult Remove(const nsACString& aKey, uint64_t aId); + static nsresult RemoveAll(const nsACString& aKey); + static void Clear(); + + private: + SSLTokensCache(); + virtual ~SSLTokensCache(); + + nsresult RemoveLocked(const nsACString& aKey, uint64_t aId); + nsresult RemovAllLocked(const nsACString& aKey); + nsresult GetLocked(const nsACString& aKey, nsTArray<uint8_t>& aToken, + SessionCacheInfo& aResult, uint64_t* aTokenId); + + void EvictIfNecessary(); + void LogStats(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + static mozilla::StaticRefPtr<SSLTokensCache> gInstance; + static StaticMutex sLock MOZ_UNANNOTATED; + static uint64_t sRecordId; + + uint32_t mCacheSize{0}; // Actual cache size in bytes + + class TokenCacheRecord { + public: + ~TokenCacheRecord(); + + uint32_t Size() const; + void Reset(); + + nsCString mKey; + PRUint32 mExpirationTime = 0; + nsTArray<uint8_t> mToken; + SessionCacheInfo mSessionCacheInfo; + // An unique id to identify the record. Mostly used when we want to remove a + // record from TokenCacheEntry. + uint64_t mId = 0; + }; + + class TokenCacheEntry { + public: + uint32_t Size() const; + // Add a record into |mRecords|. To make sure |mRecords| is sorted, we + // iterate |mRecords| everytime to find a right place to insert the new + // record. + void AddRecord(UniquePtr<TokenCacheRecord>&& aRecord, + nsTArray<TokenCacheRecord*>& aExpirationArray); + // This function returns the first record in |mRecords|. + const UniquePtr<TokenCacheRecord>& Get(); + UniquePtr<TokenCacheRecord> RemoveWithId(uint64_t aId); + uint32_t RecordCount() const { return mRecords.Length(); } + const nsTArray<UniquePtr<TokenCacheRecord>>& Records() { return mRecords; } + + private: + // The records in this array are ordered by the expiration time. + nsTArray<UniquePtr<TokenCacheRecord>> mRecords; + }; + + void OnRecordDestroyed(TokenCacheRecord* aRec); + + nsClassHashtable<nsCStringHashKey, TokenCacheEntry> mTokenCacheRecords; + nsTArray<TokenCacheRecord*> mExpirationArray; +}; + +} // namespace net +} // namespace mozilla + +#endif // SSLTokensCache_h_ diff --git a/netwerk/base/ShutdownLayer.cpp b/netwerk/base/ShutdownLayer.cpp new file mode 100644 index 0000000000..db62ea691d --- /dev/null +++ b/netwerk/base/ShutdownLayer.cpp @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "ShutdownLayer.h" +#include "prerror.h" +#include "private/pprio.h" +#include "prmem.h" +#include <winsock2.h> + +static PRDescIdentity sWinSockShutdownLayerIdentity; +static PRIOMethods sWinSockShutdownLayerMethods; +static PRIOMethods* sWinSockShutdownLayerMethodsPtr = nullptr; + +namespace mozilla { +namespace net { + +extern PRDescIdentity nsNamedPipeLayerIdentity; + +} // namespace net +} // namespace mozilla + +PRStatus WinSockClose(PRFileDesc* aFd) { + MOZ_RELEASE_ASSERT(aFd->identity == sWinSockShutdownLayerIdentity, + "Windows shutdown layer not on the top of the stack"); + + PROsfd osfd = PR_FileDesc2NativeHandle(aFd); + if (osfd != -1) { + shutdown(osfd, SD_BOTH); + } + + aFd->identity = PR_INVALID_IO_LAYER; + + if (aFd->lower) { + return aFd->lower->methods->close(aFd->lower); + } else { + return PR_SUCCESS; + } +} + +nsresult mozilla::net::AttachShutdownLayer(PRFileDesc* aFd) { + if (!sWinSockShutdownLayerMethodsPtr) { + sWinSockShutdownLayerIdentity = + PR_GetUniqueIdentity("windows shutdown call layer"); + sWinSockShutdownLayerMethods = *PR_GetDefaultIOMethods(); + sWinSockShutdownLayerMethods.close = WinSockClose; + sWinSockShutdownLayerMethodsPtr = &sWinSockShutdownLayerMethods; + } + + if (mozilla::net::nsNamedPipeLayerIdentity && + PR_GetIdentitiesLayer(aFd, mozilla::net::nsNamedPipeLayerIdentity)) { + // Do not attach shutdown layer on named pipe layer, + // it is for PR_NSPR_IO_LAYER only. + return NS_OK; + } + + PRFileDesc* layer; + PRStatus status; + + layer = PR_CreateIOLayerStub(sWinSockShutdownLayerIdentity, + sWinSockShutdownLayerMethodsPtr); + if (!layer) { + return NS_OK; + } + + status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer); + + if (status == PR_FAILURE) { + PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). + return NS_ERROR_FAILURE; + } + return NS_OK; +} diff --git a/netwerk/base/ShutdownLayer.h b/netwerk/base/ShutdownLayer.h new file mode 100644 index 0000000000..2bf68fdcdc --- /dev/null +++ b/netwerk/base/ShutdownLayer.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SHUTDOWNLAYER_H___ +#define SHUTDOWNLAYER_H___ + +#include "nscore.h" +#include "prio.h" + +namespace mozilla { +namespace net { + +// This is only for windows. This layer will be attached jus before PR_CLose +// is call and it will only call shutdown(sock). +extern nsresult AttachShutdownLayer(PRFileDesc* fd); + +} // namespace net +} // namespace mozilla + +#endif /* SHUTDOWN_H___ */ diff --git a/netwerk/base/SimpleBuffer.cpp b/netwerk/base/SimpleBuffer.cpp new file mode 100644 index 0000000000..46b8654efa --- /dev/null +++ b/netwerk/base/SimpleBuffer.cpp @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SimpleBuffer.h" +#include <algorithm> + +namespace mozilla { +namespace net { + +nsresult SimpleBuffer::Write(char* src, size_t len) { + NS_ASSERT_OWNINGTHREAD(SimpleBuffer); + if (NS_FAILED(mStatus)) { + return mStatus; + } + + while (len > 0) { + SimpleBufferPage* p = mBufferList.getLast(); + if (p && (p->mWriteOffset == SimpleBufferPage::kSimpleBufferPageSize)) { + // no room.. make a new page + p = nullptr; + } + if (!p) { + p = new (fallible) SimpleBufferPage(); + if (!p) { + mStatus = NS_ERROR_OUT_OF_MEMORY; + return mStatus; + } + mBufferList.insertBack(p); + } + size_t roomOnPage = + SimpleBufferPage::kSimpleBufferPageSize - p->mWriteOffset; + size_t toWrite = std::min(roomOnPage, len); + memcpy(p->mBuffer + p->mWriteOffset, src, toWrite); + src += toWrite; + len -= toWrite; + p->mWriteOffset += toWrite; + mAvailable += toWrite; + } + return NS_OK; +} + +size_t SimpleBuffer::Read(char* dest, size_t maxLen) { + NS_ASSERT_OWNINGTHREAD(SimpleBuffer); + if (NS_FAILED(mStatus)) { + return 0; + } + + size_t rv = 0; + for (SimpleBufferPage* p = mBufferList.getFirst(); p && (rv < maxLen); + p = mBufferList.getFirst()) { + size_t avail = p->mWriteOffset - p->mReadOffset; + size_t toRead = std::min(avail, (maxLen - rv)); + memcpy(dest + rv, p->mBuffer + p->mReadOffset, toRead); + rv += toRead; + p->mReadOffset += toRead; + if (p->mReadOffset == p->mWriteOffset) { + p->remove(); + delete p; + } + } + + MOZ_ASSERT(mAvailable >= rv); + mAvailable -= rv; + return rv; +} + +size_t SimpleBuffer::Available() { + NS_ASSERT_OWNINGTHREAD(SimpleBuffer); + return NS_SUCCEEDED(mStatus) ? mAvailable : 0; +} + +void SimpleBuffer::Clear() { + NS_ASSERT_OWNINGTHREAD(SimpleBuffer); + SimpleBufferPage* p; + while ((p = mBufferList.popFirst())) { + delete p; + } + mAvailable = 0; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/SimpleBuffer.h b/netwerk/base/SimpleBuffer.h new file mode 100644 index 0000000000..031f59afe5 --- /dev/null +++ b/netwerk/base/SimpleBuffer.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SimpleBuffer_h__ +#define SimpleBuffer_h__ + +/* + This class is similar to a nsPipe except it does not have any locking, stores + an unbounded amount of data, can only be used on one thread, and has much + simpler result code semantics to deal with. +*/ + +#include "prtypes.h" +#include "ErrorList.h" +#include "mozilla/LinkedList.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace net { + +class SimpleBufferPage : public LinkedListElement<SimpleBufferPage> { + public: + SimpleBufferPage() = default; + static const size_t kSimpleBufferPageSize = 32000; + + private: + friend class SimpleBuffer; + char mBuffer[kSimpleBufferPageSize]{0}; + size_t mReadOffset{0}; + size_t mWriteOffset{0}; +}; + +class SimpleBuffer { + public: + SimpleBuffer() = default; + ~SimpleBuffer() = default; + + nsresult Write(char* src, size_t len); // return OK or OUT_OF_MEMORY + size_t Read(char* dest, size_t maxLen); // return bytes read + size_t Available(); + void Clear(); + + private: + NS_DECL_OWNINGTHREAD + + nsresult mStatus{NS_OK}; + AutoCleanLinkedList<SimpleBufferPage> mBufferList; + size_t mAvailable{0}; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/SimpleChannel.cpp b/netwerk/base/SimpleChannel.cpp new file mode 100644 index 0000000000..6297331135 --- /dev/null +++ b/netwerk/base/SimpleChannel.cpp @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SimpleChannel.h" + +#include "nsBaseChannel.h" +#include "nsIChannel.h" +#include "nsIChildChannel.h" +#include "nsICancelable.h" +#include "nsIInputStream.h" +#include "nsIRequest.h" +#include "nsISupportsImpl.h" +#include "nsNetUtil.h" + +#include "mozilla/Try.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/PSimpleChannelChild.h" + +namespace mozilla { +namespace net { + +SimpleChannel::SimpleChannel(UniquePtr<SimpleChannelCallbacks>&& aCallbacks) + : mCallbacks(std::move(aCallbacks)) { + EnableSynthesizedProgressEvents(true); +} + +nsresult SimpleChannel::OpenContentStream(bool async, + nsIInputStream** streamOut, + nsIChannel** channel) { + NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIInputStream> stream; + MOZ_TRY_VAR(stream, mCallbacks->OpenContentStream(async, this)); + MOZ_ASSERT(stream); + + mCallbacks = nullptr; + + *channel = nullptr; + stream.forget(streamOut); + return NS_OK; +} + +nsresult SimpleChannel::BeginAsyncRead(nsIStreamListener* listener, + nsIRequest** request, + nsICancelable** cancelableRequest) { + NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED); + + RequestOrReason res = mCallbacks->StartAsyncRead(listener, this); + + if (res.isErr()) { + return res.propagateErr(); + } + + mCallbacks = nullptr; + + RequestOrCancelable value = res.unwrap(); + + if (value.is<NotNullRequest>()) { + nsCOMPtr<nsIRequest> req = value.as<NotNullRequest>().get(); + req.forget(request); + } else if (value.is<NotNullCancelable>()) { + nsCOMPtr<nsICancelable> cancelable = value.as<NotNullCancelable>().get(); + cancelable.forget(cancelableRequest); + } else { + MOZ_ASSERT_UNREACHABLE( + "StartAsyncRead didn't return a NotNull nsIRequest or nsICancelable."); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED(SimpleChannelChild, SimpleChannel, nsIChildChannel) + +SimpleChannelChild::SimpleChannelChild( + UniquePtr<SimpleChannelCallbacks>&& aCallbacks) + : SimpleChannel(std::move(aCallbacks)) {} + +NS_IMETHODIMP +SimpleChannelChild::ConnectParent(uint32_t aId) { + MOZ_ASSERT(NS_IsMainThread()); + mozilla::dom::ContentChild* cc = + static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager()); + if (cc->IsShuttingDown()) { + return NS_ERROR_FAILURE; + } + + // Reference freed in DeallocPSimpleChannelChild. + if (!gNeckoChild->SendPSimpleChannelConstructor(do_AddRef(this).take(), + aId)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) { + if (CanSend()) { + MOZ_ASSERT(NS_IsMainThread()); + } + + nsresult rv; + rv = AsyncOpen(aListener); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (CanSend()) { + Unused << Send__delete__(this); + } + return NS_OK; +} + +already_AddRefed<nsIChannel> NS_NewSimpleChannelInternal( + nsIURI* aURI, nsILoadInfo* aLoadInfo, + UniquePtr<SimpleChannelCallbacks>&& aCallbacks) { + RefPtr<SimpleChannel> chan; + if (IsNeckoChild()) { + chan = new SimpleChannelChild(std::move(aCallbacks)); + } else { + chan = new SimpleChannel(std::move(aCallbacks)); + } + + chan->SetURI(aURI); + + MOZ_ALWAYS_SUCCEEDS(chan->SetLoadInfo(aLoadInfo)); + + return chan.forget(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/SimpleChannel.h b/netwerk/base/SimpleChannel.h new file mode 100644 index 0000000000..d469adf65d --- /dev/null +++ b/netwerk/base/SimpleChannel.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SimpleChannel_h +#define SimpleChannel_h + +#include "mozilla/ResultExtensions.h" +#include "mozilla/UniquePtr.h" +#include "nsBaseChannel.h" +#include "nsIChildChannel.h" +#include "mozilla/net/PSimpleChannelChild.h" +#include "nsCOMPtr.h" + +class nsIChannel; +class nsIInputStream; +class nsILoadInfo; +class nsIRequest; +class nsIStreamListener; +class nsIURI; + +//----------------------------------------------------------------------------- + +namespace mozilla { + +using InputStreamOrReason = Result<nsCOMPtr<nsIInputStream>, nsresult>; +using NotNullRequest = NotNull<nsCOMPtr<nsIRequest>>; +using NotNullCancelable = NotNull<nsCOMPtr<nsICancelable>>; +using RequestOrCancelable = Variant<NotNullRequest, NotNullCancelable>; +using RequestOrReason = Result<RequestOrCancelable, nsresult>; + +namespace net { + +class SimpleChannelCallbacks { + public: + virtual InputStreamOrReason OpenContentStream(bool async, + nsIChannel* channel) = 0; + + virtual RequestOrReason StartAsyncRead(nsIStreamListener* stream, + nsIChannel* channel) = 0; + + virtual ~SimpleChannelCallbacks() = default; +}; + +template <typename F1, typename F2, typename T> +class SimpleChannelCallbacksImpl final : public SimpleChannelCallbacks { + public: + SimpleChannelCallbacksImpl(F1&& aStartAsyncRead, F2&& aOpenContentStream, + T* context) + : mStartAsyncRead(aStartAsyncRead), + mOpenContentStream(aOpenContentStream), + mContext(context) {} + + virtual ~SimpleChannelCallbacksImpl() = default; + + virtual InputStreamOrReason OpenContentStream(bool async, + nsIChannel* channel) override { + return mOpenContentStream(async, channel, mContext); + } + + virtual RequestOrReason StartAsyncRead(nsIStreamListener* listener, + nsIChannel* channel) override { + return mStartAsyncRead(listener, channel, mContext); + } + + private: + F1 mStartAsyncRead; + F2 mOpenContentStream; + RefPtr<T> mContext; +}; + +class SimpleChannel : public nsBaseChannel { + public: + explicit SimpleChannel(UniquePtr<SimpleChannelCallbacks>&& aCallbacks); + + protected: + virtual ~SimpleChannel() = default; + + virtual nsresult OpenContentStream(bool async, nsIInputStream** streamOut, + nsIChannel** channel) override; + + virtual nsresult BeginAsyncRead(nsIStreamListener* listener, + nsIRequest** request, + nsICancelable** cancelableRequest) override; + + private: + UniquePtr<SimpleChannelCallbacks> mCallbacks; +}; + +class SimpleChannelChild final : public SimpleChannel, + public nsIChildChannel, + public PSimpleChannelChild { + public: + explicit SimpleChannelChild(UniquePtr<SimpleChannelCallbacks>&& aCallbacks); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSICHILDCHANNEL + + private: + virtual ~SimpleChannelChild() = default; +}; + +already_AddRefed<nsIChannel> NS_NewSimpleChannelInternal( + nsIURI* aURI, nsILoadInfo* aLoadInfo, + UniquePtr<SimpleChannelCallbacks>&& aCallbacks); + +} // namespace net +} // namespace mozilla + +/** + * Creates a simple channel which wraps an input stream created by the given + * callbacks. The callbacks are not called until the underlying AsyncOpen or + * Open methods are called, and correspond to the nsBaseChannel::StartAsyncRead + * and nsBaseChannel::OpenContentStream methods of the same names. + * + * The last two arguments of each callback are the created channel instance, + * and the ref-counted context object passed to NS_NewSimpleChannel. A strong + * reference to that object is guaranteed to be kept alive until after a + * callback successfully completes. + */ +template <typename T, typename F1, typename F2> +inline already_AddRefed<nsIChannel> NS_NewSimpleChannel( + nsIURI* aURI, nsILoadInfo* aLoadInfo, T* context, F1&& aStartAsyncRead, + F2&& aOpenContentStream) { + using namespace mozilla; + + auto callbacks = MakeUnique<net::SimpleChannelCallbacksImpl<F1, F2, T>>( + std::forward<F1>(aStartAsyncRead), std::forward<F2>(aOpenContentStream), + context); + + return net::NS_NewSimpleChannelInternal(aURI, aLoadInfo, + std::move(callbacks)); +} + +template <typename T, typename F1> +inline already_AddRefed<nsIChannel> NS_NewSimpleChannel(nsIURI* aURI, + nsILoadInfo* aLoadInfo, + T* context, + F1&& aStartAsyncRead) { + using namespace mozilla; + + auto openContentStream = [](bool async, nsIChannel* channel, T* context) { + return Err(NS_ERROR_NOT_IMPLEMENTED); + }; + + return NS_NewSimpleChannel(aURI, aLoadInfo, context, + std::forward<F1>(aStartAsyncRead), + std::move(openContentStream)); +} + +#endif // SimpleChannel_h diff --git a/netwerk/base/SimpleChannelParent.cpp b/netwerk/base/SimpleChannelParent.cpp new file mode 100644 index 0000000000..3a58e14843 --- /dev/null +++ b/netwerk/base/SimpleChannelParent.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=4 sw=2 sts=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SimpleChannelParent.h" +#include "mozilla/Assertions.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" + +#ifdef FUZZING_SNAPSHOT +# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) (void)__VA_ARGS__ +#else +# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) MOZ_ALWAYS_SUCCEEDS(__VA_ARGS__) +#endif + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(SimpleChannelParent, nsIParentChannel, nsIStreamListener) + +bool SimpleChannelParent::Init(const uint64_t& aChannelId) { + nsCOMPtr<nsIChannel> channel; + + MOZ_ALWAYS_SUCCEEDS_FUZZING( + NS_LinkRedirectChannels(aChannelId, this, getter_AddRefs(channel))); + + return true; +} + +NS_IMETHODIMP +SimpleChannelParent::SetParentListener(ParentChannelListener* aListener) { + // Nothing to do. + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags, + bool aIsThirdParty) { + // Nothing to do. + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::SetClassifierMatchedInfo(const nsACString& aList, + const nsACString& aProvider, + const nsACString& aPrefix) { + // nothing to do + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::SetClassifierMatchedTrackingInfo( + const nsACString& aLists, const nsACString& aFullHashes) { + // nothing to do + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::Delete() { + // Nothing to do. + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::GetRemoteType(nsACString& aRemoteType) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void SimpleChannelParent::ActorDestroy(ActorDestroyReason aWhy) {} + +NS_IMETHODIMP +SimpleChannelParent::OnStartRequest(nsIRequest* aRequest) { + // We don't have a way to prevent nsBaseChannel from calling AsyncOpen on + // the created nsSimpleChannel. We don't have anywhere to send the data in the + // parent, so abort the binding. + return NS_BINDING_ABORTED; +} + +NS_IMETHODIMP +SimpleChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { + // See above. + MOZ_ASSERT(NS_FAILED(aStatusCode)); + return NS_OK; +} + +NS_IMETHODIMP +SimpleChannelParent::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) { + // See above. + MOZ_CRASH("Should never be called"); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/SimpleChannelParent.h b/netwerk/base/SimpleChannelParent.h new file mode 100644 index 0000000000..6a8f2c867b --- /dev/null +++ b/netwerk/base/SimpleChannelParent.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=4 sw=2 sts=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NS_SIMPLECHANNELPARENT_H +#define NS_SIMPLECHANNELPARENT_H + +#include "nsIParentChannel.h" +#include "nsISupportsImpl.h" + +#include "mozilla/net/PSimpleChannelParent.h" + +namespace mozilla { +namespace net { + +// In order to support HTTP redirects, we need to implement the HTTP +// redirection API, which requires a class that implements nsIParentChannel +// and which calls NS_LinkRedirectChannels. +class SimpleChannelParent : public nsIParentChannel, + public PSimpleChannelParent { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPARENTCHANNEL + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER; // semicolon for clang-format bug 1629756 + + [[nodiscard]] bool Init(const uint64_t& aChannelId); + + private: + ~SimpleChannelParent() = default; + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +} // namespace net +} // namespace mozilla + +#endif /* NS_SIMPLECHANNELPARENT_H */ diff --git a/netwerk/base/TLSServerSocket.cpp b/netwerk/base/TLSServerSocket.cpp new file mode 100644 index 0000000000..131ea50573 --- /dev/null +++ b/netwerk/base/TLSServerSocket.cpp @@ -0,0 +1,446 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TLSServerSocket.h" + +#include "mozilla/net/DNS.h" +#include "nsComponentManagerUtils.h" +#include "nsDependentSubstring.h" +#include "nsIServerSocket.h" +#include "nsIX509Cert.h" +#include "nsIX509CertDB.h" +#include "nsNetCID.h" +#include "nsProxyRelease.h" +#include "nsServiceManagerUtils.h" +#include "nsSocketTransport2.h" +#include "nsThreadUtils.h" +#include "ScopedNSSTypes.h" +#include "ssl.h" + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- +// TLSServerSocket +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS_INHERITED(TLSServerSocket, nsServerSocket, nsITLSServerSocket) + +nsresult TLSServerSocket::SetSocketDefaults() { + // Set TLS options on the listening socket + mFD = SSL_ImportFD(nullptr, mFD); + if (NS_WARN_IF(!mFD)) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + SSL_OptionSet(mFD, SSL_SECURITY, true); + SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_CLIENT, false); + SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_SERVER, true); + SSL_OptionSet(mFD, SSL_NO_CACHE, true); + + // We don't currently notify the server API consumer of renegotiation events + // (to revalidate peer certs, etc.), so disable it for now. + SSL_OptionSet(mFD, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER); + + SetSessionTickets(true); + SetRequestClientCertificate(REQUEST_NEVER); + + return NS_OK; +} + +void TLSServerSocket::CreateClientTransport(PRFileDesc* aClientFD, + const NetAddr& aClientAddr) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + nsresult rv; + + RefPtr<nsSocketTransport> trans = new nsSocketTransport; + if (NS_WARN_IF(!trans)) { + mCondition = NS_ERROR_OUT_OF_MEMORY; + return; + } + + RefPtr<TLSServerConnectionInfo> info = new TLSServerConnectionInfo(); + info->mServerSocket = this; + info->mTransport = trans; + nsCOMPtr<nsIInterfaceRequestor> infoInterfaceRequestor(info); + rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr, + infoInterfaceRequestor); + if (NS_WARN_IF(NS_FAILED(rv))) { + mCondition = rv; + return; + } + + // Override the default peer certificate validation, so that server consumers + // can make their own choice after the handshake completes. + SSL_AuthCertificateHook(aClientFD, AuthCertificateHook, nullptr); + // Once the TLS handshake has completed, the server consumer is notified and + // has access to various TLS state details. + // It's safe to pass info here because the socket transport holds it as + // |mSecInfo| which keeps it alive for the lifetime of the socket. + SSL_HandshakeCallback(aClientFD, TLSServerConnectionInfo::HandshakeCallback, + info); + + // Notify the consumer of the new client so it can manage the streams. + // Security details aren't known yet. The security observer will be notified + // later when they are ready. + nsCOMPtr<nsIServerSocket> serverSocket = + do_QueryInterface(NS_ISUPPORTS_CAST(nsITLSServerSocket*, this)); + mListener->OnSocketAccepted(serverSocket, trans); +} + +nsresult TLSServerSocket::OnSocketListen() { + if (NS_WARN_IF(!mServerCert)) { + return NS_ERROR_NOT_INITIALIZED; + } + + UniqueCERTCertificate cert(mServerCert->GetCert()); + if (NS_WARN_IF(!cert)) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + UniqueSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert.get(), nullptr)); + if (NS_WARN_IF(!key)) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + SSLKEAType certKEA = NSS_FindCertKEAType(cert.get()); + + nsresult rv = + MapSECStatus(SSL_ConfigSecureServer(mFD, cert.get(), key.get(), certKEA)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +// static +SECStatus TLSServerSocket::AuthCertificateHook(void* arg, PRFileDesc* fd, + PRBool checksig, + PRBool isServer) { + // Allow any client cert here, server consumer code can decide whether it's + // okay after being notified of the new client socket. + return SECSuccess; +} + +//----------------------------------------------------------------------------- +// TLSServerSocket::nsITLSServerSocket +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +TLSServerSocket::GetServerCert(nsIX509Cert** aCert) { + if (NS_WARN_IF(!aCert)) { + return NS_ERROR_INVALID_POINTER; + } + *aCert = do_AddRef(mServerCert).take(); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetServerCert(nsIX509Cert* aCert) { + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + mServerCert = aCert; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetSessionTickets(bool aEnabled) { + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + SSL_OptionSet(mFD, SSL_ENABLE_SESSION_TICKETS, aEnabled); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetRequestClientCertificate(uint32_t aMode) { + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + SSL_OptionSet(mFD, SSL_REQUEST_CERTIFICATE, aMode != REQUEST_NEVER); + + switch (aMode) { + case REQUEST_ALWAYS: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NO_ERROR); + break; + case REQUIRE_FIRST_HANDSHAKE: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_FIRST_HANDSHAKE); + break; + case REQUIRE_ALWAYS: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_ALWAYS); + break; + default: + SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER); + } + return NS_OK; +} + +NS_IMETHODIMP +TLSServerSocket::SetVersionRange(uint16_t aMinVersion, uint16_t aMaxVersion) { + // If AsyncListen was already called (and set mListener), it's too late to set + // this. + if (NS_WARN_IF(mListener)) { + return NS_ERROR_IN_PROGRESS; + } + + SSLVersionRange range = {aMinVersion, aMaxVersion}; + if (SSL_VersionRangeSet(mFD, &range) != SECSuccess) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// TLSServerConnectionInfo +//----------------------------------------------------------------------------- + +namespace { + +class TLSServerSecurityObserverProxy final + : public nsITLSServerSecurityObserver { + ~TLSServerSecurityObserverProxy() = default; + + public: + explicit TLSServerSecurityObserverProxy( + nsITLSServerSecurityObserver* aListener) + : mListener(new nsMainThreadPtrHolder<nsITLSServerSecurityObserver>( + "TLSServerSecurityObserverProxy::mListener", aListener)) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITLSSERVERSECURITYOBSERVER + + class OnHandshakeDoneRunnable : public Runnable { + public: + OnHandshakeDoneRunnable( + const nsMainThreadPtrHandle<nsITLSServerSecurityObserver>& aListener, + nsITLSServerSocket* aServer, nsITLSClientStatus* aStatus) + : Runnable( + "net::TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable"), + mListener(aListener), + mServer(aServer), + mStatus(aStatus) {} + + NS_DECL_NSIRUNNABLE + + private: + nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener; + nsCOMPtr<nsITLSServerSocket> mServer; + nsCOMPtr<nsITLSClientStatus> mStatus; + }; + + private: + nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener; +}; + +NS_IMPL_ISUPPORTS(TLSServerSecurityObserverProxy, nsITLSServerSecurityObserver) + +NS_IMETHODIMP +TLSServerSecurityObserverProxy::OnHandshakeDone(nsITLSServerSocket* aServer, + nsITLSClientStatus* aStatus) { + RefPtr<OnHandshakeDoneRunnable> r = + new OnHandshakeDoneRunnable(mListener, aServer, aStatus); + return NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable::Run() { + mListener->OnHandshakeDone(mServer, mStatus); + return NS_OK; +} + +} // namespace + +NS_IMPL_ISUPPORTS(TLSServerConnectionInfo, nsITLSServerConnectionInfo, + nsITLSClientStatus, nsIInterfaceRequestor) + +TLSServerConnectionInfo::~TLSServerConnectionInfo() { + RefPtr<nsITLSServerSecurityObserver> observer; + { + MutexAutoLock lock(mLock); + observer = ToRefPtr(std::move(mSecurityObserver)); + } + + if (observer) { + NS_ReleaseOnMainThread("TLSServerConnectionInfo::mSecurityObserver", + observer.forget()); + } +} + +NS_IMETHODIMP +TLSServerConnectionInfo::SetSecurityObserver( + nsITLSServerSecurityObserver* aObserver) { + { + MutexAutoLock lock(mLock); + if (!aObserver) { + mSecurityObserver = nullptr; + return NS_OK; + } + + mSecurityObserver = new TLSServerSecurityObserverProxy(aObserver); + // Call `OnHandshakeDone` if TLS handshake is already completed. + if (mTlsVersionUsed != TLS_VERSION_UNKNOWN) { + nsCOMPtr<nsITLSServerSocket> serverSocket; + GetServerSocket(getter_AddRefs(serverSocket)); + mSecurityObserver->OnHandshakeDone(serverSocket, this); + mSecurityObserver = nullptr; + } + } + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetServerSocket(nsITLSServerSocket** aSocket) { + if (NS_WARN_IF(!aSocket)) { + return NS_ERROR_INVALID_POINTER; + } + *aSocket = do_AddRef(mServerSocket).take(); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetStatus(nsITLSClientStatus** aStatus) { + if (NS_WARN_IF(!aStatus)) { + return NS_ERROR_INVALID_POINTER; + } + *aStatus = do_AddRef(this).take(); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetPeerCert(nsIX509Cert** aCert) { + if (NS_WARN_IF(!aCert)) { + return NS_ERROR_INVALID_POINTER; + } + *aCert = do_AddRef(mPeerCert).take(); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetTlsVersionUsed(int16_t* aTlsVersionUsed) { + if (NS_WARN_IF(!aTlsVersionUsed)) { + return NS_ERROR_INVALID_POINTER; + } + *aTlsVersionUsed = mTlsVersionUsed; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetCipherName(nsACString& aCipherName) { + aCipherName.Assign(mCipherName); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetKeyLength(uint32_t* aKeyLength) { + if (NS_WARN_IF(!aKeyLength)) { + return NS_ERROR_INVALID_POINTER; + } + *aKeyLength = mKeyLength; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetMacLength(uint32_t* aMacLength) { + if (NS_WARN_IF(!aMacLength)) { + return NS_ERROR_INVALID_POINTER; + } + *aMacLength = mMacLength; + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetInterface(const nsIID& aIID, void** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + if (aIID.Equals(NS_GET_IID(nsITLSServerConnectionInfo))) { + *aResult = static_cast<nsITLSServerConnectionInfo*>(this); + NS_ADDREF_THIS(); + return NS_OK; + } + + return NS_NOINTERFACE; +} + +// static +void TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD, void* aArg) { + RefPtr<TLSServerConnectionInfo> info = + static_cast<TLSServerConnectionInfo*>(aArg); + nsISocketTransport* transport = info->mTransport; + // No longer needed outside this function, so clear the weak ref + info->mTransport = nullptr; + nsresult rv = info->HandshakeCallback(aFD); + if (NS_WARN_IF(NS_FAILED(rv))) { + transport->Close(rv); + } +} + +nsresult TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD) { + nsresult rv; + + UniqueCERTCertificate clientCert(SSL_PeerCertificate(aFD)); + if (clientCert) { + nsCOMPtr<nsIX509CertDB> certDB = + do_GetService(NS_X509CERTDB_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIX509Cert> clientCertPSM; + nsTArray<uint8_t> clientCertBytes; + clientCertBytes.AppendElements(clientCert->derCert.data, + clientCert->derCert.len); + rv = certDB->ConstructX509(clientCertBytes, getter_AddRefs(clientCertPSM)); + if (NS_FAILED(rv)) { + return rv; + } + + mPeerCert = clientCertPSM; + } + + SSLChannelInfo channelInfo; + rv = MapSECStatus(SSL_GetChannelInfo(aFD, &channelInfo, sizeof(channelInfo))); + if (NS_FAILED(rv)) { + return rv; + } + + SSLCipherSuiteInfo cipherInfo; + rv = MapSECStatus(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo, + sizeof(cipherInfo))); + if (NS_FAILED(rv)) { + return rv; + } + mCipherName.Assign(cipherInfo.cipherSuiteName); + mKeyLength = cipherInfo.effectiveKeyBits; + mMacLength = cipherInfo.macBits; + + // Notify consumer code that handshake is complete + nsCOMPtr<nsITLSServerSecurityObserver> observer; + { + MutexAutoLock lock(mLock); + mTlsVersionUsed = channelInfo.protocolVersion; + if (!mSecurityObserver) { + return NS_OK; + } + mSecurityObserver.swap(observer); + } + nsCOMPtr<nsITLSServerSocket> serverSocket; + GetServerSocket(getter_AddRefs(serverSocket)); + observer->OnHandshakeDone(serverSocket, this); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/TLSServerSocket.h b/netwerk/base/TLSServerSocket.h new file mode 100644 index 0000000000..c8f4380d46 --- /dev/null +++ b/netwerk/base/TLSServerSocket.h @@ -0,0 +1,81 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_TLSServerSocket_h +#define mozilla_net_TLSServerSocket_h + +#include "nsIInterfaceRequestor.h" +#include "nsITLSServerSocket.h" +#include "nsServerSocket.h" +#include "nsString.h" +#include "mozilla/Mutex.h" +#include "seccomon.h" + +namespace mozilla { +namespace net { + +class TLSServerSocket final : public nsServerSocket, public nsITLSServerSocket { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSISERVERSOCKET(nsServerSocket::) + NS_DECL_NSITLSSERVERSOCKET + + // Override methods from nsServerSocket + virtual void CreateClientTransport(PRFileDesc* clientFD, + const NetAddr& clientAddr) override; + virtual nsresult SetSocketDefaults() override; + virtual nsresult OnSocketListen() override; + + TLSServerSocket() = default; + + private: + virtual ~TLSServerSocket() = default; + + static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, + PRBool checksig, PRBool isServer); + + nsCOMPtr<nsIX509Cert> mServerCert; +}; + +class TLSServerConnectionInfo : public nsITLSServerConnectionInfo, + public nsITLSClientStatus, + public nsIInterfaceRequestor { + friend class TLSServerSocket; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITLSSERVERCONNECTIONINFO + NS_DECL_NSITLSCLIENTSTATUS + NS_DECL_NSIINTERFACEREQUESTOR + + TLSServerConnectionInfo() = default; + + private: + virtual ~TLSServerConnectionInfo(); + + static void HandshakeCallback(PRFileDesc* aFD, void* aArg); + nsresult HandshakeCallback(PRFileDesc* aFD); + + RefPtr<TLSServerSocket> mServerSocket; + // Weak ref to the transport, to avoid cycles since the transport holds a + // reference to the TLSServerConnectionInfo object. This is not handed out to + // anyone, and is only used in HandshakeCallback to close the transport in + // case of an error. After this, it's set to nullptr. + nsISocketTransport* mTransport{nullptr}; + nsCOMPtr<nsIX509Cert> mPeerCert; + int16_t mTlsVersionUsed{TLS_VERSION_UNKNOWN}; + nsCString mCipherName; + uint32_t mKeyLength{0}; + uint32_t mMacLength{0}; + // lock protects access to mSecurityObserver + mozilla::Mutex mLock{"TLSServerConnectionInfo.mLock"}; + nsCOMPtr<nsITLSServerSecurityObserver> mSecurityObserver + MOZ_GUARDED_BY(mLock); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_TLSServerSocket_h diff --git a/netwerk/base/TRRLoadInfo.cpp b/netwerk/base/TRRLoadInfo.cpp new file mode 100644 index 0000000000..920e7623a7 --- /dev/null +++ b/netwerk/base/TRRLoadInfo.cpp @@ -0,0 +1,865 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TRRLoadInfo.h" +#include "mozilla/dom/ClientSource.h" +#include "nsContentUtils.h" +#include "nsIRedirectHistoryEntry.h" + +using namespace mozilla::dom; + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(TRRLoadInfo, nsILoadInfo) + +TRRLoadInfo::TRRLoadInfo(nsIURI* aResultPrincipalURI, + nsContentPolicyType aContentPolicyType) + : mResultPrincipalURI(aResultPrincipalURI), + mInternalContentPolicyType(aContentPolicyType) {} + +already_AddRefed<nsILoadInfo> TRRLoadInfo::Clone() const { + nsCOMPtr<nsILoadInfo> loadInfo = + new TRRLoadInfo(mResultPrincipalURI, mInternalContentPolicyType); + + return loadInfo.forget(); +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsIPrincipal* TRRLoadInfo::VirtualGetLoadingPrincipal() { return nullptr; } + +NS_IMETHODIMP +TRRLoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsIPrincipal* TRRLoadInfo::TriggeringPrincipal() { return nullptr; } + +NS_IMETHODIMP +TRRLoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsIPrincipal* TRRLoadInfo::PrincipalToInherit() { return nullptr; } + +nsIPrincipal* TRRLoadInfo::FindPrincipalToInherit(nsIChannel* aChannel) { + return nullptr; +} + +const nsID& TRRLoadInfo::GetSandboxedNullPrincipalID() { + return mSandboxedNullPrincipalID; +} + +void TRRLoadInfo::ResetSandboxedNullPrincipalID() {} + +nsIPrincipal* TRRLoadInfo::GetTopLevelPrincipal() { return nullptr; } + +NS_IMETHODIMP +TRRLoadInfo::GetTriggeringRemoteType(nsACString& aTriggeringRemoteType) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetTriggeringRemoteType(const nsACString& aTriggeringRemoteType) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadingDocument(Document** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsINode* TRRLoadInfo::LoadingNode() { return nullptr; } + +already_AddRefed<nsISupports> TRRLoadInfo::ContextForTopLevelLoad() { + return nullptr; +} + +already_AddRefed<nsISupports> TRRLoadInfo::GetLoadingContext() { + return nullptr; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadingContextXPCOM(nsISupports** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSecurityFlags(nsSecurityFlags* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSandboxFlags(uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +TRRLoadInfo::GetTriggeringSandboxFlags(uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +TRRLoadInfo::SetTriggeringSandboxFlags(uint32_t aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetTriggeringWindowId(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +TRRLoadInfo::SetTriggeringWindowId(uint64_t aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetTriggeringStorageAccess(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +TRRLoadInfo::SetTriggeringStorageAccess(bool aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSecurityMode(uint32_t* aFlags) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsInThirdPartyContext(bool aIsInThirdPartyContext) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsThirdPartyContextToTopWindow( + bool* aIsThirdPartyContextToTopWindow) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsThirdPartyContextToTopWindow( + bool aIsThirdPartyContextToTopWindow) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetCookiePolicy(uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetCookieJarSettings(nsICookieJarSettings** aCookieJarSettings) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetCookieJarSettings(nsICookieJarSettings* aCookieJarSettings) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetStoragePermission( + nsILoadInfo::StoragePermissionState* aHasStoragePermission) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetStoragePermission( + nsILoadInfo::StoragePermissionState aHasStoragePermission) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +const Maybe<RFPTarget>& TRRLoadInfo::GetOverriddenFingerprintingSettings() { + return mOverriddenFingerprintingSettings; +} + +void TRRLoadInfo::SetOverriddenFingerprintingSettings(RFPTarget aTargets) {} + +NS_IMETHODIMP +TRRLoadInfo::GetIsMetaRefresh(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsMetaRefresh(bool aResult) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +TRRLoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetAboutBlankInherits(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetAllowChrome(bool* aResult) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +TRRLoadInfo::GetDisallowScript(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetDontFollowRedirects(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadErrorPage(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsFormSubmission(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsFormSubmission(bool aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSendCSPViolationEvents(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetSendCSPViolationEvents(bool aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult) { + // We have to use nsContentPolicyType because ExtContentPolicyType is not + // visible from xpidl. + *aResult = static_cast<nsContentPolicyType>( + nsContentUtils::InternalContentPolicyTypeToExternal( + mInternalContentPolicyType)); + return NS_OK; +} + +nsContentPolicyType TRRLoadInfo::InternalContentPolicyType() { + return mInternalContentPolicyType; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBlockAllMixedContent(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetUpgradeInsecureRequests(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBrowserUpgradeInsecureRequests(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBrowserDidUpgradeInsecureRequests(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetBrowserDidUpgradeInsecureRequests( + bool aBrowserDidUpgradeInsecureRequests) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBrowserWouldUpgradeInsecureRequests(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetForceAllowDataURI(bool* aForceAllowDataURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetAllowInsecureRedirectToDataURI( + bool aAllowInsecureRedirectToDataURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetAllowInsecureRedirectToDataURI( + bool* aAllowInsecureRedirectToDataURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetSkipContentPolicyCheckForWebRequest(bool aSkip) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSkipContentPolicyCheckForWebRequest(bool* aSkip) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetOriginalFrameSrcLoad(bool aOriginalFrameSrcLoad) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetOriginalFrameSrcLoad(bool* aOriginalFrameSrcLoad) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetForceInheritPrincipalDropped(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetInnerWindowID(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBrowsingContextID(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetWorkerAssociatedBrowsingContextID(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetWorkerAssociatedBrowsingContextID(uint64_t aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetFrameBrowsingContextID(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetTargetBrowsingContextID(uint64_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetBrowsingContext(dom::BrowsingContext** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetWorkerAssociatedBrowsingContext( + dom::BrowsingContext** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetFrameBrowsingContext(dom::BrowsingContext** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetTargetBrowsingContext(dom::BrowsingContext** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetScriptableOriginAttributes( + JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::ResetPrincipalToInheritToNullPrincipal() { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetScriptableOriginAttributes( + JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult TRRLoadInfo::GetOriginAttributes( + mozilla::OriginAttributes* aOriginAttributes) { + NS_ENSURE_ARG(aOriginAttributes); + *aOriginAttributes = mOriginAttributes; + return NS_OK; +} + +nsresult TRRLoadInfo::SetOriginAttributes( + const mozilla::OriginAttributes& aOriginAttributes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetInitialSecurityCheckDone(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::AppendRedirectHistoryEntry(nsIChannel* aChannelToDeriveFrom, + bool aIsInternalRedirect) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetRedirectChainIncludingInternalRedirects( + JSContext* aCx, JS::MutableHandle<JS::Value> aChain) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& +TRRLoadInfo::RedirectChainIncludingInternalRedirects() { + return mEmptyRedirectChain; +} + +NS_IMETHODIMP +TRRLoadInfo::GetRedirectChain(JSContext* aCx, + JS::MutableHandle<JS::Value> aChain) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& +TRRLoadInfo::RedirectChain() { + return mEmptyRedirectChain; +} + +const nsTArray<nsCOMPtr<nsIPrincipal>>& TRRLoadInfo::AncestorPrincipals() { + return mEmptyPrincipals; +} + +const nsTArray<uint64_t>& TRRLoadInfo::AncestorBrowsingContextIDs() { + return mEmptyBrowsingContextIDs; +} + +void TRRLoadInfo::SetCorsPreflightInfo(const nsTArray<nsCString>& aHeaders, + bool aForcePreflight) {} + +const nsTArray<nsCString>& TRRLoadInfo::CorsUnsafeHeaders() { + return mCorsUnsafeHeaders; +} + +NS_IMETHODIMP +TRRLoadInfo::GetForcePreflight(bool* aForcePreflight) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsPreflight(bool* aIsPreflight) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetLoadTriggeredFromExternal(bool aLoadTriggeredFromExternal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadTriggeredFromExternal(bool* aLoadTriggeredFromExternal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetServiceWorkerTaintingSynthesized( + bool* aServiceWorkerTaintingSynthesized) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetTainting(uint32_t* aTaintingOut) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::MaybeIncreaseTainting(uint32_t aTainting) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void TRRLoadInfo::SynthesizeServiceWorkerTainting(LoadTainting aTainting) {} + +NS_IMETHODIMP +TRRLoadInfo::GetDocumentHasUserInteracted(bool* aDocumentHasUserInteracted) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetDocumentHasUserInteracted(bool aDocumentHasUserInteracted) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetAllowListFutureDocumentsCreatedFromThisRedirectChain( + bool* aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetAllowListFutureDocumentsCreatedFromThisRedirectChain( + bool aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetNeedForCheckingAntiTrackingHeuristic(bool* aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetNeedForCheckingAntiTrackingHeuristic(bool aValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetCspNonce(nsAString& aCspNonce) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetCspNonce(const nsAString& aCspNonce) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIntegrityMetadata(nsAString& aIntegrityMetadata) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsTopLevelLoad(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsFromProcessingFrameAttributes( + bool* aIsFromProcessingFrameAttributes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsMediaRequest(bool aIsMediaRequest) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsMediaRequest(bool* aIsMediaRequest) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsMediaInitialRequest(bool aIsMediaInitialRequest) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsMediaInitialRequest(bool* aIsMediaInitialRequest) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsFromObjectOrEmbed(bool aIsFromObjectOrEmbed) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsFromObjectOrEmbed(bool* aIsFromObjectOrEmbed) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetShouldSkipCheckForBrokenURLOrZeroSized( + bool* aShouldSkipCheckForBrokenURLOrZeroSized) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetResultPrincipalURI(nsIURI** aURI) { + nsCOMPtr<nsIURI> uri = mResultPrincipalURI; + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +TRRLoadInfo::SetResultPrincipalURI(nsIURI* aURI) { + mResultPrincipalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +TRRLoadInfo::GetChannelCreationOriginalURI(nsIURI** aURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetChannelCreationOriginalURI(nsIURI* aURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetRequestBlockingReason(uint32_t aReason) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +TRRLoadInfo::GetRequestBlockingReason(uint32_t* aReason) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void TRRLoadInfo::SetClientInfo(const ClientInfo& aClientInfo) {} + +const Maybe<ClientInfo>& TRRLoadInfo::GetClientInfo() { return mClientInfo; } + +void TRRLoadInfo::GiveReservedClientSource( + UniquePtr<ClientSource>&& aClientSource) {} + +UniquePtr<ClientSource> TRRLoadInfo::TakeReservedClientSource() { + return nullptr; +} + +void TRRLoadInfo::SetReservedClientInfo(const ClientInfo& aClientInfo) {} + +void TRRLoadInfo::OverrideReservedClientInfoInParent( + const ClientInfo& aClientInfo) {} + +const Maybe<ClientInfo>& TRRLoadInfo::GetReservedClientInfo() { + return mReservedClientInfo; +} + +void TRRLoadInfo::SetInitialClientInfo(const ClientInfo& aClientInfo) {} + +const Maybe<ClientInfo>& TRRLoadInfo::GetInitialClientInfo() { + return mInitialClientInfo; +} + +void TRRLoadInfo::SetController(const ServiceWorkerDescriptor& aServiceWorker) { +} + +void TRRLoadInfo::ClearController() {} + +const Maybe<ServiceWorkerDescriptor>& TRRLoadInfo::GetController() { + return mController; +} + +void TRRLoadInfo::SetPerformanceStorage( + PerformanceStorage* aPerformanceStorage) {} + +PerformanceStorage* TRRLoadInfo::GetPerformanceStorage() { return nullptr; } + +NS_IMETHODIMP +TRRLoadInfo::GetCspEventListener(nsICSPEventListener** aCSPEventListener) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +already_AddRefed<nsIContentSecurityPolicy> TRRLoadInfo::GetCsp() { + return nullptr; +} + +already_AddRefed<nsIContentSecurityPolicy> TRRLoadInfo::GetPreloadCsp() { + return nullptr; +} + +already_AddRefed<nsIContentSecurityPolicy> TRRLoadInfo::GetCspToInherit() { + return nullptr; +} + +NS_IMETHODIMP +TRRLoadInfo::GetHttpsOnlyStatus(uint32_t* aHttpsOnlyStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetHttpsOnlyStatus(uint32_t aHttpsOnlyStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetHstsStatus(bool* aHstsStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetHstsStatus(bool aHstsStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetHasValidUserGestureActivation( + bool* aHasValidUserGestureActivation) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetHasValidUserGestureActivation( + bool aHasValidUserGestureActivation) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetInternalContentPolicyType(nsContentPolicyType* aResult) { + *aResult = mInternalContentPolicyType; + return NS_OK; +} + +NS_IMETHODIMP +TRRLoadInfo::GetAllowDeprecatedSystemRequests( + bool* aAllowDeprecatedSystemRequests) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetAllowDeprecatedSystemRequests( + bool aAllowDeprecatedSystemRequests) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsUserTriggeredSave(bool* aIsUserTriggeredSave) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsUserTriggeredSave(bool aIsUserTriggeredSave) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsInDevToolsContext(bool* aIsInDevToolsContext) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsInDevToolsContext(bool aIsInDevToolsContext) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetParserCreatedScript(bool* aParserCreatedScript) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetParserCreatedScript(bool aParserCreatedScript) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetLoadingEmbedderPolicy( + nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetLoadingEmbedderPolicy( + nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool* aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetIsOriginTrialCoepCredentiallessEnabledForTopLevel( + bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetUnstrippedURI(nsIURI** aURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetUnstrippedURI(nsIURI* aURI) { return NS_ERROR_NOT_IMPLEMENTED; } + +nsIInterceptionInfo* TRRLoadInfo::InterceptionInfo() { return nullptr; } +void TRRLoadInfo::SetInterceptionInfo(nsIInterceptionInfo* aPrincipla) {} + +NS_IMETHODIMP +TRRLoadInfo::GetHasInjectedCookieForCookieBannerHandling( + bool* aHasInjectedCookieForCookieBannerHandling) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetHasInjectedCookieForCookieBannerHandling( + bool aHasInjectedCookieForCookieBannerHandling) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::GetWasSchemelessInput(bool* aWasSchemelessInput) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +TRRLoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/TRRLoadInfo.h b/netwerk/base/TRRLoadInfo.h new file mode 100644 index 0000000000..2e08abe100 --- /dev/null +++ b/netwerk/base/TRRLoadInfo.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TRRLoadInfo_h +#define mozilla_TRRLoadInfo_h + +#include "nsILoadInfo.h" +#include "nsIURI.h" +#include "nsTArray.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ServiceWorkerDescriptor.h" +#include "mozilla/OriginAttributes.h" + +namespace mozilla { +namespace net { + +// TRRLoadInfo is designed to be used by TRRServiceChannel only. Most of +// nsILoadInfo functions are not implemented since TRRLoadInfo needs to +// support off main thread. +class TRRLoadInfo final : public nsILoadInfo { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSILOADINFO + + TRRLoadInfo(nsIURI* aResultPrincipalURI, + nsContentPolicyType aContentPolicyType); + + already_AddRefed<nsILoadInfo> Clone() const; + + private: + virtual ~TRRLoadInfo() = default; + + nsCOMPtr<nsIURI> mResultPrincipalURI; + nsContentPolicyType mInternalContentPolicyType; + OriginAttributes mOriginAttributes; + nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>> mEmptyRedirectChain; + nsTArray<nsCOMPtr<nsIPrincipal>> mEmptyPrincipals; + nsTArray<uint64_t> mEmptyBrowsingContextIDs; + nsTArray<nsCString> mCorsUnsafeHeaders; + nsID mSandboxedNullPrincipalID; + Maybe<mozilla::dom::ClientInfo> mClientInfo; + Maybe<mozilla::dom::ClientInfo> mReservedClientInfo; + Maybe<mozilla::dom::ClientInfo> mInitialClientInfo; + Maybe<mozilla::dom::ServiceWorkerDescriptor> mController; + Maybe<RFPTarget> mOverriddenFingerprintingSettings; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_TRRLoadInfo_h diff --git a/netwerk/base/ThrottleQueue.cpp b/netwerk/base/ThrottleQueue.cpp new file mode 100644 index 0000000000..4313a6ecb3 --- /dev/null +++ b/netwerk/base/ThrottleQueue.cpp @@ -0,0 +1,410 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ThrottleQueue.h" +#include "mozilla/net/InputChannelThrottleQueueParent.h" +#include "nsISeekableStream.h" +#include "nsIAsyncInputStream.h" +#include "nsIOService.h" +#include "nsSocketTransportService2.h" +#include "nsStreamUtils.h" +#include "nsNetUtil.h" + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- + +class ThrottleInputStream final : public nsIAsyncInputStream, + public nsISeekableStream { + public: + ThrottleInputStream(nsIInputStream* aStream, ThrottleQueue* aQueue); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + void AllowInput(); + + private: + ~ThrottleInputStream(); + + nsCOMPtr<nsIInputStream> mStream; + RefPtr<ThrottleQueue> mQueue; + nsresult mClosedStatus; + + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mEventTarget; +}; + +NS_IMPL_ISUPPORTS(ThrottleInputStream, nsIAsyncInputStream, nsIInputStream, + nsITellableStream, nsISeekableStream) + +ThrottleInputStream::ThrottleInputStream(nsIInputStream* aStream, + ThrottleQueue* aQueue) + : mStream(aStream), mQueue(aQueue), mClosedStatus(NS_OK) { + MOZ_ASSERT(aQueue != nullptr); +} + +ThrottleInputStream::~ThrottleInputStream() { Close(); } + +NS_IMETHODIMP +ThrottleInputStream::Close() { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + if (mQueue) { + mQueue->DequeueStream(this); + mQueue = nullptr; + mClosedStatus = NS_BASE_STREAM_CLOSED; + } + return mStream->Close(); +} + +NS_IMETHODIMP +ThrottleInputStream::Available(uint64_t* aResult) { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + return mStream->Available(aResult); +} + +NS_IMETHODIMP +ThrottleInputStream::StreamStatus() { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + return mStream->StreamStatus(); +} + +NS_IMETHODIMP +ThrottleInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + uint32_t realCount; + nsresult rv = mQueue->Available(aCount, &realCount); + if (NS_FAILED(rv)) { + return rv; + } + + if (realCount == 0) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + rv = mStream->Read(aBuf, realCount, aResult); + if (NS_SUCCEEDED(rv) && *aResult > 0) { + mQueue->RecordRead(*aResult); + } + return rv; +} + +NS_IMETHODIMP +ThrottleInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + uint32_t realCount; + nsresult rv = mQueue->Available(aCount, &realCount); + if (NS_FAILED(rv)) { + return rv; + } + MOZ_ASSERT(realCount <= aCount); + + if (realCount == 0) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + rv = mStream->ReadSegments(aWriter, aClosure, realCount, aResult); + if (NS_SUCCEEDED(rv) && *aResult > 0) { + mQueue->RecordRead(*aResult); + } + return rv; +} + +NS_IMETHODIMP +ThrottleInputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = true; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleInputStream::Seek(int32_t aWhence, int64_t aOffset) { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream); + if (!sstream) { + return NS_ERROR_FAILURE; + } + + return sstream->Seek(aWhence, aOffset); +} + +NS_IMETHODIMP +ThrottleInputStream::Tell(int64_t* aResult) { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + nsCOMPtr<nsITellableStream> sstream = do_QueryInterface(mStream); + if (!sstream) { + return NS_ERROR_FAILURE; + } + + return sstream->Tell(aResult); +} + +NS_IMETHODIMP +ThrottleInputStream::SetEOF() { + if (NS_FAILED(mClosedStatus)) { + return mClosedStatus; + } + + nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream); + if (!sstream) { + return NS_ERROR_FAILURE; + } + + return sstream->SetEOF(); +} + +NS_IMETHODIMP +ThrottleInputStream::CloseWithStatus(nsresult aStatus) { + if (NS_FAILED(mClosedStatus)) { + // Already closed, ignore. + return NS_OK; + } + if (NS_SUCCEEDED(aStatus)) { + aStatus = NS_BASE_STREAM_CLOSED; + } + + mClosedStatus = Close(); + if (NS_SUCCEEDED(mClosedStatus)) { + mClosedStatus = aStatus; + } + return NS_OK; +} + +NS_IMETHODIMP +ThrottleInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + if (aFlags != 0) { + return NS_ERROR_ILLEGAL_VALUE; + } + + mCallback = aCallback; + mEventTarget = aEventTarget; + if (mCallback) { + mQueue->QueueStream(this); + } else { + mQueue->DequeueStream(this); + } + return NS_OK; +} + +void ThrottleInputStream::AllowInput() { + MOZ_ASSERT(mCallback); + nsCOMPtr<nsIInputStreamCallback> callbackEvent = NS_NewInputStreamReadyEvent( + "ThrottleInputStream::AllowInput", mCallback, mEventTarget); + mCallback = nullptr; + mEventTarget = nullptr; + callbackEvent->OnInputStreamReady(this); +} + +//----------------------------------------------------------------------------- + +// static +already_AddRefed<nsIInputChannelThrottleQueue> ThrottleQueue::Create() { + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr<nsIInputChannelThrottleQueue> tq; + if (nsIOService::UseSocketProcess()) { + tq = new InputChannelThrottleQueueParent(); + } else { + tq = new ThrottleQueue(); + } + + return tq.forget(); +} + +NS_IMPL_ISUPPORTS(ThrottleQueue, nsIInputChannelThrottleQueue, nsITimerCallback, + nsINamed) + +ThrottleQueue::ThrottleQueue() + +{ + nsresult rv; + nsCOMPtr<nsIEventTarget> sts; + nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); + if (NS_SUCCEEDED(rv)) { + sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + } + if (NS_SUCCEEDED(rv)) mTimer = NS_NewTimer(sts); +} + +ThrottleQueue::~ThrottleQueue() { + if (mTimer && mTimerArmed) { + mTimer->Cancel(); + } + mTimer = nullptr; +} + +NS_IMETHODIMP +ThrottleQueue::RecordRead(uint32_t aBytesRead) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + ThrottleEntry entry; + entry.mTime = TimeStamp::Now(); + entry.mBytesRead = aBytesRead; + mReadEvents.AppendElement(entry); + mBytesProcessed += aBytesRead; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::Available(uint32_t aRemaining, uint32_t* aAvailable) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + TimeStamp now = TimeStamp::Now(); + TimeStamp oneSecondAgo = now - TimeDuration::FromSeconds(1); + size_t i; + + // Remove all stale events. + for (i = 0; i < mReadEvents.Length(); ++i) { + if (mReadEvents[i].mTime >= oneSecondAgo) { + break; + } + } + mReadEvents.RemoveElementsAt(0, i); + + uint32_t totalBytes = 0; + for (i = 0; i < mReadEvents.Length(); ++i) { + totalBytes += mReadEvents[i].mBytesRead; + } + + uint32_t spread = mMaxBytesPerSecond - mMeanBytesPerSecond; + double prob = static_cast<double>(rand()) / RAND_MAX; + uint32_t thisSliceBytes = + mMeanBytesPerSecond - spread + static_cast<uint32_t>(2 * spread * prob); + + if (totalBytes >= thisSliceBytes) { + *aAvailable = 0; + } else { + *aAvailable = std::min(thisSliceBytes, aRemaining); + } + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::Init(uint32_t aMeanBytesPerSecond, uint32_t aMaxBytesPerSecond) { + // Can be called on any thread. + if (aMeanBytesPerSecond == 0 || aMaxBytesPerSecond == 0 || + aMaxBytesPerSecond < aMeanBytesPerSecond) { + return NS_ERROR_ILLEGAL_VALUE; + } + + mMeanBytesPerSecond = aMeanBytesPerSecond; + mMaxBytesPerSecond = aMaxBytesPerSecond; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::BytesProcessed(uint64_t* aResult) { + *aResult = mBytesProcessed; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::WrapStream(nsIInputStream* aInputStream, + nsIAsyncInputStream** aResult) { + nsCOMPtr<nsIAsyncInputStream> result = + new ThrottleInputStream(aInputStream, this); + result.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::Notify(nsITimer* aTimer) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + // A notified reader may need to push itself back on the queue. + // Swap out the list of readers so that this works properly. + nsTArray<RefPtr<ThrottleInputStream>> events = std::move(mAsyncEvents); + + // Optimistically notify all the waiting readers, and then let them + // requeue if there isn't enough bandwidth. + for (size_t i = 0; i < events.Length(); ++i) { + events[i]->AllowInput(); + } + + mTimerArmed = false; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::GetName(nsACString& aName) { + aName.AssignLiteral("net::ThrottleQueue"); + return NS_OK; +} + +void ThrottleQueue::QueueStream(ThrottleInputStream* aStream) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mAsyncEvents.IndexOf(aStream) == + nsTArray<RefPtr<mozilla::net::ThrottleInputStream>>::NoIndex) { + mAsyncEvents.AppendElement(aStream); + + if (!mTimerArmed) { + uint32_t ms = 1000; + if (mReadEvents.Length() > 0) { + TimeStamp t = mReadEvents[0].mTime + TimeDuration::FromSeconds(1); + TimeStamp now = TimeStamp::Now(); + + if (t > now) { + ms = static_cast<uint32_t>((t - now).ToMilliseconds()); + } else { + ms = 1; + } + } + + if (NS_SUCCEEDED( + mTimer->InitWithCallback(this, ms, nsITimer::TYPE_ONE_SHOT))) { + mTimerArmed = true; + } + } + } +} + +void ThrottleQueue::DequeueStream(ThrottleInputStream* aStream) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + mAsyncEvents.RemoveElement(aStream); +} + +NS_IMETHODIMP +ThrottleQueue::GetMeanBytesPerSecond(uint32_t* aMeanBytesPerSecond) { + NS_ENSURE_ARG(aMeanBytesPerSecond); + + *aMeanBytesPerSecond = mMeanBytesPerSecond; + return NS_OK; +} + +NS_IMETHODIMP +ThrottleQueue::GetMaxBytesPerSecond(uint32_t* aMaxBytesPerSecond) { + NS_ENSURE_ARG(aMaxBytesPerSecond); + + *aMaxBytesPerSecond = mMaxBytesPerSecond; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/ThrottleQueue.h b/netwerk/base/ThrottleQueue.h new file mode 100644 index 0000000000..4d06af8e64 --- /dev/null +++ b/netwerk/base/ThrottleQueue.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_ThrottleQueue_h +#define mozilla_net_ThrottleQueue_h + +#include "mozilla/TimeStamp.h" +#include "nsINamed.h" +#include "nsIThrottledInputChannel.h" +#include "nsITimer.h" +#include "nsTArray.h" + +namespace mozilla { +namespace net { + +class ThrottleInputStream; + +/** + * An implementation of nsIInputChannelThrottleQueue that can be used + * to throttle uploads. This class is not thread-safe. + * Initialization and calls to WrapStream may be done on any thread; + * but otherwise, after creation, it can only be used on the socket + * thread. It currently throttles with a one second granularity, so + * may be a bit choppy. + */ + +class ThrottleQueue : public nsIInputChannelThrottleQueue, + public nsITimerCallback, + public nsINamed { + public: + static already_AddRefed<nsIInputChannelThrottleQueue> Create(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + void QueueStream(ThrottleInputStream* aStream); + void DequeueStream(ThrottleInputStream* aStream); + + protected: + ThrottleQueue(); + virtual ~ThrottleQueue(); + + struct ThrottleEntry { + TimeStamp mTime; + uint32_t mBytesRead = 0; + }; + + nsTArray<ThrottleEntry> mReadEvents; + uint32_t mMeanBytesPerSecond{0}; + uint32_t mMaxBytesPerSecond{0}; + uint64_t mBytesProcessed{0}; + + nsTArray<RefPtr<ThrottleInputStream>> mAsyncEvents; + nsCOMPtr<nsITimer> mTimer; + bool mTimerArmed{false}; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ThrottleQueue_h diff --git a/netwerk/base/Tickler.cpp b/netwerk/base/Tickler.cpp new file mode 100644 index 0000000000..3030b24653 --- /dev/null +++ b/netwerk/base/Tickler.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Tickler.h" + +#ifdef MOZ_USE_WIFI_TICKLER +# include "nsComponentManagerUtils.h" +# include "nsINamed.h" +# include "nsServiceManagerUtils.h" +# include "nsThreadUtils.h" +# include "prnetdb.h" + +# include "mozilla/java/GeckoAppShellWrappers.h" +# include "mozilla/jni/Utils.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(Tickler, nsISupportsWeakReference, Tickler) + +Tickler::Tickler() + : mLock("Tickler::mLock"), + mActive(false), + mCanceled(false), + mEnabled(false), + mDelay(16), + mDuration(TimeDuration::FromMilliseconds(400)), + mFD(nullptr) { + MOZ_ASSERT(NS_IsMainThread()); +} + +Tickler::~Tickler() { + // non main thread uses of the tickler should hold weak + // references to it if they must hold a reference at all + MOZ_ASSERT(NS_IsMainThread()); + + if (mThread) { + mThread->AsyncShutdown(); + mThread = nullptr; + } + + if (mTimer) mTimer->Cancel(); + if (mFD) PR_Close(mFD); +} + +nsresult Tickler::Init() { + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mTimer); + MOZ_ASSERT(!mActive); + MOZ_ASSERT(!mThread); + MOZ_ASSERT(!mFD); + + if (jni::IsAvailable()) { + java::GeckoAppShell::EnableNetworkNotifications(); + } + + mFD = PR_OpenUDPSocket(PR_AF_INET); + if (!mFD) return NS_ERROR_FAILURE; + + // make sure new socket has a ttl of 1 + // failure is not fatal. + PRSocketOptionData opt; + opt.option = PR_SockOpt_IpTimeToLive; + opt.value.ip_ttl = 1; + PR_SetSocketOption(mFD, &opt); + + nsresult rv = NS_NewNamedThread("wifi tickler", getter_AddRefs(mThread)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsITimer> tmpTimer = NS_NewTimer(mThread); + if (!tmpTimer) return NS_ERROR_OUT_OF_MEMORY; + + mTimer.swap(tmpTimer); + + mAddr.inet.family = PR_AF_INET; + mAddr.inet.port = PR_htons(4886); + mAddr.inet.ip = 0; + + return NS_OK; +} + +void Tickler::Tickle() { + MutexAutoLock lock(mLock); + MOZ_ASSERT(mThread); + mLastTickle = TimeStamp::Now(); + if (!mActive) MaybeStartTickler(); +} + +void Tickler::PostCheckTickler() { + mLock.AssertCurrentThreadOwns(); + mThread->Dispatch(NewRunnableMethod("net::Tickler::CheckTickler", this, + &Tickler::CheckTickler), + NS_DISPATCH_NORMAL); + return; +} + +void Tickler::MaybeStartTicklerUnlocked() { + MutexAutoLock lock(mLock); + MaybeStartTickler(); +} + +void Tickler::MaybeStartTickler() { + mLock.AssertCurrentThreadOwns(); + if (!NS_IsMainThread()) { + NS_DispatchToMainThread( + NewRunnableMethod("net::Tickler::MaybeStartTicklerUnlocked", this, + &Tickler::MaybeStartTicklerUnlocked)); + return; + } + + if (!mPrefs) mPrefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (mPrefs) { + int32_t val; + bool boolVal; + + if (NS_SUCCEEDED( + mPrefs->GetBoolPref("network.tickle-wifi.enabled", &boolVal))) + mEnabled = boolVal; + + if (NS_SUCCEEDED( + mPrefs->GetIntPref("network.tickle-wifi.duration", &val))) { + if (val < 1) val = 1; + if (val > 100000) val = 100000; + mDuration = TimeDuration::FromMilliseconds(val); + } + + if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.delay", &val))) { + if (val < 1) val = 1; + if (val > 1000) val = 1000; + mDelay = static_cast<uint32_t>(val); + } + } + + PostCheckTickler(); +} + +void Tickler::CheckTickler() { + MutexAutoLock lock(mLock); + MOZ_ASSERT(mThread == NS_GetCurrentThread()); + + bool shouldRun = + (!mCanceled) && ((TimeStamp::Now() - mLastTickle) <= mDuration); + + if ((shouldRun && mActive) || (!shouldRun && !mActive)) + return; // no change in state + + if (mActive) + StopTickler(); + else + StartTickler(); +} + +void Tickler::Cancel() { + MutexAutoLock lock(mLock); + MOZ_ASSERT(NS_IsMainThread()); + mCanceled = true; + if (mThread) PostCheckTickler(); +} + +void Tickler::StopTickler() { + mLock.AssertCurrentThreadOwns(); + MOZ_ASSERT(mThread == NS_GetCurrentThread()); + MOZ_ASSERT(mTimer); + MOZ_ASSERT(mActive); + + mTimer->Cancel(); + mActive = false; +} + +class TicklerTimer final : public nsITimerCallback, public nsINamed { + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + explicit TicklerTimer(Tickler* aTickler) { + mTickler = do_GetWeakReference(aTickler); + } + + // nsINamed + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("TicklerTimer"); + return NS_OK; + } + + private: + ~TicklerTimer() {} + + nsWeakPtr mTickler; +}; + +void Tickler::StartTickler() { + mLock.AssertCurrentThreadOwns(); + MOZ_ASSERT(mThread == NS_GetCurrentThread()); + MOZ_ASSERT(!mActive); + MOZ_ASSERT(mTimer); + + if (NS_SUCCEEDED(mTimer->InitWithCallback(new TicklerTimer(this), + mEnabled ? mDelay : 1000, + nsITimer::TYPE_REPEATING_SLACK))) + mActive = true; +} + +// argument should be in network byte order +void Tickler::SetIPV4Address(uint32_t address) { mAddr.inet.ip = address; } + +// argument should be in network byte order +void Tickler::SetIPV4Port(uint16_t port) { mAddr.inet.port = port; } + +NS_IMPL_ISUPPORTS(TicklerTimer, nsITimerCallback, nsINamed) + +NS_IMETHODIMP TicklerTimer::Notify(nsITimer* timer) { + RefPtr<Tickler> tickler = do_QueryReferent(mTickler); + if (!tickler) return NS_ERROR_FAILURE; + MutexAutoLock lock(tickler->mLock); + + if (!tickler->mFD) { + tickler->StopTickler(); + return NS_ERROR_FAILURE; + } + + if (tickler->mCanceled || + ((TimeStamp::Now() - tickler->mLastTickle) > tickler->mDuration)) { + tickler->StopTickler(); + return NS_OK; + } + + if (!tickler->mEnabled) return NS_OK; + + PR_SendTo(tickler->mFD, "", 0, 0, &tickler->mAddr, 0); + return NS_OK; +} + +} // namespace net +} // namespace mozilla + +#else // not defined MOZ_USE_WIFI_TICKLER + +namespace mozilla { +namespace net { +NS_IMPL_ISUPPORTS0(Tickler) +} // namespace net +} // namespace mozilla + +#endif // defined MOZ_USE_WIFI_TICKLER diff --git a/netwerk/base/Tickler.h b/netwerk/base/Tickler.h new file mode 100644 index 0000000000..264ecefebb --- /dev/null +++ b/netwerk/base/Tickler.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_Tickler_h +#define mozilla_net_Tickler_h + +// The tickler sends a regular 0 byte UDP heartbeat out to a +// particular address for a short time after it has been touched. This +// is used on some mobile wifi chipsets to mitigate Power Save Polling +// (PSP) Mode when we are anticipating a response packet +// soon. Typically PSP adds 100ms of latency to a read event because +// the packet delivery is not triggered until the 802.11 beacon is +// delivered to the host (100ms is the standard Access Point +// configuration for the beacon interval.) Requesting a frequent +// transmission and getting a CTS frame from the AP at least that +// frequently allows for low latency receives when we have reason to +// expect them (e.g a SYN-ACK). +// +// The tickler is used to allow RTT based phases of web transport to +// complete quickly when on wifi - ARP, DNS, TCP handshake, SSL +// handshake, HTTP headers, and the TCP slow start phase. The +// transaction is given up to 400 miliseconds by default to get +// through those phases before the tickler is disabled. +// +// The tickler only applies to wifi on mobile right now. Hopefully it +// can also be restricted to particular handset models in the future. + +#if defined(ANDROID) && !defined(MOZ_PROXY_BYPASS_PROTECTION) +# define MOZ_USE_WIFI_TICKLER +#endif + +#include "mozilla/Attributes.h" +#include "nsISupports.h" +#include <stdint.h> + +#ifdef MOZ_USE_WIFI_TICKLER +# include "mozilla/Mutex.h" +# include "mozilla/TimeStamp.h" +# include "nsISupports.h" +# include "nsIThread.h" +# include "nsITimer.h" +# include "nsWeakReference.h" +# include "prio.h" + +class nsIPrefBranch; +#endif + +namespace mozilla { +namespace net { + +#ifdef MOZ_USE_WIFI_TICKLER + +// 8f769ed6-207c-4af9-9f7e-9e832da3754e +# define NS_TICKLER_IID \ + { \ + 0x8f769ed6, 0x207c, 0x4af9, { \ + 0x9f, 0x7e, 0x9e, 0x83, 0x2d, 0xa3, 0x75, 0x4e \ + } \ + } + +class Tickler final : public nsSupportsWeakReference { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECLARE_STATIC_IID_ACCESSOR(NS_TICKLER_IID) + + // These methods are main thread only + Tickler(); + void Cancel(); + nsresult Init(); + void SetIPV4Address(uint32_t address); + void SetIPV4Port(uint16_t port); + + // Tickle the tickler to (re-)start the activity. + // May call from any thread + void Tickle(); + + private: + ~Tickler(); + + friend class TicklerTimer; + Mutex mLock MOZ_UNANNOTATED; + nsCOMPtr<nsIThread> mThread; + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsIPrefBranch> mPrefs; + + bool mActive; + bool mCanceled; + bool mEnabled; + uint32_t mDelay; + TimeDuration mDuration; + PRFileDesc* mFD; + + TimeStamp mLastTickle; + PRNetAddr mAddr; + + // These functions may be called from any thread + void PostCheckTickler(); + void MaybeStartTickler(); + void MaybeStartTicklerUnlocked(); + + // Tickler thread only + void CheckTickler(); + void StartTickler(); + void StopTickler(); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(Tickler, NS_TICKLER_IID) + +#else // not defined MOZ_USE_WIFI_TICKLER + +class Tickler final : public nsISupports { + ~Tickler() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + Tickler() = default; + nsresult Init() { return NS_ERROR_NOT_IMPLEMENTED; } + void Cancel() {} + void SetIPV4Address(uint32_t){}; + void SetIPV4Port(uint16_t) {} + void Tickle() {} +}; + +#endif // defined MOZ_USE_WIFI_TICKLER + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_Tickler_h diff --git a/netwerk/base/ascii_pac_utils.js b/netwerk/base/ascii_pac_utils.js new file mode 100644 index 0000000000..cedb1a0f21 --- /dev/null +++ b/netwerk/base/ascii_pac_utils.js @@ -0,0 +1,256 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* global dnsResolve */ + +function dnsDomainIs(host, domain) { + return ( + host.length >= domain.length && + host.substring(host.length - domain.length) == domain + ); +} + +function dnsDomainLevels(host) { + return host.split(".").length - 1; +} + +function isValidIpAddress(ipchars) { + var matches = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(ipchars); + if (matches == null) { + return false; + } else if ( + matches[1] > 255 || + matches[2] > 255 || + matches[3] > 255 || + matches[4] > 255 + ) { + return false; + } + return true; +} + +function convert_addr(ipchars) { + var bytes = ipchars.split("."); + var result = + ((bytes[0] & 0xff) << 24) | + ((bytes[1] & 0xff) << 16) | + ((bytes[2] & 0xff) << 8) | + (bytes[3] & 0xff); + return result; +} + +function isInNet(ipaddr, pattern, maskstr) { + if (!isValidIpAddress(pattern) || !isValidIpAddress(maskstr)) { + return false; + } + if (!isValidIpAddress(ipaddr)) { + ipaddr = dnsResolve(ipaddr); + if (ipaddr == null) { + return false; + } + } + var host = convert_addr(ipaddr); + var pat = convert_addr(pattern); + var mask = convert_addr(maskstr); + return (host & mask) == (pat & mask); +} + +function isPlainHostName(host) { + return host.search("(\\.)|:") == -1; +} + +function isResolvable(host) { + var ip = dnsResolve(host); + return ip != null; +} + +function localHostOrDomainIs(host, hostdom) { + return host == hostdom || hostdom.lastIndexOf(host + ".", 0) == 0; +} + +function shExpMatch(url, pattern) { + pattern = pattern.replace(/\./g, "\\."); + pattern = pattern.replace(/\*/g, ".*"); + pattern = pattern.replace(/\?/g, "."); + var newRe = new RegExp("^" + pattern + "$"); + return newRe.test(url); +} + +var wdays = { SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6 }; +var months = { + JAN: 0, + FEB: 1, + MAR: 2, + APR: 3, + MAY: 4, + JUN: 5, + JUL: 6, + AUG: 7, + SEP: 8, + OCT: 9, + NOV: 10, + DEC: 11, +}; + +function weekdayRange() { + function getDay(weekday) { + if (weekday in wdays) { + return wdays[weekday]; + } + return -1; + } + var date = new Date(); + var argc = arguments.length; + var wday; + if (argc < 1) { + return false; + } + if (arguments[argc - 1] == "GMT") { + argc--; + wday = date.getUTCDay(); + } else { + wday = date.getDay(); + } + var wd1 = getDay(arguments[0]); + var wd2 = argc == 2 ? getDay(arguments[1]) : wd1; + if (wd1 == -1 || wd2 == -1) { + return false; + } + + if (wd1 <= wd2) { + return wd1 <= wday && wday <= wd2; + } + + return wd2 >= wday || wday >= wd1; +} + +function dateRange() { + function getMonth(name) { + if (name in months) { + return months[name]; + } + return -1; + } + var date = new Date(); + var argc = arguments.length; + if (argc < 1) { + return false; + } + var isGMT = arguments[argc - 1] == "GMT"; + + if (isGMT) { + argc--; + } + // function will work even without explict handling of this case + if (argc == 1) { + let tmp = parseInt(arguments[0]); + if (isNaN(tmp)) { + return ( + (isGMT ? date.getUTCMonth() : date.getMonth()) == getMonth(arguments[0]) + ); + } else if (tmp < 32) { + return (isGMT ? date.getUTCDate() : date.getDate()) == tmp; + } + return (isGMT ? date.getUTCFullYear() : date.getFullYear()) == tmp; + } + var year = date.getFullYear(); + var date1, date2; + date1 = new Date(year, 0, 1, 0, 0, 0); + date2 = new Date(year, 11, 31, 23, 59, 59); + var adjustMonth = false; + for (let i = 0; i < argc >> 1; i++) { + let tmp = parseInt(arguments[i]); + if (isNaN(tmp)) { + let mon = getMonth(arguments[i]); + date1.setMonth(mon); + } else if (tmp < 32) { + adjustMonth = argc <= 2; + date1.setDate(tmp); + } else { + date1.setFullYear(tmp); + } + } + for (let i = argc >> 1; i < argc; i++) { + let tmp = parseInt(arguments[i]); + if (isNaN(tmp)) { + let mon = getMonth(arguments[i]); + date2.setMonth(mon); + } else if (tmp < 32) { + date2.setDate(tmp); + } else { + date2.setFullYear(tmp); + } + } + if (adjustMonth) { + date1.setMonth(date.getMonth()); + date2.setMonth(date.getMonth()); + } + if (isGMT) { + let tmp = date; + tmp.setFullYear(date.getUTCFullYear()); + tmp.setMonth(date.getUTCMonth()); + tmp.setDate(date.getUTCDate()); + tmp.setHours(date.getUTCHours()); + tmp.setMinutes(date.getUTCMinutes()); + tmp.setSeconds(date.getUTCSeconds()); + date = tmp; + } + return date1 <= date2 + ? date1 <= date && date <= date2 + : date2 >= date || date >= date1; +} + +function timeRange() { + var argc = arguments.length; + var date = new Date(); + var isGMT = false; + if (argc < 1) { + return false; + } + if (arguments[argc - 1] == "GMT") { + isGMT = true; + argc--; + } + + var hour = isGMT ? date.getUTCHours() : date.getHours(); + var date1, date2; + date1 = new Date(); + date2 = new Date(); + + if (argc == 1) { + return hour == arguments[0]; + } else if (argc == 2) { + return arguments[0] <= hour && hour <= arguments[1]; + } + switch (argc) { + case 6: + date1.setSeconds(arguments[2]); + date2.setSeconds(arguments[5]); + // falls through + case 4: + var middle = argc >> 1; + date1.setHours(arguments[0]); + date1.setMinutes(arguments[1]); + date2.setHours(arguments[middle]); + date2.setMinutes(arguments[middle + 1]); + if (middle == 2) { + date2.setSeconds(59); + } + break; + default: + throw new Error("timeRange: bad number of arguments"); + } + + if (isGMT) { + date.setFullYear(date.getUTCFullYear()); + date.setMonth(date.getUTCMonth()); + date.setDate(date.getUTCDate()); + date.setHours(date.getUTCHours()); + date.setMinutes(date.getUTCMinutes()); + date.setSeconds(date.getUTCSeconds()); + } + return date1 <= date2 + ? date1 <= date && date <= date2 + : date2 >= date || date >= date1; +} diff --git a/netwerk/base/http-sfv/Cargo.toml b/netwerk/base/http-sfv/Cargo.toml new file mode 100644 index 0000000000..4625cdf974 --- /dev/null +++ b/netwerk/base/http-sfv/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "http_sfv" +version = "0.1.0" +authors = ["barabass <yalyna.ts@gmail.com>"] +edition = "2018" +license = "MPL-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nserror = { path = "../../../xpcom/rust/nserror" } +nsstring = { path = "../../../xpcom/rust/nsstring" } +sfv = "0.9.1" +xpcom = { path = "../../../xpcom/rust/xpcom" } +thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } diff --git a/netwerk/base/http-sfv/SFVService.cpp b/netwerk/base/http-sfv/SFVService.cpp new file mode 100644 index 0000000000..9759993e7f --- /dev/null +++ b/netwerk/base/http-sfv/SFVService.cpp @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "nsCOMPtr.h" +#include "SFVService.h" + +// This anonymous namespace prevents outside C++ code from improperly accessing +// these implementation details. +namespace { +extern "C" { +// Implemented in Rust. +void new_sfv_service(nsISFVService** result); +} + +static mozilla::StaticRefPtr<nsISFVService> sService; +} // namespace + +namespace mozilla::net { + +already_AddRefed<nsISFVService> GetSFVService() { + nsCOMPtr<nsISFVService> service; + + if (sService) { + service = sService; + } else { + new_sfv_service(getter_AddRefs(service)); + sService = service; + mozilla::ClearOnShutdown(&sService); + } + + return service.forget(); +} + +} // namespace mozilla::net diff --git a/netwerk/base/http-sfv/SFVService.h b/netwerk/base/http-sfv/SFVService.h new file mode 100644 index 0000000000..4016951609 --- /dev/null +++ b/netwerk/base/http-sfv/SFVService.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStructuredFieldValues.h" +namespace mozilla { +namespace net { + +already_AddRefed<nsISFVService> GetSFVService(); + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/http-sfv/moz.build b/netwerk/base/http-sfv/moz.build new file mode 100644 index 0000000000..ed857c91a8 --- /dev/null +++ b/netwerk/base/http-sfv/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + "nsIStructuredFieldValues.idl", +] + +XPIDL_MODULE = "http-sfv" + +EXPORTS.mozilla.net += ["SFVService.h"] + +SOURCES += ["SFVService.cpp"] + +FINAL_LIBRARY = "xul" diff --git a/netwerk/base/http-sfv/nsIStructuredFieldValues.idl b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl new file mode 100644 index 0000000000..3f02b33953 --- /dev/null +++ b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl @@ -0,0 +1,290 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Conceptually, there are three types of structured field (header) values: + * + * Item - can be an Integer, Decimal, String, Token, Byte Sequence, or Boolean. + * It can have associated Parameters. + * List - array of zero or more members, each of which can be an Item or an InnerList, + * both of which can be Parameterized. + * Dictionary - ordered map of name-value pairs, where the names are short textual strings + * and the values are Items or arrays of Items (represented with InnerList), + * both of which can be Parameterized. There can be zero or more members, + * and their names are unique in the scope of the Dictionary they occur within. + * + * + * There's also a few primitive types used to construct structured field values: + * - BareItem used as Item's value or as a parameter value in Parameters. + * - Parameters are an ordered map of key-value pairs that are associated with an Item or InnerList. + * The keys are unique within the scope the Parameters they occur within, and the values are BareItem. + * - InnerList is an array of zero or more Items. Can have Parameters. + * - ListEntry represents either Item or InnerList as a member of List or as member-value in Dictionary. + */ + + + +/** + * nsISFVBareItem is a building block for Item header value (nsISFVItem) and Parameters (nsISFVParams). + * It can be of type BOOL, STRING, DECIMAL, INTEGER, TOKEN, BYTE_SEQUENCE. + * Each type is represented by its own interface which is used to create + * a bare item of that type. + */ +[scriptable, builtinclass, uuid(7072853f-215b-4a8a-92e5-9732bccc377b)] +interface nsISFVBareItem : nsISupports { + const long BOOL = 1; + const long STRING = 2; + const long DECIMAL = 3; + const long INTEGER = 4; + const long TOKEN = 5; + const long BYTE_SEQUENCE = 6; + + /** + * Returns value associated with type of bare item. + * Used to identify type of bare item without querying for interface + * (like nsISFVString, etc). + */ + readonly attribute long type; +}; + +[scriptable, builtinclass, uuid(843eea44-990a-422c-bbf2-2aa4ba9ee4d2)] +interface nsISFVInteger : nsISFVBareItem { + attribute long long value; +}; + +[scriptable, builtinclass, uuid(df6a0787-7caa-4fef-b145-08c1104c2fde)] +interface nsISFVString : nsISFVBareItem { + attribute ACString value; +}; + +[scriptable, builtinclass, uuid(d263c6d7-4123-4c39-a121-ccf874a19012)] +interface nsISFVBool : nsISFVBareItem { + attribute boolean value; +}; + +[scriptable, builtinclass, uuid(1098da8b-b4df-4526-b985-53dbd4160ad2)] +interface nsISFVDecimal : nsISFVBareItem { + attribute double value; +}; + +[scriptable, builtinclass, uuid(8ad33d52-b9b2-4a17-8aa8-991250fc1214)] +interface nsISFVToken : nsISFVBareItem { + attribute ACString value; +}; + +[scriptable, builtinclass, uuid(887eaef0-19fe-42bc-9a42-9ff773aa8fea)] +interface nsISFVByteSeq : nsISFVBareItem { + attribute ACString value; +}; + + +/** + * nsISFVParams represents parameters, key-value pairs of ACString and nsISFVBareItem, + * which parametrize Item type header or InnerList type withing List type header. + */ +[scriptable, builtinclass, uuid(b1a397d7-3333-43e7-993a-fbe8ab90ee96)] +interface nsISFVParams : nsISupports { + /** + * Return value (nsISFVBareItem) stored for key, if it is present + * + * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters. + */ + nsISFVBareItem get(in ACString key); + + /** + * Insert a new key-value pair. + * If an equivalent key already exists: the key remains and retains in its place in the order, + * its corresponding value is updated with the new value. + * + * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVBareItem interface. + */ + void set(in ACString key, in nsISFVBareItem item); + + /** + * Remove the key-value pair equivalent to key. + * + * @throws NS_ERROR_UNEXPECTED upon attempt to delete key that does not exist in parameters. + */ + void delete(in ACString key); + + /** + * Returns array of keys. + */ + Array<ACString> keys(); +}; + +/** + * nsISFVParametrizable is implemented for types that + * can be parametrized with nsISFVParams + */ +[scriptable, builtinclass, uuid(6c0399f8-01de-4b25-b339-68e35e8d2e49)] +interface nsISFVParametrizable : nsISupports { + readonly attribute nsISFVParams params; +}; + +/** + * nsISFVItemOrInnerList represents member in nsISFVList + * or member-value in nsISFVDictionary. + * nsISFVItemOrInnerList is implemented for + * nsISFVItem or nsISFVInnerList, both of which are used + * to create nsISFVList and nsISFVDictionary. + */ +[scriptable, builtinclass, uuid(99ac1b56-b5b3-44e7-ad96-db7444aae4b2)] +interface nsISFVItemOrInnerList : nsISFVParametrizable { +}; + +/** + * nsISFVSerialize indicates that object can be serialized into ACString. + */ +[scriptable, builtinclass, uuid(28b9215d-c131-413c-9482-0004a371a5ec)] +interface nsISFVSerialize : nsISupports { + ACString serialize(); +}; + +/** + * nsISFVItem represents Item structured header value. + */ +[scriptable, builtinclass, uuid(abe8826b-6af7-4e54-bd2c-46ab231700ce)] +interface nsISFVItem : nsISFVItemOrInnerList { + readonly attribute nsISFVBareItem value; + ACString serialize(); +}; + +/** + * nsISFVInnerList can be used as a member of nsISFVList + * or a member-value of nsISFVDictionary. + */ +[scriptable, builtinclass, uuid(b2e52be2-8488-41b2-9ee2-3c48d92d095c)] +interface nsISFVInnerList : nsISFVItemOrInnerList { + attribute Array<nsISFVItem> items; +}; + +/** + * nsISFVList represents List structured header value. + */ +[scriptable, builtinclass, uuid(02bb92a6-d1de-449c-b54f-d137f30c613d)] +interface nsISFVList : nsISFVSerialize { + /** + * Returns array of members. + * QueryInterface can be used on a member to get more specific type. + */ + attribute Array<nsISFVItemOrInnerList> members; + + /** + * In case when header value is split across lines, it's possible + * this method parses supplied line and merges it with members of existing object. + */ + void parseMore(in ACString header); +}; + +/** + * nsISFVDictionary represents nsISFVDictionary structured header value. + */ +[scriptable, builtinclass, uuid(6642a7fe-7026-4eba-b730-05e230ee3437)] +interface nsISFVDictionary : nsISFVSerialize { + + /** + * Return value (nsISFVItemOrInnerList) stored for key, if it is present. + * QueryInterface can be used on a value to get more specific type. + * + * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters. + */ + nsISFVItemOrInnerList get(in ACString key); + + /** + * Insert a new key-value pair. + * If an equivalent key already exists: the key remains and retains in its place in the order, + * its corresponding value is updated with the new value. + * + * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVItemOrInnerList interface. + */ + void set(in ACString key, in nsISFVItemOrInnerList member_value); + + /** + * Remove the key-value pair equivalent to key. + * + * @throws NS_ERROR_UNEXPECTED upon attempt to delete key that does not exist in parameters. + */ + void delete(in ACString key); + + /** + * Returns array of keys. + */ + Array<ACString> keys(); + + /** + * In case when header value is split across lines, it's possible + * this method parses supplied line and merges it with members of existing object. + */ + void parseMore(in ACString header); +}; + + +/** + * nsISFVService provides a set of functions for working with HTTP header value as an object. + * It exposes functions for creating object from string containing header value, + * as well as individual components for manual structured header object creation. + */ +[scriptable, builtinclass, uuid(049f4be1-2f22-4438-a8da-518552ed390c)] +interface nsISFVService: nsISupports +{ + /** + * Parses provided string into Dictionary header value (nsISFVDictionary). + * + * @throws NS_ERROR_FAILURE if parsing fails. + */ + nsISFVDictionary parseDictionary(in ACString header); + + /** + * Parses provided string into List header value (nsISFVList). + * + * @throws NS_ERROR_FAILURE if parsing fails. + */ + nsISFVList parseList(in ACString header); + + /** + * Parses provided string into Item header value (nsISFVItem). + * + * @throws NS_ERROR_FAILURE if parsing fails. + */ + nsISFVItem parseItem(in ACString header); + + /** + * The following functions create bare item of specific type. + */ + nsISFVInteger newInteger(in long long value); + nsISFVBool newBool(in bool value); + nsISFVDecimal newDecimal(in double value); + nsISFVString newString(in ACString value); + nsISFVByteSeq newByteSequence(in ACString value); + nsISFVToken newToken(in ACString value); + + /** + * Creates nsISFVParams with no parameters. In other words, it's an empty map byt default. + */ + nsISFVParams newParameters(); + + /** + * Creates nsISFVInnerList from nsISFVItem array and nsISFVParams. + */ + nsISFVInnerList newInnerList(in Array<nsISFVItem> items, in nsISFVParams params); + + /** + * Creates nsISFVItem, which represents Item header value, from nsISFVBareItem and associated nsISFVParams. + */ + nsISFVItem newItem(in nsISFVBareItem value, in nsISFVParams params); + + /** + * Creates nsISFVList, which represents List header value, from array of nsISFVItemOrInnerList. + * nsISFVItemOrInnerList represens either Item (nsISFVItem) or Inner List (nsISFVInnerList). + */ + nsISFVList newList(in Array<nsISFVItemOrInnerList> members); + + /** + * Creates nsISFVDictionary representing Dictionary header value. It is empty by default. + */ + nsISFVDictionary newDictionary(); +}; diff --git a/netwerk/base/http-sfv/src/lib.rs b/netwerk/base/http-sfv/src/lib.rs new file mode 100644 index 0000000000..fe669f5f3f --- /dev/null +++ b/netwerk/base/http-sfv/src/lib.rs @@ -0,0 +1,873 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_NULL_POINTER, NS_ERROR_UNEXPECTED, NS_OK}; +use nsstring::{nsACString, nsCString}; +use sfv::Parser; +use sfv::SerializeValue; +use sfv::{ + BareItem, Decimal, Dictionary, FromPrimitive, InnerList, Item, List, ListEntry, Parameters, + ParseMore, +}; +use std::cell::RefCell; +use std::ops::Deref; +use thin_vec::ThinVec; +use xpcom::interfaces::{ + nsISFVBareItem, nsISFVBool, nsISFVByteSeq, nsISFVDecimal, nsISFVDictionary, nsISFVInnerList, + nsISFVInteger, nsISFVItem, nsISFVItemOrInnerList, nsISFVList, nsISFVParams, nsISFVService, + nsISFVString, nsISFVToken, +}; +use xpcom::{xpcom, xpcom_method, RefPtr, XpCom}; + +#[no_mangle] +pub unsafe extern "C" fn new_sfv_service(result: *mut *const nsISFVService) { + let service: RefPtr<SFVService> = SFVService::new(); + RefPtr::new(service.coerce::<nsISFVService>()).forget(&mut *result); +} + +#[xpcom(implement(nsISFVService), atomic)] +struct SFVService {} + +impl SFVService { + fn new() -> RefPtr<SFVService> { + SFVService::allocate(InitSFVService {}) + } + + xpcom_method!(parse_dictionary => ParseDictionary(header: *const nsACString) -> *const nsISFVDictionary); + fn parse_dictionary(&self, header: &nsACString) -> Result<RefPtr<nsISFVDictionary>, nsresult> { + let parsed_dict = Parser::parse_dictionary(&header).map_err(|_| NS_ERROR_FAILURE)?; + let sfv_dict = SFVDictionary::new(); + sfv_dict.value.replace(parsed_dict); + Ok(RefPtr::new(sfv_dict.coerce::<nsISFVDictionary>())) + } + + xpcom_method!(parse_list => ParseList(field_value: *const nsACString) -> *const nsISFVList); + fn parse_list(&self, header: &nsACString) -> Result<RefPtr<nsISFVList>, nsresult> { + let parsed_list = Parser::parse_list(&header).map_err(|_| NS_ERROR_FAILURE)?; + + let mut nsi_members = Vec::new(); + for item_or_inner_list in parsed_list.iter() { + nsi_members.push(interface_from_list_entry(item_or_inner_list)?) + } + let sfv_list = SFVList::allocate(InitSFVList { + members: RefCell::new(nsi_members), + }); + Ok(RefPtr::new(sfv_list.coerce::<nsISFVList>())) + } + + xpcom_method!(parse_item => ParseItem(header: *const nsACString) -> *const nsISFVItem); + fn parse_item(&self, header: &nsACString) -> Result<RefPtr<nsISFVItem>, nsresult> { + let parsed_item = Parser::parse_item(&header).map_err(|_| NS_ERROR_FAILURE)?; + interface_from_item(&parsed_item) + } + + xpcom_method!(new_integer => NewInteger(value: i64) -> *const nsISFVInteger); + fn new_integer(&self, value: i64) -> Result<RefPtr<nsISFVInteger>, nsresult> { + Ok(RefPtr::new( + SFVInteger::new(value).coerce::<nsISFVInteger>(), + )) + } + + xpcom_method!(new_decimal => NewDecimal(value: f64) -> *const nsISFVDecimal); + fn new_decimal(&self, value: f64) -> Result<RefPtr<nsISFVDecimal>, nsresult> { + Ok(RefPtr::new( + SFVDecimal::new(value).coerce::<nsISFVDecimal>(), + )) + } + + xpcom_method!(new_bool => NewBool(value: bool) -> *const nsISFVBool); + fn new_bool(&self, value: bool) -> Result<RefPtr<nsISFVBool>, nsresult> { + Ok(RefPtr::new(SFVBool::new(value).coerce::<nsISFVBool>())) + } + + xpcom_method!(new_string => NewString(value: *const nsACString) -> *const nsISFVString); + fn new_string(&self, value: &nsACString) -> Result<RefPtr<nsISFVString>, nsresult> { + Ok(RefPtr::new(SFVString::new(value).coerce::<nsISFVString>())) + } + + xpcom_method!(new_token => NewToken(value: *const nsACString) -> *const nsISFVToken); + fn new_token(&self, value: &nsACString) -> Result<RefPtr<nsISFVToken>, nsresult> { + Ok(RefPtr::new(SFVToken::new(value).coerce::<nsISFVToken>())) + } + + xpcom_method!(new_byte_sequence => NewByteSequence(value: *const nsACString) -> *const nsISFVByteSeq); + fn new_byte_sequence(&self, value: &nsACString) -> Result<RefPtr<nsISFVByteSeq>, nsresult> { + Ok(RefPtr::new( + SFVByteSeq::new(value).coerce::<nsISFVByteSeq>(), + )) + } + + xpcom_method!(new_parameters => NewParameters() -> *const nsISFVParams); + fn new_parameters(&self) -> Result<RefPtr<nsISFVParams>, nsresult> { + Ok(RefPtr::new(SFVParams::new().coerce::<nsISFVParams>())) + } + + xpcom_method!(new_item => NewItem(value: *const nsISFVBareItem, params: *const nsISFVParams) -> *const nsISFVItem); + fn new_item( + &self, + value: &nsISFVBareItem, + params: &nsISFVParams, + ) -> Result<RefPtr<nsISFVItem>, nsresult> { + Ok(RefPtr::new( + SFVItem::new(value, params).coerce::<nsISFVItem>(), + )) + } + + xpcom_method!(new_inner_list => NewInnerList(items: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>, params: *const nsISFVParams) -> *const nsISFVInnerList); + fn new_inner_list( + &self, + items: &thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>, + params: &nsISFVParams, + ) -> Result<RefPtr<nsISFVInnerList>, nsresult> { + let items = items + .iter() + .cloned() + .map(|item| item.ok_or(NS_ERROR_NULL_POINTER)) + .collect::<Result<Vec<_>, nsresult>>()?; + Ok(RefPtr::new( + SFVInnerList::new(items, params).coerce::<nsISFVInnerList>(), + )) + } + + xpcom_method!(new_list => NewList(members: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>) -> *const nsISFVList); + fn new_list( + &self, + members: &thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>, + ) -> Result<RefPtr<nsISFVList>, nsresult> { + let members = members + .iter() + .cloned() + .map(|item| item.ok_or(NS_ERROR_NULL_POINTER)) + .collect::<Result<Vec<_>, nsresult>>()?; + Ok(RefPtr::new(SFVList::new(members).coerce::<nsISFVList>())) + } + + xpcom_method!(new_dictionary => NewDictionary() -> *const nsISFVDictionary); + fn new_dictionary(&self) -> Result<RefPtr<nsISFVDictionary>, nsresult> { + Ok(RefPtr::new( + SFVDictionary::new().coerce::<nsISFVDictionary>(), + )) + } +} + +#[xpcom(implement(nsISFVInteger, nsISFVBareItem), nonatomic)] +struct SFVInteger { + value: RefCell<i64>, +} + +impl SFVInteger { + fn new(value: i64) -> RefPtr<SFVInteger> { + SFVInteger::allocate(InitSFVInteger { + value: RefCell::new(value), + }) + } + + xpcom_method!(get_value => GetValue() -> i64); + fn get_value(&self) -> Result<i64, nsresult> { + Ok(*self.value.borrow()) + } + + xpcom_method!(set_value => SetValue(value: i64)); + fn set_value(&self, value: i64) -> Result<(), nsresult> { + self.value.replace(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result<i32, nsresult> { + Ok(nsISFVBareItem::INTEGER) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVBool, nsISFVBareItem), nonatomic)] +struct SFVBool { + value: RefCell<bool>, +} + +impl SFVBool { + fn new(value: bool) -> RefPtr<SFVBool> { + SFVBool::allocate(InitSFVBool { + value: RefCell::new(value), + }) + } + + xpcom_method!(get_value => GetValue() -> bool); + fn get_value(&self) -> Result<bool, nsresult> { + Ok(*self.value.borrow()) + } + + xpcom_method!(set_value => SetValue(value: bool)); + fn set_value(&self, value: bool) -> Result<(), nsresult> { + self.value.replace(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result<i32, nsresult> { + Ok(nsISFVBareItem::BOOL) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVString, nsISFVBareItem), nonatomic)] +struct SFVString { + value: RefCell<nsCString>, +} + +impl SFVString { + fn new(value: &nsACString) -> RefPtr<SFVString> { + SFVString::allocate(InitSFVString { + value: RefCell::new(nsCString::from(value)), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> nsACString + ); + + fn get_value(&self) -> Result<nsCString, nsresult> { + Ok(self.value.borrow().clone()) + } + + xpcom_method!( + set_value => SetValue(value: *const nsACString) + ); + + fn set_value(&self, value: &nsACString) -> Result<(), nsresult> { + self.value.borrow_mut().assign(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result<i32, nsresult> { + Ok(nsISFVBareItem::STRING) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVToken, nsISFVBareItem), nonatomic)] +struct SFVToken { + value: RefCell<nsCString>, +} + +impl SFVToken { + fn new(value: &nsACString) -> RefPtr<SFVToken> { + SFVToken::allocate(InitSFVToken { + value: RefCell::new(nsCString::from(value)), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> nsACString + ); + + fn get_value(&self) -> Result<nsCString, nsresult> { + Ok(self.value.borrow().clone()) + } + + xpcom_method!( + set_value => SetValue(value: *const nsACString) + ); + + fn set_value(&self, value: &nsACString) -> Result<(), nsresult> { + self.value.borrow_mut().assign(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result<i32, nsresult> { + Ok(nsISFVBareItem::TOKEN) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVByteSeq, nsISFVBareItem), nonatomic)] +struct SFVByteSeq { + value: RefCell<nsCString>, +} + +impl SFVByteSeq { + fn new(value: &nsACString) -> RefPtr<SFVByteSeq> { + SFVByteSeq::allocate(InitSFVByteSeq { + value: RefCell::new(nsCString::from(value)), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> nsACString + ); + + fn get_value(&self) -> Result<nsCString, nsresult> { + Ok(self.value.borrow().clone()) + } + + xpcom_method!( + set_value => SetValue(value: *const nsACString) + ); + + fn set_value(&self, value: &nsACString) -> Result<(), nsresult> { + self.value.borrow_mut().assign(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result<i32, nsresult> { + Ok(nsISFVBareItem::BYTE_SEQUENCE) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVDecimal, nsISFVBareItem), nonatomic)] +struct SFVDecimal { + value: RefCell<f64>, +} + +impl SFVDecimal { + fn new(value: f64) -> RefPtr<SFVDecimal> { + SFVDecimal::allocate(InitSFVDecimal { + value: RefCell::new(value), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> f64 + ); + + fn get_value(&self) -> Result<f64, nsresult> { + Ok(*self.value.borrow()) + } + + xpcom_method!( + set_value => SetValue(value: f64) + ); + + fn set_value(&self, value: f64) -> Result<(), nsresult> { + self.value.replace(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i32); + fn get_type(&self) -> Result<i32, nsresult> { + Ok(nsISFVBareItem::DECIMAL) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVParams), nonatomic)] +struct SFVParams { + params: RefCell<Parameters>, +} + +impl SFVParams { + fn new() -> RefPtr<SFVParams> { + SFVParams::allocate(InitSFVParams { + params: RefCell::new(Parameters::new()), + }) + } + + xpcom_method!( + get => Get(key: *const nsACString) -> *const nsISFVBareItem + ); + + fn get(&self, key: &nsACString) -> Result<RefPtr<nsISFVBareItem>, nsresult> { + let key = key.to_utf8(); + let params = self.params.borrow(); + let param_val = params.get(key.as_ref()); + + match param_val { + Some(val) => interface_from_bare_item(val), + None => return Err(NS_ERROR_UNEXPECTED), + } + } + + xpcom_method!( + set => Set(key: *const nsACString, item: *const nsISFVBareItem) + ); + + fn set(&self, key: &nsACString, item: &nsISFVBareItem) -> Result<(), nsresult> { + let key = key.to_utf8().into_owned(); + let bare_item = bare_item_from_interface(item)?; + self.params.borrow_mut().insert(key, bare_item); + Ok(()) + } + + xpcom_method!( + delete => Delete(key: *const nsACString) + ); + fn delete(&self, key: &nsACString) -> Result<(), nsresult> { + let key = key.to_utf8(); + let mut params = self.params.borrow_mut(); + + if !params.contains_key(key.as_ref()) { + return Err(NS_ERROR_UNEXPECTED); + } + + // Keeps only entries that don't match key + params.retain(|k, _| k != key.as_ref()); + Ok(()) + } + + xpcom_method!( + keys => Keys() -> thin_vec::ThinVec<nsCString> + ); + fn keys(&self) -> Result<thin_vec::ThinVec<nsCString>, nsresult> { + let keys = self.params.borrow(); + let keys = keys + .keys() + .map(nsCString::from) + .collect::<ThinVec<nsCString>>(); + Ok(keys) + } + + fn from_interface(obj: &nsISFVParams) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVItem, nsISFVItemOrInnerList), nonatomic)] +struct SFVItem { + value: RefPtr<nsISFVBareItem>, + params: RefPtr<nsISFVParams>, +} + +impl SFVItem { + fn new(value: &nsISFVBareItem, params: &nsISFVParams) -> RefPtr<SFVItem> { + SFVItem::allocate(InitSFVItem { + value: RefPtr::new(value), + params: RefPtr::new(params), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> *const nsISFVBareItem + ); + + fn get_value(&self) -> Result<RefPtr<nsISFVBareItem>, nsresult> { + Ok(self.value.clone()) + } + + xpcom_method!( + get_params => GetParams( + ) -> *const nsISFVParams + ); + fn get_params(&self) -> Result<RefPtr<nsISFVParams>, nsresult> { + Ok(self.params.clone()) + } + + xpcom_method!( + serialize => Serialize() -> nsACString + ); + fn serialize(&self) -> Result<nsCString, nsresult> { + let bare_item = bare_item_from_interface(self.value.deref())?; + let params = params_from_interface(self.params.deref())?; + let serialized = Item::with_params(bare_item, params) + .serialize_value() + .map_err(|_| NS_ERROR_FAILURE)?; + Ok(nsCString::from(serialized)) + } + + fn from_interface(obj: &nsISFVItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVInnerList, nsISFVItemOrInnerList), nonatomic)] +struct SFVInnerList { + items: RefCell<Vec<RefPtr<nsISFVItem>>>, + params: RefPtr<nsISFVParams>, +} + +impl SFVInnerList { + fn new(items: Vec<RefPtr<nsISFVItem>>, params: &nsISFVParams) -> RefPtr<SFVInnerList> { + SFVInnerList::allocate(InitSFVInnerList { + items: RefCell::new(items), + params: RefPtr::new(params), + }) + } + + xpcom_method!( + get_items => GetItems() -> thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>> + ); + + fn get_items(&self) -> Result<thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>, nsresult> { + let items = self.items.borrow().iter().cloned().map(Some).collect(); + Ok(items) + } + + #[allow(non_snake_case)] + unsafe fn SetItems( + &self, + value: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>, + ) -> nsresult { + if value.is_null() { + return NS_ERROR_NULL_POINTER; + } + match (*value) + .iter() + .map(|v| v.clone().ok_or(NS_ERROR_NULL_POINTER)) + .collect::<Result<Vec<_>, nsresult>>() + { + Ok(value) => *self.items.borrow_mut() = value, + Err(rv) => return rv, + } + NS_OK + } + + xpcom_method!( + get_params => GetParams( + ) -> *const nsISFVParams + ); + fn get_params(&self) -> Result<RefPtr<nsISFVParams>, nsresult> { + Ok(self.params.clone()) + } + + fn from_interface(obj: &nsISFVInnerList) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[xpcom(implement(nsISFVList, nsISFVSerialize), nonatomic)] +struct SFVList { + members: RefCell<Vec<RefPtr<nsISFVItemOrInnerList>>>, +} + +impl SFVList { + fn new(members: Vec<RefPtr<nsISFVItemOrInnerList>>) -> RefPtr<SFVList> { + SFVList::allocate(InitSFVList { + members: RefCell::new(members), + }) + } + + xpcom_method!( + get_members => GetMembers() -> thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>> + ); + + fn get_members( + &self, + ) -> Result<thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>, nsresult> { + Ok(self.members.borrow().iter().cloned().map(Some).collect()) + } + + #[allow(non_snake_case)] + unsafe fn SetMembers( + &self, + value: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>, + ) -> nsresult { + if value.is_null() { + return NS_ERROR_NULL_POINTER; + } + match (*value) + .iter() + .map(|v| v.clone().ok_or(NS_ERROR_NULL_POINTER)) + .collect::<Result<Vec<_>, nsresult>>() + { + Ok(value) => *self.members.borrow_mut() = value, + Err(rv) => return rv, + } + NS_OK + } + + xpcom_method!( + parse_more => ParseMore(header: *const nsACString) + ); + fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> { + // create List from SFVList to call parse_more on it + let mut list = List::new(); + let members = self.members.borrow().clone(); + for interface_entry in members.iter() { + let item_or_inner_list = list_entry_from_interface(interface_entry)?; + list.push(item_or_inner_list); + } + + let _ = list.parse_more(&header).map_err(|_| NS_ERROR_FAILURE)?; + + // replace SFVList's members with new_members + let mut new_members = Vec::new(); + for item_or_inner_list in list.iter() { + new_members.push(interface_from_list_entry(item_or_inner_list)?) + } + self.members.replace(new_members); + Ok(()) + } + + xpcom_method!( + serialize => Serialize() -> nsACString + ); + fn serialize(&self) -> Result<nsCString, nsresult> { + let mut list = List::new(); + let members = self.members.borrow().clone(); + for interface_entry in members.iter() { + let item_or_inner_list = list_entry_from_interface(interface_entry)?; + list.push(item_or_inner_list); + } + + let serialized = list.serialize_value().map_err(|_| NS_ERROR_FAILURE)?; + Ok(nsCString::from(serialized)) + } +} + +#[xpcom(implement(nsISFVDictionary, nsISFVSerialize), nonatomic)] +struct SFVDictionary { + value: RefCell<Dictionary>, +} + +impl SFVDictionary { + fn new() -> RefPtr<SFVDictionary> { + SFVDictionary::allocate(InitSFVDictionary { + value: RefCell::new(Dictionary::new()), + }) + } + + xpcom_method!( + get => Get(key: *const nsACString) -> *const nsISFVItemOrInnerList + ); + + fn get(&self, key: &nsACString) -> Result<RefPtr<nsISFVItemOrInnerList>, nsresult> { + let key = key.to_utf8(); + let value = self.value.borrow(); + let member_value = value.get(key.as_ref()); + + match member_value { + Some(member) => interface_from_list_entry(member), + None => return Err(NS_ERROR_UNEXPECTED), + } + } + + xpcom_method!( + set => Set(key: *const nsACString, item: *const nsISFVItemOrInnerList) + ); + + fn set(&self, key: &nsACString, member_value: &nsISFVItemOrInnerList) -> Result<(), nsresult> { + let key = key.to_utf8().into_owned(); + let value = list_entry_from_interface(member_value)?; + self.value.borrow_mut().insert(key, value); + Ok(()) + } + + xpcom_method!( + delete => Delete(key: *const nsACString) + ); + + fn delete(&self, key: &nsACString) -> Result<(), nsresult> { + let key = key.to_utf8(); + let mut params = self.value.borrow_mut(); + + if !params.contains_key(key.as_ref()) { + return Err(NS_ERROR_UNEXPECTED); + } + + // Keeps only entries that don't match key + params.retain(|k, _| k != key.as_ref()); + Ok(()) + } + + xpcom_method!( + keys => Keys() -> thin_vec::ThinVec<nsCString> + ); + fn keys(&self) -> Result<thin_vec::ThinVec<nsCString>, nsresult> { + let members = self.value.borrow(); + let keys = members + .keys() + .map(nsCString::from) + .collect::<ThinVec<nsCString>>(); + Ok(keys) + } + + xpcom_method!( + parse_more => ParseMore(header: *const nsACString) + ); + fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> { + let _ = self + .value + .borrow_mut() + .parse_more(&header) + .map_err(|_| NS_ERROR_FAILURE)?; + Ok(()) + } + + xpcom_method!( + serialize => Serialize() -> nsACString + ); + fn serialize(&self) -> Result<nsCString, nsresult> { + let serialized = self + .value + .borrow() + .serialize_value() + .map_err(|_| NS_ERROR_FAILURE)?; + Ok(nsCString::from(serialized)) + } +} + +fn bare_item_from_interface(obj: &nsISFVBareItem) -> Result<BareItem, nsresult> { + let obj = obj + .query_interface::<nsISFVBareItem>() + .ok_or(NS_ERROR_UNEXPECTED)?; + let mut obj_type: i32 = -1; + unsafe { + obj.deref().GetType(&mut obj_type); + } + + match obj_type { + nsISFVBareItem::BOOL => { + let item_value = SFVBool::from_bare_item_interface(obj.deref()).get_value()?; + Ok(BareItem::Boolean(item_value)) + } + nsISFVBareItem::STRING => { + let string_itm = SFVString::from_bare_item_interface(obj.deref()).get_value()?; + let item_value = (*string_itm.to_utf8()).to_string(); + Ok(BareItem::String(item_value)) + } + nsISFVBareItem::TOKEN => { + let token_itm = SFVToken::from_bare_item_interface(obj.deref()).get_value()?; + let item_value = (*token_itm.to_utf8()).to_string(); + Ok(BareItem::Token(item_value)) + } + nsISFVBareItem::INTEGER => { + let item_value = SFVInteger::from_bare_item_interface(obj.deref()).get_value()?; + Ok(BareItem::Integer(item_value)) + } + nsISFVBareItem::DECIMAL => { + let item_value = SFVDecimal::from_bare_item_interface(obj.deref()).get_value()?; + let decimal: Decimal = Decimal::from_f64(item_value).ok_or(NS_ERROR_UNEXPECTED)?; + Ok(BareItem::Decimal(decimal)) + } + nsISFVBareItem::BYTE_SEQUENCE => { + let token_itm = SFVByteSeq::from_bare_item_interface(obj.deref()).get_value()?; + let item_value: String = (*token_itm.to_utf8()).to_string(); + Ok(BareItem::ByteSeq(item_value.into_bytes())) + } + _ => return Err(NS_ERROR_UNEXPECTED), + } +} + +fn params_from_interface(obj: &nsISFVParams) -> Result<Parameters, nsresult> { + let params = SFVParams::from_interface(obj).params.borrow(); + Ok(params.clone()) +} + +fn item_from_interface(obj: &nsISFVItem) -> Result<Item, nsresult> { + let sfv_item = SFVItem::from_interface(obj); + let bare_item = bare_item_from_interface(sfv_item.value.deref())?; + let parameters = params_from_interface(sfv_item.params.deref())?; + Ok(Item::with_params(bare_item, parameters)) +} + +fn inner_list_from_interface(obj: &nsISFVInnerList) -> Result<InnerList, nsresult> { + let sfv_inner_list = SFVInnerList::from_interface(obj); + + let mut inner_list_items: Vec<Item> = vec![]; + for item in sfv_inner_list.items.borrow().iter() { + let item = item_from_interface(item)?; + inner_list_items.push(item); + } + let inner_list_params = params_from_interface(sfv_inner_list.params.deref())?; + Ok(InnerList::with_params(inner_list_items, inner_list_params)) +} + +fn list_entry_from_interface(obj: &nsISFVItemOrInnerList) -> Result<ListEntry, nsresult> { + if let Some(nsi_item) = obj.query_interface::<nsISFVItem>() { + let item = item_from_interface(nsi_item.deref())?; + Ok(ListEntry::Item(item)) + } else if let Some(nsi_inner_list) = obj.query_interface::<nsISFVInnerList>() { + let inner_list = inner_list_from_interface(nsi_inner_list.deref())?; + Ok(ListEntry::InnerList(inner_list)) + } else { + return Err(NS_ERROR_UNEXPECTED); + } +} + +fn interface_from_bare_item(bare_item: &BareItem) -> Result<RefPtr<nsISFVBareItem>, nsresult> { + let bare_item = match bare_item { + BareItem::Boolean(val) => RefPtr::new(SFVBool::new(*val).coerce::<nsISFVBareItem>()), + BareItem::String(val) => { + RefPtr::new(SFVString::new(&nsCString::from(val)).coerce::<nsISFVBareItem>()) + } + BareItem::Token(val) => { + RefPtr::new(SFVToken::new(&nsCString::from(val)).coerce::<nsISFVBareItem>()) + } + BareItem::ByteSeq(val) => RefPtr::new( + SFVByteSeq::new(&nsCString::from(String::from_utf8(val.to_vec()).unwrap())) + .coerce::<nsISFVBareItem>(), + ), + BareItem::Decimal(val) => { + let val = val + .to_string() + .parse::<f64>() + .map_err(|_| NS_ERROR_UNEXPECTED)?; + RefPtr::new(SFVDecimal::new(val).coerce::<nsISFVBareItem>()) + } + BareItem::Integer(val) => RefPtr::new(SFVInteger::new(*val).coerce::<nsISFVBareItem>()), + }; + + Ok(bare_item) +} + +fn interface_from_item(item: &Item) -> Result<RefPtr<nsISFVItem>, nsresult> { + let nsi_bare_item = interface_from_bare_item(&item.bare_item)?; + let nsi_params = interface_from_params(&item.params)?; + Ok(RefPtr::new( + SFVItem::new(&nsi_bare_item, &nsi_params).coerce::<nsISFVItem>(), + )) +} + +fn interface_from_params(params: &Parameters) -> Result<RefPtr<nsISFVParams>, nsresult> { + let sfv_params = SFVParams::new(); + for (key, value) in params.iter() { + sfv_params + .params + .borrow_mut() + .insert(key.clone(), value.clone()); + } + Ok(RefPtr::new(sfv_params.coerce::<nsISFVParams>())) +} + +fn interface_from_list_entry( + member: &ListEntry, +) -> Result<RefPtr<nsISFVItemOrInnerList>, nsresult> { + match member { + ListEntry::Item(item) => { + let nsi_bare_item = interface_from_bare_item(&item.bare_item)?; + let nsi_params = interface_from_params(&item.params)?; + Ok(RefPtr::new( + SFVItem::new(&nsi_bare_item, &nsi_params).coerce::<nsISFVItemOrInnerList>(), + )) + } + ListEntry::InnerList(inner_list) => { + let mut nsi_inner_list = Vec::new(); + for item in inner_list.items.iter() { + let nsi_item = interface_from_item(item)?; + nsi_inner_list.push(nsi_item); + } + + let nsi_params = interface_from_params(&inner_list.params)?; + Ok(RefPtr::new( + SFVInnerList::new(nsi_inner_list, &nsi_params).coerce::<nsISFVItemOrInnerList>(), + )) + } + } +} diff --git a/netwerk/base/makecppstring.py b/netwerk/base/makecppstring.py new file mode 100644 index 0000000000..330fcfb04c --- /dev/null +++ b/netwerk/base/makecppstring.py @@ -0,0 +1,17 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys + + +def main(output, filename): + file = open(filename, "r") + output.write('R"(') # insert literal start + for line in file: + output.write(line) + output.write(')"') # insert literal end + + +if __name__ == "__main__": + main(sys.stdout, sys.argv[1]) diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build new file mode 100644 index 0000000000..e069a5f296 --- /dev/null +++ b/netwerk/base/moz.build @@ -0,0 +1,319 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + "mozIThirdPartyUtil.idl", + "nsIArrayBufferInputStream.idl", + "nsIAsyncStreamCopier.idl", + "nsIAsyncStreamCopier2.idl", + "nsIAsyncVerifyRedirectCallback.idl", + "nsIAuthInformation.idl", + "nsIAuthModule.idl", + "nsIAuthPrompt.idl", + "nsIAuthPrompt2.idl", + "nsIAuthPromptAdapterFactory.idl", + "nsIAuthPromptCallback.idl", + "nsIAuthPromptProvider.idl", + "nsIBackgroundFileSaver.idl", + "nsIBufferedStreams.idl", + "nsIByteRangeRequest.idl", + "nsICacheInfoChannel.idl", + "nsICachingChannel.idl", + "nsICancelable.idl", + "nsICaptivePortalService.idl", + "nsIChannel.idl", + "nsIChannelEventSink.idl", + "nsIChildChannel.idl", + "nsIClassifiedChannel.idl", + "nsIClassOfService.idl", + "nsIContentSniffer.idl", + "nsIDashboard.idl", + "nsIDashboardEventNotifier.idl", + "nsIDHCPClient.idl", + "nsIDownloader.idl", + "nsIEncodedChannel.idl", + "nsIExternalProtocolHandler.idl", + "nsIFileStreams.idl", + "nsIFileURL.idl", + "nsIForcePendingChannel.idl", + "nsIFormPOSTActionChannel.idl", + "nsIHttpAuthenticatorCallback.idl", + "nsIHttpPushListener.idl", + "nsIIncrementalDownload.idl", + "nsIIncrementalStreamLoader.idl", + "nsIInputStreamChannel.idl", + "nsIInputStreamPump.idl", + "nsIInterceptionInfo.idl", + "nsIIOService.idl", + "nsILoadContextInfo.idl", + "nsILoadGroup.idl", + "nsILoadGroupChild.idl", + "nsILoadInfo.idl", + "nsIMIMEInputStream.idl", + "nsIMultiPartChannel.idl", + "nsINestedURI.idl", + "nsINetAddr.idl", + "nsINetUtil.idl", + "nsINetworkConnectivityService.idl", + "nsINetworkInfoService.idl", + "nsINetworkInterceptController.idl", + "nsINetworkLinkService.idl", + "nsINetworkPredictor.idl", + "nsINetworkPredictorVerifier.idl", + "nsINullChannel.idl", + "nsIParentChannel.idl", + "nsIParentRedirectingChannel.idl", + "nsIPermission.idl", + "nsIPermissionManager.idl", + "nsIPrivateBrowsingChannel.idl", + "nsIProgressEventSink.idl", + "nsIPrompt.idl", + "nsIProtocolHandler.idl", + "nsIProtocolProxyCallback.idl", + "nsIProtocolProxyFilter.idl", + "nsIProtocolProxyService.idl", + "nsIProtocolProxyService2.idl", + "nsIProxiedChannel.idl", + "nsIProxiedProtocolHandler.idl", + "nsIProxyInfo.idl", + "nsIRandomGenerator.idl", + "nsIRedirectChannelRegistrar.idl", + "nsIRedirectHistoryEntry.idl", + "nsIRedirectResultListener.idl", + "nsIRequest.idl", + "nsIRequestContext.idl", + "nsIRequestObserver.idl", + "nsIRequestObserverProxy.idl", + "nsIResumableChannel.idl", + "nsISecCheckWrapChannel.idl", + "nsISecureBrowserUI.idl", + "nsISensitiveInfoHiddenURI.idl", + "nsISerializationHelper.idl", + "nsIServerSocket.idl", + "nsISimpleStreamListener.idl", + "nsISimpleURIMutator.idl", + "nsISocketFilter.idl", + "nsISocketTransport.idl", + "nsISocketTransportService.idl", + "nsISpeculativeConnect.idl", + "nsIStandardURL.idl", + "nsIStreamListener.idl", + "nsIStreamListenerTee.idl", + "nsIStreamLoader.idl", + "nsIStreamTransportService.idl", + "nsISyncStreamListener.idl", + "nsISystemProxySettings.idl", + "nsIThreadRetargetableRequest.idl", + "nsIThreadRetargetableStreamListener.idl", + "nsIThrottledInputChannel.idl", + "nsITimedChannel.idl", + "nsITLSServerSocket.idl", + "nsITraceableChannel.idl", + "nsITransport.idl", + "nsIUDPSocket.idl", + "nsIUploadChannel.idl", + "nsIUploadChannel2.idl", + "nsIURI.idl", + "nsIURIMutator.idl", + "nsIURIWithSpecialOrigin.idl", + "nsIURL.idl", + "nsIURLParser.idl", + "nsPISocketTransportService.idl", +] + +XPIDL_MODULE = "necko" + +EXPORTS += [ + "netCore.h", + "nsASocketHandler.h", + "nsAsyncRedirectVerifyHelper.h", + "nsBaseChannel.h", + "nsFileStreams.h", + "nsInputStreamPump.h", + "nsMIMEInputStream.h", + "nsNetUtil.h", + "nsReadLine.h", + "nsSerializationHelper.h", + "nsSimpleNestedURI.h", + "nsSimpleURI.h", + "nsStandardURL.h", + "nsStreamListenerWrapper.h", + "nsURIHashKey.h", + "nsURLHelper.h", + "nsURLParsers.h", + "SimpleChannel.h", +] + +EXPORTS.mozilla += [ + "LoadContextInfo.h", + "LoadInfo.h", + "LoadTainting.h", + "nsRedirectHistoryEntry.h", +] + +EXPORTS.mozilla.net += [ + "CacheInfoIPCTypes.h", + "CaptivePortalService.h", + "Dashboard.h", + "DashboardTypes.h", + "DefaultURI.h", + "InterceptionInfo.h", + "IOActivityMonitor.h", + "MemoryDownloader.h", + "NetworkConnectivityService.h", + "Predictor.h", + "PrivateBrowsingChannel.h", + "ProtocolHandlerInfo.h", + "RedirectChannelRegistrar.h", + "RequestContextService.h", + "SimpleChannelParent.h", + "SSLTokensCache.h", + "ThrottleQueue.h", +] + +UNIFIED_SOURCES += [ + "ArrayBufferInputStream.cpp", + "BackgroundFileSaver.cpp", + "CaptivePortalService.cpp", + "Dashboard.cpp", + "DefaultURI.cpp", + "EventTokenBucket.cpp", + "InterceptionInfo.cpp", + "IOActivityMonitor.cpp", + "LoadContextInfo.cpp", + "LoadInfo.cpp", + "MemoryDownloader.cpp", + "NetworkConnectivityService.cpp", + "NetworkDataCountLayer.cpp", + "nsAsyncRedirectVerifyHelper.cpp", + "nsAsyncStreamCopier.cpp", + "nsAuthInformationHolder.cpp", + "nsBase64Encoder.cpp", + "nsBaseChannel.cpp", + "nsBaseContentStream.cpp", + "nsBufferedStreams.cpp", + "nsDirectoryIndexStream.cpp", + "nsDNSPrefetch.cpp", + "nsDownloader.cpp", + "nsFileStreams.cpp", + "nsIncrementalDownload.cpp", + "nsIncrementalStreamLoader.cpp", + "nsInputStreamChannel.cpp", + "nsInputStreamPump.cpp", + "nsIOService.cpp", + "nsIURIMutatorUtils.cpp", + "nsLoadGroup.cpp", + "nsMIMEInputStream.cpp", + "nsNetAddr.cpp", + "nsNetUtil.cpp", + "nsPACMan.cpp", + "nsPreloadedStream.cpp", + "nsProtocolProxyService.cpp", + "nsProxyInfo.cpp", + "nsRedirectHistoryEntry.cpp", + "nsRequestObserverProxy.cpp", + "nsSerializationHelper.cpp", + "nsServerSocket.cpp", + "nsSimpleNestedURI.cpp", + "nsSimpleStreamListener.cpp", + "nsSimpleURI.cpp", + "nsSocketTransport2.cpp", + "nsSocketTransportService2.cpp", + "nsStandardURL.cpp", + "nsStreamListenerTee.cpp", + "nsStreamListenerWrapper.cpp", + "nsStreamLoader.cpp", + "nsStreamTransportService.cpp", + "nsSyncStreamListener.cpp", + "nsTransportUtils.cpp", + "nsUDPSocket.cpp", + "PollableEvent.cpp", + "Predictor.cpp", + "ProtocolHandlerInfo.cpp", + "ProxyAutoConfig.cpp", + "RedirectChannelRegistrar.cpp", + "RequestContextService.cpp", + "SimpleBuffer.cpp", + "SimpleChannel.cpp", + "SimpleChannelParent.cpp", + "SSLTokensCache.cpp", + "ThrottleQueue.cpp", + "Tickler.cpp", + "TLSServerSocket.cpp", + "TRRLoadInfo.cpp", +] + +if CONFIG["FUZZING"]: + SOURCES += [ + "FuzzyLayer.cpp", + "FuzzySecurityInfo.cpp", + "FuzzySocketControl.cpp", + ] + +if CONFIG["FUZZING_INTERFACES"] and CONFIG["LIBFUZZER"]: + include("/tools/fuzzing/libfuzzer-flags.mozbuild") + SOURCES += [ + "nsMediaFragmentURIParser.cpp", + "nsURLHelper.cpp", + "nsURLParsers.cpp", + ] + SOURCES["nsMediaFragmentURIParser.cpp"].flags += libfuzzer_flags + SOURCES["nsURLHelper.cpp"].flags += libfuzzer_flags + SOURCES["nsURLParsers.cpp"].flags += libfuzzer_flags +else: + UNIFIED_SOURCES += [ + "nsMediaFragmentURIParser.cpp", + "nsURLHelper.cpp", + "nsURLParsers.cpp", + ] + + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + SOURCES += [ + "nsURLHelperWin.cpp", + "ShutdownLayer.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + SOURCES += [ + "nsURLHelperOSX.cpp", + ] +else: + SOURCES += [ + "nsURLHelperUnix.cpp", + ] + +EXTRA_JS_MODULES += [ + "NetUtil.sys.mjs", +] + +DIRS += ["mozurl", "rust-helper", "http-sfv"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "!/xpcom/components", + "/docshell/base", + "/dom/base", + "/js/xpconnect/src", + "/netwerk/dns", + "/netwerk/protocol/http", + "/netwerk/protocol/webtransport", + "/netwerk/socket", + "/netwerk/url-classifier", + "/security/manager/ssl", + "/xpcom/components", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + LOCAL_INCLUDES += [ + "/xpcom/base", + ] + +GeneratedFile( + "ascii_pac_utils.inc", script="makecppstring.py", inputs=["ascii_pac_utils.js"] +) diff --git a/netwerk/base/mozIThirdPartyUtil.idl b/netwerk/base/mozIThirdPartyUtil.idl new file mode 100644 index 0000000000..0c029942d8 --- /dev/null +++ b/netwerk/base/mozIThirdPartyUtil.idl @@ -0,0 +1,231 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; +interface mozIDOMWindowProxy; +interface nsIChannel; +interface nsIPrincipal; +interface nsILoadInfo; + +%{C++ + +#include "mozilla/EnumSet.h" + +enum class ThirdPartyAnalysis { + IsForeign, + IsThirdPartyTrackingResource, + IsThirdPartySocialTrackingResource, + IsStorageAccessPermissionGranted, +}; + +using ThirdPartyAnalysisResult = mozilla::EnumSet<ThirdPartyAnalysis>; + +typedef bool (*RequireThirdPartyCheck)(nsILoadInfo*); + +%} + +native ThirdPartyAnalysisResult(ThirdPartyAnalysisResult); +native RequireThirdPartyCheck(RequireThirdPartyCheck); + +/** + * Utility functions for determining whether a given URI, channel, or window + * hierarchy is third party with respect to a known URI. + */ +[scriptable, builtinclass, uuid(fd82700e-ffb4-4932-b7d6-08f0b5697dda)] +interface mozIThirdPartyUtil : nsISupports +{ + /** + * isThirdPartyURI + * + * Determine whether two URIs are third party with respect to each other. + * This is determined by computing the base domain for both URIs. If they can + * be determined, and the base domains match, the request is defined as first + * party. If it cannot be determined because one or both URIs do not have a + * base domain (for instance, in the case of IP addresses, host aliases such + * as 'localhost', or a file:// URI), an exact string comparison on host is + * performed. + * + * For example, the URI "http://mail.google.com/" is not third party with + * respect to "http://images.google.com/", but "http://mail.yahoo.com/" and + * "http://192.168.1.1/" are. + * + * @return true if aFirstURI is third party with respect to aSecondURI. + * + * @throws if either URI is null, has a malformed host, or has an empty host + * and is not a file:// URI. + */ + boolean isThirdPartyURI(in nsIURI aFirstURI, in nsIURI aSecondURI); + + /** + * isThirdPartyWindow + * + * Determine whether the given window hierarchy is third party. This is done + * as follows: + * + * 1) Obtain the URI of the principal associated with 'aWindow'. Call this the + * 'bottom URI'. + * 2) If 'aURI' is provided, determine if it is third party with respect to + * the bottom URI. If so, return. + * 3) Find the same-type parent window, if there is one, and its URI. + * Determine whether it is third party with respect to the bottom URI. If + * so, return. + * + * Therefore, each level in the window hierarchy is tested. (This means that + * nested iframes with different base domains, even though the bottommost and + * topmost URIs might be equal, will be considered third party.) + * + * @param aWindow + * The bottommost window in the hierarchy. + * @param aURI + * A URI to test against. If null, the URI of the principal + * associated with 'aWindow' will be used. + * + * For example, if 'aURI' is "http://mail.google.com/", 'aWindow' has a URI + * of "http://google.com/", and its parent is the topmost content window with + * a URI of "http://mozilla.com", the result will be true. + * + * @return true if 'aURI' is third party with respect to any of the URIs + * associated with aWindow and its same-type parents. + * + * @throws if aWindow is null; the same-type parent of any window in the + * hierarchy cannot be determined; or the URI associated with any + * window in the hierarchy is null, has a malformed host, or has an + * empty host and is not a file:// URI. + * + * @see isThirdPartyURI + */ + boolean isThirdPartyWindow(in mozIDOMWindowProxy aWindow, [optional] in nsIURI aURI); + + /** + * isThirdPartyChannel + * + * Determine whether the given channel and its content window hierarchy is + * third party. This is done as follows: + * + * 1) If 'aChannel' is an nsIHttpChannel and has the + * 'forceAllowThirdPartyCookie' property set, then: + * a) If 'aURI' is null, return false. + * b) Otherwise, find the URI of the channel, determine whether it is + * foreign with respect to 'aURI', and return. + * 2) Find the URI of the channel and determine whether it is third party with + * respect to the URI of the channel. If so, return. + * 3) Obtain the bottommost nsIDOMWindow, and its same-type parent if it + * exists, from the channel's notification callbacks. Then: + * a) If the parent is the same as the bottommost window, and the channel + * has the LOAD_DOCUMENT_URI flag set, return false. This represents the + * case where a toplevel load is occurring and the window's URI has not + * yet been updated. (We have already checked that 'aURI' is not foreign + * with respect to the channel URI.) + * b) Otherwise, return the result of isThirdPartyWindow with arguments + * of the channel's bottommost window and the channel URI, respectively. + * + * Therefore, both the channel's URI and each level in the window hierarchy + * associated with the channel is tested. + * + * @param aChannel + * The channel associated with the load. + * @param aURI + * A URI to test against. If null, the URI of the channel will be used. + * + * For example, if 'aURI' is "http://mail.google.com/", 'aChannel' has a URI + * of "http://google.com/", and its parent is the topmost content window with + * a URI of "http://mozilla.com", the result will be true. + * + * @return true if aURI is third party with respect to the channel URI or any + * of the URIs associated with the same-type window hierarchy of the + * channel. + * + * @throws if 'aChannel' is null; the channel has no notification callbacks or + * an associated window; or isThirdPartyWindow throws. + * + * @see isThirdPartyWindow + */ + boolean isThirdPartyChannel(in nsIChannel aChannel, [optional] in nsIURI aURI); + + /** + * getBaseDomain + * + * Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be + * "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing + * dot may be present. If aHostURI is an IP address, an alias such as + * 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will + * be the exact host. The result of this function should only be used in exact + * string comparisons, since substring comparisons will not be valid for the + * special cases elided above. + * + * @param aHostURI + * The URI to analyze. + * + * @return the base domain. + */ + AUTF8String getBaseDomain(in nsIURI aHostURI); + + /** + * NOTE: Long term, this method won't be needed once bug 922464 is fixed which + * will make it possible to parse all URI's off the main thread. + * + * getBaseDomainFromSchemeHost + * + * Get the base domain for aScheme and aHost. Otherwise identical to + * getBaseDomain(). + * + * @param aScheme + * The scheme to analyze. + * + * @param aAsciiHost + * The host to analyze. + * + * @return the base domain. + */ + AUTF8String getBaseDomainFromSchemeHost(in AUTF8String aScheme, + in AUTF8String aAsciiHost); + + /** + * getURIFromWindow + * + * Returns the URI associated with the script object principal for the + * window. + */ + nsIURI getURIFromWindow(in mozIDOMWindowProxy aWindow); + + /** + * getPrincipalFromWindow + * + * Returns the script object principal for the window. + */ + nsIPrincipal getPrincipalFromWindow(in mozIDOMWindowProxy aWindow); + + /** + * getTopWindowForChannel + * + * Returns the top-level window associated with the given channel. + */ + [noscript] + mozIDOMWindowProxy getTopWindowForChannel(in nsIChannel aChannel, [optional] in nsIURI aURIBeingLoaded); + + /* + * Performs a full analysis of a channel. + * + * aChannel the input channel + * aNotify whether to send content blocking notifications if access control checks fail + * aURI optional URI to check for (the channel URI will be used instead if not provided) + * aRequireThirdPartyCheck a functor used to determine whether the load info requires third-party checks + */ + [noscript, notxpcom] + ThirdPartyAnalysisResult analyzeChannel(in nsIChannel aChannel, + in boolean aNotify, + [optional] in nsIURI aURI, + [optional] in RequireThirdPartyCheck aRequireThirdPartyCheck, + out uint32_t aRejectedReason); +}; + +%{ C++ +/** + * The mozIThirdPartyUtil implementation is an XPCOM service registered + * under the ContractID: + */ +#define THIRDPARTYUTIL_CONTRACTID "@mozilla.org/thirdpartyutil;1" +%} diff --git a/netwerk/base/mozurl/.gitignore b/netwerk/base/mozurl/.gitignore new file mode 100644 index 0000000000..4fffb2f89c --- /dev/null +++ b/netwerk/base/mozurl/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/netwerk/base/mozurl/Cargo.toml b/netwerk/base/mozurl/Cargo.toml new file mode 100644 index 0000000000..1166bd2bfa --- /dev/null +++ b/netwerk/base/mozurl/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mozurl" +version = "0.0.1" +authors = ["Nika Layzell <nika@thelayzells.com>"] +license = "MPL-2.0" + +[dependencies] +url = "2.4" +nserror = { path = "../../../xpcom/rust/nserror" } +nsstring = { path = "../../../xpcom/rust/nsstring" } +xpcom = { path = "../../../xpcom/rust/xpcom" } +uuid = { version = "1.0", features = ["v4"] } diff --git a/netwerk/base/mozurl/MozURL.cpp b/netwerk/base/mozurl/MozURL.cpp new file mode 100644 index 0000000000..47a679164a --- /dev/null +++ b/netwerk/base/mozurl/MozURL.cpp @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/StaticPrefs_security.h" + +extern "C" { + +bool Gecko_StrictFileOriginPolicy() { + return mozilla::StaticPrefs::security_fileuri_strict_origin_policy(); +} +} diff --git a/netwerk/base/mozurl/MozURL.h b/netwerk/base/mozurl/MozURL.h new file mode 100644 index 0000000000..60e8c189ae --- /dev/null +++ b/netwerk/base/mozurl/MozURL.h @@ -0,0 +1,233 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozURL_h__ +#define mozURL_h__ + +#include "mozilla/net/MozURL_ffi.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Result.h" + +namespace mozilla { +namespace net { + +// This class provides a thread-safe, immutable URL parser. +// As long as there is RefPtr to the object, you may use it on any thread. +// The constructor is private. One can instantiate the object by +// calling the Init() method as such: +// +// RefPtr<MozURL> url; +// nsAutoCString href("http://example.com/path?query#ref"); +// nsresult rv = MozURL::Init(getter_AddRefs(url), href); +// if (NS_SUCCEEDED(rv)) { /* use url */ } +// +// When changing the URL is needed, you need to call the Mutate() method. +// This gives you a Mutator object, on which you can perform setter operations. +// Calling Finalize() on the Mutator will result in a new MozURL and a status +// code. If any of the setter operations failed, it will be reflected in the +// status code, and a null MozURL. +// +// Note: In the case of a domain name containing non-ascii characters, +// GetSpec and GetHostname will return the IDNA(punycode) version of the host. +// Also note that for now, MozURL only supports the UTF-8 charset. +// +// Implementor Note: This type is only a holder for methods in C++, and does not +// reflect the actual layout of the type. +class MozURL final { + public: + static nsresult Init(MozURL** aURL, const nsACString& aSpec, + const MozURL* aBaseURL = nullptr) { + return mozurl_new(aURL, &aSpec, aBaseURL); + } + + nsDependentCSubstring Spec() const { return mozurl_spec(this); } + nsDependentCSubstring Scheme() const { return mozurl_scheme(this); } + nsDependentCSubstring Username() const { return mozurl_username(this); } + nsDependentCSubstring Password() const { return mozurl_password(this); } + // Will return the hostname of URL. If the hostname is an IPv6 address, + // it will be enclosed in square brackets, such as `[::1]` + nsDependentCSubstring Host() const { return mozurl_host(this); } + // Will return the port number, if specified, or -1 + int32_t Port() const { return mozurl_port(this); } + int32_t RealPort() const { return mozurl_real_port(this); } + // If the URL's port number is equal to the default port, will only return the + // hostname, otherwise it will return a string of the form `{host}:{port}` + // See: https://url.spec.whatwg.org/#default-port + nsDependentCSubstring HostPort() const { return mozurl_host_port(this); } + nsDependentCSubstring FilePath() const { return mozurl_filepath(this); } + nsDependentCSubstring Path() const { return mozurl_path(this); } + nsDependentCSubstring Query() const { return mozurl_query(this); } + bool HasQuery() const { return mozurl_has_query(this); } + nsDependentCSubstring Ref() const { return mozurl_fragment(this); } + bool HasFragment() const { return mozurl_has_fragment(this); } + nsDependentCSubstring Directory() const { return mozurl_directory(this); } + nsDependentCSubstring PrePath() const { return mozurl_prepath(this); } + nsDependentCSubstring SpecNoRef() const { return mozurl_spec_no_ref(this); } + + // This matches the definition of origins and base domains in nsIPrincipal for + // almost all URIs (some rare file:// URIs don't match and it would be hard to + // fix them). It definitely matches nsIPrincipal for URIs used in quota + // manager and there are checks in quota manager and its clients that prevent + // different definitions (see QuotaManager::IsPrincipalInfoValid). + // See also TestMozURL.cpp which enumerates a huge pile of URIs and checks + // that origin and base domain definitions are in sync. + void Origin(nsACString& aOrigin) const { mozurl_origin(this, &aOrigin); } + nsresult BaseDomain(nsACString& aBaseDomain) const { + return mozurl_base_domain(this, &aBaseDomain); + } + bool CannotBeABase() { return mozurl_cannot_be_a_base(this); } + + nsresult GetCommonBase(const MozURL* aOther, MozURL** aCommon) const { + return mozurl_common_base(this, aOther, aCommon); + } + nsresult GetRelative(const MozURL* aOther, nsACString* aRelative) const { + return mozurl_relative(this, aOther, aRelative); + } + + size_t SizeOf() { return mozurl_sizeof(this); } + + class Mutator { + public: + // Calling this method will result in the creation of a new MozURL that + // adopts the mutator's mURL. + // If any of the setters failed with an error code, that error code will be + // returned here. It will also return an error code if Finalize is called + // more than once on the Mutator. + nsresult Finalize(MozURL** aURL) { + nsresult rv = GetStatus(); + if (NS_SUCCEEDED(rv)) { + mURL.forget(aURL); + } else { + *aURL = nullptr; + } + return rv; + } + + // These setter methods will return a reference to `this` so that you may + // chain setter operations as such: + // + // RefPtr<MozURL> url2; + // nsresult rv = url->Mutate().SetHostname("newhost"_ns) + // .SetFilePath("new/file/path"_ns) + // .Finalize(getter_AddRefs(url2)); + // if (NS_SUCCEEDED(rv)) { /* use url2 */ } + Mutator& SetScheme(const nsACString& aScheme) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_scheme(mURL, &aScheme); + } + return *this; + } + Mutator& SetUsername(const nsACString& aUser) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_username(mURL, &aUser); + } + return *this; + } + Mutator& SetPassword(const nsACString& aPassword) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_password(mURL, &aPassword); + } + return *this; + } + Mutator& SetHostname(const nsACString& aHost) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_hostname(mURL, &aHost); + } + return *this; + } + Mutator& SetHostPort(const nsACString& aHostPort) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_host_port(mURL, &aHostPort); + } + return *this; + } + Mutator& SetFilePath(const nsACString& aPath) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_pathname(mURL, &aPath); + } + return *this; + } + Mutator& SetQuery(const nsACString& aQuery) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_query(mURL, &aQuery); + } + return *this; + } + Mutator& SetRef(const nsACString& aRef) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_fragment(mURL, &aRef); + } + return *this; + } + Mutator& SetPort(int32_t aPort) { + if (NS_SUCCEEDED(GetStatus())) { + mStatus = mozurl_set_port_no(mURL, aPort); + } + return *this; + } + + // This method returns the status code of the setter operations. + // If any of the setters failed, it will return the code of the first error + // that occured. If none of the setters failed, it will return NS_OK. + // This method is useful to avoid doing expensive operations when the result + // would not be used because an error occurred. For example: + // + // RefPtr<MozURL> url2; + // MozURL::Mutator mut = url->Mutate(); + // mut.SetScheme("!@#$"); // this would fail + // if (NS_SUCCEDED(mut.GetStatus())) { + // nsAutoCString host(ExpensiveComputing()); + // rv = mut.SetHostname(host).Finalize(getter_AddRefs(url2)); + // } + // if (NS_SUCCEEDED(rv)) { /* use url2 */ } + nsresult GetStatus() { return mURL ? mStatus : NS_ERROR_NOT_AVAILABLE; } + + static Result<Mutator, nsresult> FromSpec( + const nsACString& aSpec, const MozURL* aBaseURL = nullptr) { + Mutator m = Mutator(aSpec, aBaseURL); + if (m.mURL) { + MOZ_ASSERT(NS_SUCCEEDED(m.mStatus)); + return m; + } + + MOZ_ASSERT(NS_FAILED(m.mStatus)); + return Err(m.mStatus); + } + + private: + explicit Mutator(MozURL* aUrl) : mStatus(NS_OK) { + mozurl_clone(aUrl, getter_AddRefs(mURL)); + } + + // This is only used to create a mutator from a string without cloning it + // so we avoid a pointless copy in FromSpec. It is important that we + // check the value of mURL afterwards. + explicit Mutator(const nsACString& aSpec, + const MozURL* aBaseURL = nullptr) { + mStatus = mozurl_new(getter_AddRefs(mURL), &aSpec, aBaseURL); + } + RefPtr<MozURL> mURL; + nsresult mStatus; + friend class MozURL; + }; + + Mutator Mutate() { return Mutator(this); } + + // AddRef and Release are non-virtual on this type, and always call into rust. + void AddRef() { mozurl_addref(this); } + void Release() { mozurl_release(this); } + + private: + // Make it a compile time error for C++ code to ever create, destruct, or copy + // MozURL objects. All of these operations will be performed by rust. + MozURL(); /* never defined */ + ~MozURL(); /* never defined */ + MozURL(const MozURL&) = delete; + MozURL& operator=(const MozURL&) = delete; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozURL_h__ diff --git a/netwerk/base/mozurl/cbindgen.toml b/netwerk/base/mozurl/cbindgen.toml new file mode 100644 index 0000000000..bb5fdbd08f --- /dev/null +++ b/netwerk/base/mozurl/cbindgen.toml @@ -0,0 +1,61 @@ +header = """/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* The MozURL type is implemented in Rust code, and uses extern "C" FFI calls to + * operate on the internal data structure. This file contains C declarations of + * these files. + + * WARNING: DO NOT CALL ANY OF THESE FUNCTIONS. USE |MozURL| INSTEAD! */ + """ +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */ + +namespace mozilla { +namespace net { +class MozURL; +} // namespace net +} // namespace mozilla + +extern "C" { + +// FFI-compatible string slice struct used internally by MozURL. +// Coerces to nsDependentCSubstring. +struct MozURLSpecSlice { + char* mData; + uint32_t mLen; + + operator nsDependentCSubstring() { + return nsDependentCSubstring(mData, mLen); + } +}; + +nsresult mozurl_new(mozilla::net::MozURL** aResult, const nsACString* aSpec, + /* optional */ const mozilla::net::MozURL* aBase); + +void mozurl_clone(const mozilla::net::MozURL* aThis, + mozilla::net::MozURL** aResult); + +nsresult mozurl_common_base(const mozilla::net::MozURL* aUrl1, + const mozilla::net::MozURL* aUrl2, + mozilla::net::MozURL** aResult); +} + +""" + +include_guard = "mozilla_net_MozURL_ffi_h" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" +namespaces = [] +includes = ["nsError.h", "nsString.h"] + +[export] +exclude = ["Gecko_StrictFileOriginPolicy", "MozURL", "SpecSlice", "mozurl_new", "mozurl_clone", "mozurl_common_base"] +item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"] + + +[export.rename] +"SpecSlice" = "MozURLSpecSlice" +"MozURL" = "mozilla::net::MozURL" diff --git a/netwerk/base/mozurl/moz.build b/netwerk/base/mozurl/moz.build new file mode 100644 index 0000000000..0a4e522b5a --- /dev/null +++ b/netwerk/base/mozurl/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS.mozilla.net += [ + "MozURL.h", +] + +SOURCES += [ + "MozURL.cpp", +] + +FINAL_LIBRARY = "xul" + +if CONFIG["COMPILE_ENVIRONMENT"]: + CbindgenHeader("MozURL_ffi.h", inputs=["/netwerk/base/mozurl"]) + + EXPORTS.mozilla.net += [ + "!MozURL_ffi.h", + ] diff --git a/netwerk/base/mozurl/src/lib.rs b/netwerk/base/mozurl/src/lib.rs new file mode 100644 index 0000000000..21be5f7e8b --- /dev/null +++ b/netwerk/base/mozurl/src/lib.rs @@ -0,0 +1,574 @@ +/* -*- Mode: rust; rust-indent-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate url; +use url::quirks; +use url::{ParseOptions, Position, Url}; + +extern crate nsstring; +use nsstring::{nsACString, nsCString}; + +extern crate nserror; +use nserror::*; + +extern crate xpcom; +use xpcom::interfaces::mozIThirdPartyUtil; +use xpcom::{AtomicRefcnt, RefCounted, RefPtr}; + +extern crate uuid; +use uuid::Uuid; + +use std::fmt::Write; +use std::marker::PhantomData; +use std::mem; +use std::ops; +use std::ptr; +use std::str; + +extern "C" { + fn Gecko_StrictFileOriginPolicy() -> bool; +} + +/// Helper macro. If the expression $e is Ok(t) evaluates to t, otherwise, +/// returns NS_ERROR_MALFORMED_URI. +macro_rules! try_or_malformed { + ($e:expr) => { + match $e { + Ok(v) => v, + Err(_) => return NS_ERROR_MALFORMED_URI, + } + }; +} + +fn parser<'a>() -> ParseOptions<'a> { + Url::options() +} + +fn default_port(scheme: &str) -> Option<u16> { + match scheme { + "ftp" => Some(21), + "gopher" => Some(70), + "http" => Some(80), + "https" => Some(443), + "ws" => Some(80), + "wss" => Some(443), + "rtsp" => Some(443), + "android" => Some(443), + _ => None, + } +} + +/// A slice into the backing string. This type is only valid as long as the +/// MozURL which it was pulled from is valid. In C++, this type implicitly +/// converts to a nsDependentCString, and is an implementation detail. +/// +/// This type exists because, unlike &str, this type is safe to return over FFI. +#[repr(C)] +pub struct SpecSlice<'a> { + data: *const u8, + len: u32, + _marker: PhantomData<&'a [u8]>, +} + +impl<'a> From<&'a str> for SpecSlice<'a> { + fn from(s: &'a str) -> SpecSlice<'a> { + assert!(s.len() < u32::max_value() as usize); + SpecSlice { + data: s.as_ptr(), + len: s.len() as u32, + _marker: PhantomData, + } + } +} + +/// The MozURL reference-counted threadsafe URL type. This type intentionally +/// implements no XPCOM interfaces, and all method calls are non-virtual. +#[repr(C)] +pub struct MozURL { + pub url: Url, + refcnt: AtomicRefcnt, +} + +impl MozURL { + pub fn from_url(url: Url) -> RefPtr<MozURL> { + // Actually allocate the URL on the heap. This is the only place we actually + // create a MozURL, other than in clone(). + unsafe { + RefPtr::from_raw(Box::into_raw(Box::new(MozURL { + url: url, + refcnt: AtomicRefcnt::new(), + }))) + .unwrap() + } + } +} + +impl ops::Deref for MozURL { + type Target = Url; + fn deref(&self) -> &Url { + &self.url + } +} +impl ops::DerefMut for MozURL { + fn deref_mut(&mut self) -> &mut Url { + &mut self.url + } +} + +// Memory Management for MozURL +#[no_mangle] +pub unsafe extern "C" fn mozurl_addref(url: &MozURL) { + url.refcnt.inc(); +} + +#[no_mangle] +pub unsafe extern "C" fn mozurl_release(url: &MozURL) { + let rc = url.refcnt.dec(); + if rc == 0 { + mem::drop(Box::from_raw(url as *const MozURL as *mut MozURL)); + } +} + +// xpcom::RefPtr support +unsafe impl RefCounted for MozURL { + unsafe fn addref(&self) { + mozurl_addref(self); + } + unsafe fn release(&self) { + mozurl_release(self); + } +} + +// Allocate a new MozURL object with a RefCnt of 1, and store a pointer to it +// into url. +#[no_mangle] +pub extern "C" fn mozurl_new( + result: &mut *const MozURL, + spec: &nsACString, + base: Option<&MozURL>, +) -> nsresult { + *result = ptr::null_mut(); + + let spec = try_or_malformed!(str::from_utf8(spec)); + let url = if let Some(base) = base { + try_or_malformed!(base.url.join(spec)) + } else { + try_or_malformed!(parser().parse(spec)) + }; + + MozURL::from_url(url).forget(result); + NS_OK +} + +/// Allocate a new MozURL object which is a clone of the original, and store a +/// pointer to it into newurl. +#[no_mangle] +pub extern "C" fn mozurl_clone(url: &MozURL, newurl: &mut *const MozURL) { + MozURL::from_url(url.url.clone()).forget(newurl); +} + +#[no_mangle] +pub extern "C" fn mozurl_spec(url: &MozURL) -> SpecSlice { + url.as_ref().into() +} + +#[no_mangle] +pub extern "C" fn mozurl_scheme(url: &MozURL) -> SpecSlice { + url.scheme().into() +} + +#[no_mangle] +pub extern "C" fn mozurl_username(url: &MozURL) -> SpecSlice { + if url.cannot_be_a_base() { + "".into() + } else { + url.username().into() + } +} + +#[no_mangle] +pub extern "C" fn mozurl_password(url: &MozURL) -> SpecSlice { + url.password().unwrap_or("").into() +} + +#[no_mangle] +pub extern "C" fn mozurl_host(url: &MozURL) -> SpecSlice { + url.host_str().unwrap_or("").into() +} + +#[no_mangle] +pub extern "C" fn mozurl_port(url: &MozURL) -> i32 { + // NOTE: Gecko uses -1 to represent the default port. + url.port().map(|p| p as i32).unwrap_or(-1) +} + +#[no_mangle] +pub extern "C" fn mozurl_real_port(url: &MozURL) -> i32 { + url.port() + .or_else(|| default_port(url.scheme())) + .map(|p| p as i32) + .unwrap_or(-1) +} + +#[no_mangle] +pub extern "C" fn mozurl_host_port(url: &MozURL) -> SpecSlice { + (&url[Position::BeforeHost..Position::BeforePath]).into() +} + +#[no_mangle] +pub extern "C" fn mozurl_filepath(url: &MozURL) -> SpecSlice { + url.path().into() +} + +#[no_mangle] +pub extern "C" fn mozurl_path(url: &MozURL) -> SpecSlice { + (&url[Position::BeforePath..]).into() +} + +#[no_mangle] +pub extern "C" fn mozurl_query(url: &MozURL) -> SpecSlice { + url.query().unwrap_or("").into() +} + +#[no_mangle] +pub extern "C" fn mozurl_fragment(url: &MozURL) -> SpecSlice { + url.fragment().unwrap_or("").into() +} + +#[no_mangle] +pub extern "C" fn mozurl_spec_no_ref(url: &MozURL) -> SpecSlice { + (&url[..Position::AfterQuery]).into() +} + +#[no_mangle] +pub extern "C" fn mozurl_has_fragment(url: &MozURL) -> bool { + url.fragment().is_some() +} + +#[no_mangle] +pub extern "C" fn mozurl_has_query(url: &MozURL) -> bool { + url.query().is_some() +} + +#[no_mangle] +pub extern "C" fn mozurl_directory(url: &MozURL) -> SpecSlice { + if let Some(position) = url.path().rfind('/') { + url.path()[..position + 1].into() + } else { + url.path().into() + } +} + +#[no_mangle] +pub extern "C" fn mozurl_prepath(url: &MozURL) -> SpecSlice { + (&url[..Position::BeforePath]).into() +} + +fn get_origin(url: &MozURL) -> Option<String> { + match url.scheme() { + "blob" | "ftp" | "http" | "https" | "ws" | "wss" => { + Some(url.origin().ascii_serialization()) + } + "indexeddb" | "moz-extension" | "resource" => { + let host = url.host_str().unwrap_or(""); + + let port = url.port().or_else(|| default_port(url.scheme())); + + if port == default_port(url.scheme()) { + Some(format!("{}://{}", url.scheme(), host)) + } else { + Some(format!("{}://{}:{}", url.scheme(), host, port.unwrap())) + } + } + "file" => { + if unsafe { Gecko_StrictFileOriginPolicy() } { + Some(url[..Position::AfterPath].to_owned()) + } else { + Some("file://UNIVERSAL_FILE_URI_ORIGIN".to_owned()) + } + } + "about" | "moz-safe-about" => Some(url[..Position::AfterPath].to_owned()), + _ => None, + } +} + +#[no_mangle] +pub extern "C" fn mozurl_origin(url: &MozURL, origin: &mut nsACString) { + let origin_str = if !url.as_ref().starts_with("about:blank") { + get_origin(url) + } else { + None + }; + + let origin_str = origin_str.unwrap_or_else(|| { + // nsIPrincipal stores the uuid, so the same uuid is returned everytime. + // We can't do that for MozURL because it can be used across threads. + // Storing uuid would mutate the object which would cause races between + // threads. + format!("moz-nullprincipal:{{{}}}", Uuid::new_v4()) + }); + + // NOTE: Try to re-use the allocation we got from rust-url, and transfer + // ownership of the buffer to C++. + let mut o = nsCString::from(origin_str); + origin.take_from(&mut o); +} + +fn get_base_domain(url: &MozURL) -> Result<Option<String>, nsresult> { + match url.scheme() { + "ftp" | "http" | "https" | "moz-extension" | "resource" => { + let third_party_util: RefPtr<mozIThirdPartyUtil> = + xpcom::components::ThirdPartyUtil::service() + .map_err(|_| NS_ERROR_ILLEGAL_DURING_SHUTDOWN)?; + + let scheme = nsCString::from(url.scheme()); + + let mut host_str = url.host_str().unwrap_or(""); + + if host_str.starts_with('[') && host_str.ends_with(']') { + host_str = &host_str[1..host_str.len() - 1]; + } + + let host = nsCString::from(host_str); + + unsafe { + let mut string = nsCString::new(); + third_party_util + .GetBaseDomainFromSchemeHost(&*scheme, &*host, &mut *string) + .to_result()?; + + // We know that GetBaseDomainFromSchemeHost returns AUTF8String, so just + // use unwrap(). + Ok(Some(String::from_utf8(string.to_vec()).unwrap())) + } + } + "ws" | "wss" => Ok(Some(url.as_ref().to_owned())), + "file" => { + if unsafe { Gecko_StrictFileOriginPolicy() } { + Ok(Some(url.path().to_owned())) + } else { + Ok(Some("UNIVERSAL_FILE_URI_ORIGIN".to_owned())) + } + } + "about" | "moz-safe-about" | "indexeddb" => Ok(Some(url.as_ref().to_owned())), + _ => Ok(None), + } +} + +#[no_mangle] +pub extern "C" fn mozurl_base_domain(url: &MozURL, base_domain: &mut nsACString) -> nsresult { + let base_domain_str = if !url.as_ref().starts_with("about:blank") { + match get_base_domain(url) { + Ok(domain) => domain, + Err(rv) => return rv, + } + } else { + None + }; + + let base_domain_str = base_domain_str.unwrap_or_else(|| { + // See the comment in mozurl_origin about why we return a new uuid for + // "moz-nullprincipals". + format!("{{{}}}", Uuid::new_v4()) + }); + + let mut bd = nsCString::from(base_domain_str); + base_domain.take_from(&mut bd); + + NS_OK +} + +// Helper macro for debug asserting that we're the only reference to MozURL. +macro_rules! debug_assert_mut { + ($e:expr) => { + debug_assert_eq!($e.refcnt.get(), 1, "Cannot mutate an aliased MozURL!"); + }; +} + +#[no_mangle] +pub extern "C" fn mozurl_cannot_be_a_base(url: &mut MozURL) -> bool { + debug_assert_mut!(url); + url.cannot_be_a_base() +} + +#[no_mangle] +pub extern "C" fn mozurl_set_scheme(url: &mut MozURL, scheme: &nsACString) -> nsresult { + debug_assert_mut!(url); + let scheme = try_or_malformed!(str::from_utf8(scheme)); + try_or_malformed!(quirks::set_protocol(url, scheme)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_username(url: &mut MozURL, username: &nsACString) -> nsresult { + debug_assert_mut!(url); + let username = try_or_malformed!(str::from_utf8(username)); + try_or_malformed!(quirks::set_username(url, username)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_password(url: &mut MozURL, password: &nsACString) -> nsresult { + debug_assert_mut!(url); + let password = try_or_malformed!(str::from_utf8(password)); + try_or_malformed!(quirks::set_password(url, password)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_host_port(url: &mut MozURL, hostport: &nsACString) -> nsresult { + debug_assert_mut!(url); + let hostport = try_or_malformed!(str::from_utf8(hostport)); + try_or_malformed!(quirks::set_host(url, hostport)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_hostname(url: &mut MozURL, host: &nsACString) -> nsresult { + debug_assert_mut!(url); + let host = try_or_malformed!(str::from_utf8(host)); + try_or_malformed!(quirks::set_hostname(url, host)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_port_no(url: &mut MozURL, new_port: i32) -> nsresult { + debug_assert_mut!(url); + if url.cannot_be_a_base() { + return NS_ERROR_MALFORMED_URI; + } + + let port = match new_port { + new if new < 0 || u16::max_value() as i32 <= new => None, + new if Some(new as u16) == default_port(url.scheme()) => None, + new => Some(new as u16), + }; + try_or_malformed!(url.set_port(port)); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_pathname(url: &mut MozURL, path: &nsACString) -> nsresult { + debug_assert_mut!(url); + let path = try_or_malformed!(str::from_utf8(path)); + quirks::set_pathname(url, path); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_query(url: &mut MozURL, query: &nsACString) -> nsresult { + debug_assert_mut!(url); + let query = try_or_malformed!(str::from_utf8(query)); + quirks::set_search(url, query); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_set_fragment(url: &mut MozURL, fragment: &nsACString) -> nsresult { + debug_assert_mut!(url); + let fragment = try_or_malformed!(str::from_utf8(fragment)); + quirks::set_hash(url, fragment); + NS_OK +} + +#[no_mangle] +pub extern "C" fn mozurl_sizeof(url: &MozURL) -> usize { + debug_assert_mut!(url); + mem::size_of::<MozURL>() + url.as_str().len() +} + +#[no_mangle] +pub extern "C" fn mozurl_common_base( + url1: &MozURL, + url2: &MozURL, + result: &mut *const MozURL, +) -> nsresult { + *result = ptr::null(); + if url1.url == url2.url { + RefPtr::new(url1).forget(result); + return NS_OK; + } + + if url1.scheme() != url2.scheme() + || url1.host() != url2.host() + || url1.username() != url2.username() + || url1.password() != url2.password() + || url1.port() != url2.port() + { + return NS_OK; + } + + match (url1.path_segments(), url2.path_segments()) { + (Some(path1), Some(path2)) => { + // Take the shared prefix of path segments + let mut url = url1.url.clone(); + if let Ok(mut segs) = url.path_segments_mut() { + segs.clear(); + segs.extend(path1.zip(path2).take_while(|&(a, b)| a == b).map(|p| p.0)); + } else { + return NS_OK; + } + + MozURL::from_url(url).forget(result); + NS_OK + } + _ => NS_OK, + } +} + +#[no_mangle] +pub extern "C" fn mozurl_relative( + url1: &MozURL, + url2: &MozURL, + result: &mut nsACString, +) -> nsresult { + if url1.url == url2.url { + result.truncate(); + return NS_OK; + } + + if url1.scheme() != url2.scheme() + || url1.host() != url2.host() + || url1.username() != url2.username() + || url1.password() != url2.password() + || url1.port() != url2.port() + { + result.assign(url2.as_ref()); + return NS_OK; + } + + match (url1.path_segments(), url2.path_segments()) { + (Some(mut path1), Some(mut path2)) => { + // Exhaust the part of the iterators that match + while let (Some(ref p1), Some(ref p2)) = (path1.next(), path2.next()) { + if p1 != p2 { + break; + } + } + + result.truncate(); + for _ in path1 { + result.append("../"); + } + for p2 in path2 { + result.append(p2); + result.append("/"); + } + } + _ => { + result.assign(url2.as_ref()); + } + } + NS_OK +} + +/// This type is used by nsStandardURL +#[no_mangle] +pub extern "C" fn rusturl_parse_ipv6addr(input: &nsACString, addr: &mut nsACString) -> nsresult { + let ip6 = try_or_malformed!(str::from_utf8(input)); + let host = try_or_malformed!(url::Host::parse(ip6)); + let _ = write!(addr, "{}", host); + NS_OK +} diff --git a/netwerk/base/netCore.h b/netwerk/base/netCore.h new file mode 100644 index 0000000000..258cc5cb58 --- /dev/null +++ b/netwerk/base/netCore.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __netCore_h__ +#define __netCore_h__ + +#include "nsError.h" + +// Where most necko status messages come from: +#define NECKO_MSGS_URL "chrome://necko/locale/necko.properties" + +#endif // __netCore_h__ diff --git a/netwerk/base/nsASocketHandler.h b/netwerk/base/nsASocketHandler.h new file mode 100644 index 0000000000..c68da7c359 --- /dev/null +++ b/netwerk/base/nsASocketHandler.h @@ -0,0 +1,102 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsASocketHandler_h__ +#define nsASocketHandler_h__ + +#include "nsError.h" +#include "nsINetAddr.h" +#include "nsISupports.h" +#include "prio.h" + +// socket handler used by nsISocketTransportService. +// methods are only called on the socket thread. + +class nsASocketHandler : public nsISupports { + public: + nsASocketHandler() = default; + + // + // this condition variable will be checked to determine if the socket + // handler should be detached. it must only be accessed on the socket + // thread. + // + nsresult mCondition{NS_OK}; + + // + // these flags can only be modified on the socket transport thread. + // the socket transport service will check these flags before calling + // PR_Poll. + // + uint16_t mPollFlags{0}; + + // + // this value specifies the maximum amount of time in seconds that may be + // spent waiting for activity on this socket. if this timeout is reached, + // then OnSocketReady will be called with outFlags = -1. + // + // the default value for this member is UINT16_MAX, which disables the + // timeout error checking. (i.e., a timeout value of UINT16_MAX is + // never reached.) + // + uint16_t mPollTimeout{UINT16_MAX}; + + bool mIsPrivate{false}; + + // + // called to service a socket + // + // params: + // socketRef - socket identifier + // fd - socket file descriptor + // outFlags - value of PR_PollDesc::out_flags after PR_Poll returns + // or -1 if a timeout occurred + // + virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) = 0; + + // + // called when a socket is no longer under the control of the socket + // transport service. the socket handler may close the socket at this + // point. after this call returns, the handler will no longer be owned + // by the socket transport service. + // + virtual void OnSocketDetached(PRFileDesc* fd) = 0; + + // + // called to determine if the socket is for a local peer. + // when used for server sockets, indicates if it only accepts local + // connections. + // + virtual void IsLocal(bool* aIsLocal) = 0; + + // + // called to determine if this socket should not be terminated when Gecko + // is turned offline. This is mostly useful for the debugging server + // socket. + // + virtual void KeepWhenOffline(bool* aKeepWhenOffline) { + *aKeepWhenOffline = false; + } + + // + // called when global pref for keepalive has changed. + // + virtual void OnKeepaliveEnabledPrefChange(bool aEnabled) {} + + // + // called to determine the NetAddr. addr can only be assumed to be initialized + // when NS_OK is returned + // + virtual nsresult GetRemoteAddr(mozilla::net::NetAddr* addr) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // + // returns the number of bytes sent/transmitted over the socket + // + virtual uint64_t ByteCountSent() = 0; + virtual uint64_t ByteCountReceived() = 0; +}; + +#endif // !nsASocketHandler_h__ diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp new file mode 100644 index 0000000000..7a10d4d3f0 --- /dev/null +++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsThreadUtils.h" +#include "nsNetUtil.h" + +#include "nsIOService.h" +#include "nsIChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsILoadInfo.h" + +namespace mozilla { +namespace net { + +static LazyLogModule gRedirectLog("nsRedirect"); +#undef LOG +#define LOG(args) MOZ_LOG(gRedirectLog, LogLevel::Debug, args) + +NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper, nsIAsyncVerifyRedirectCallback, + nsIRunnable, nsINamed) + +class nsAsyncVerifyRedirectCallbackEvent : public Runnable { + public: + nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback* cb, + nsresult result) + : Runnable("nsAsyncVerifyRedirectCallbackEvent"), + mCallback(cb), + mResult(result) {} + + NS_IMETHOD Run() override { + LOG( + ("nsAsyncVerifyRedirectCallbackEvent::Run() " + "callback to %p with result %" PRIx32, + mCallback.get(), static_cast<uint32_t>(mResult))); + (void)mCallback->OnRedirectVerifyCallback(mResult); + return NS_OK; + } + + private: + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback; + nsresult mResult; +}; + +nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() { + NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0, + "Did not receive all required callbacks!"); +} + +nsresult nsAsyncRedirectVerifyHelper::Init( + nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags, + nsIEventTarget* mainThreadEventTarget, bool synchronize) { + LOG( + ("nsAsyncRedirectVerifyHelper::Init() " + "oldChan=%p newChan=%p", + oldChan, newChan)); + mOldChan = oldChan; + mNewChan = newChan; + mFlags = flags; + mCallbackEventTarget = NS_IsMainThread() && mainThreadEventTarget + ? mainThreadEventTarget + : GetCurrentSerialEventTarget(); + + if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE))) { + nsCOMPtr<nsILoadInfo> loadInfo = oldChan->LoadInfo(); + if (loadInfo->GetDontFollowRedirects()) { + ExplicitCallback(NS_BINDING_ABORTED); + return NS_OK; + } + } + + if (synchronize) mWaitingForRedirectCallback = true; + + nsCOMPtr<nsIRunnable> runnable = this; + nsresult rv; + rv = mainThreadEventTarget + ? mainThreadEventTarget->Dispatch(runnable.forget()) + : GetMainThreadSerialEventTarget()->Dispatch(runnable.forget()); + NS_ENSURE_SUCCESS(rv, rv); + + if (synchronize) { + if (!SpinEventLoopUntil("nsAsyncRedirectVerifyHelper::Init"_ns, + [&]() { return !mWaitingForRedirectCallback; })) { + return NS_ERROR_UNEXPECTED; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result) { + LOG( + ("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() " + "result=%" PRIx32 " expectedCBs=%u mResult=%" PRIx32, + static_cast<uint32_t>(result), mExpectedCallbacks, + static_cast<uint32_t>(mResult))); + + MOZ_DIAGNOSTIC_ASSERT( + mExpectedCallbacks > 0, + "OnRedirectVerifyCallback called more times than expected"); + if (mExpectedCallbacks <= 0) { + return NS_ERROR_UNEXPECTED; + } + + --mExpectedCallbacks; + + // If response indicates failure we may call back immediately + if (NS_FAILED(result)) { + // We chose to store the first failure-value (as opposed to the last) + if (NS_SUCCEEDED(mResult)) mResult = result; + + // If InitCallback() has been called, just invoke the callback and + // return. Otherwise it will be invoked from InitCallback() + if (mCallbackInitiated) { + ExplicitCallback(mResult); + return NS_OK; + } + } + + // If the expected-counter is in balance and InitCallback() was called, all + // sinks have agreed that the redirect is ok and we can invoke our callback + if (mCallbackInitiated && mExpectedCallbacks == 0) { + ExplicitCallback(mResult); + } + + return NS_OK; +} + +nsresult nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect( + nsIChannelEventSink* sink, nsIChannel* oldChannel, nsIChannel* newChannel, + uint32_t flags) { + LOG( + ("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() " + "sink=%p expectedCBs=%u mResult=%" PRIx32, + sink, mExpectedCallbacks, static_cast<uint32_t>(mResult))); + + ++mExpectedCallbacks; + + if (IsOldChannelCanceled()) { + LOG( + (" old channel has been canceled, cancel the redirect by " + "emulating OnRedirectVerifyCallback...")); + (void)OnRedirectVerifyCallback(NS_BINDING_ABORTED); + return NS_BINDING_ABORTED; + } + + nsresult rv = + sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); + + LOG((" result=%" PRIx32 " expectedCBs=%u", static_cast<uint32_t>(rv), + mExpectedCallbacks)); + + // If the sink returns failure from this call the redirect is vetoed. We + // emulate a callback from the sink in this case in order to perform all + // the necessary logic. + if (NS_FAILED(rv)) { + LOG((" emulating OnRedirectVerifyCallback...")); + (void)OnRedirectVerifyCallback(rv); + } + + return rv; // Return the actual status since our caller may need it +} + +void nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result) { + LOG( + ("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "result=%" PRIx32 + " expectedCBs=%u mCallbackInitiated=%u mResult=%" PRIx32, + static_cast<uint32_t>(result), mExpectedCallbacks, mCallbackInitiated, + static_cast<uint32_t>(mResult))); + + nsCOMPtr<nsIAsyncVerifyRedirectCallback> callback( + do_QueryInterface(mOldChan)); + + if (!callback || !mCallbackEventTarget) { + LOG( + ("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "callback=%p mCallbackEventTarget=%p", + callback.get(), mCallbackEventTarget.get())); + return; + } + + mCallbackInitiated = false; // reset to ensure only one callback + mWaitingForRedirectCallback = false; + + // Now, dispatch the callback on the event-target which called Init() + nsCOMPtr<nsIRunnable> event = + new nsAsyncVerifyRedirectCallbackEvent(callback, result); + if (!event) { + NS_WARNING( + "nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "failed creating callback event!"); + return; + } + nsresult rv = mCallbackEventTarget->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING( + "nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "failed dispatching callback event!"); + } else { + LOG( + ("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "dispatched callback event=%p", + event.get())); + } +} + +void nsAsyncRedirectVerifyHelper::InitCallback() { + LOG( + ("nsAsyncRedirectVerifyHelper::InitCallback() " + "expectedCBs=%d mResult=%" PRIx32, + mExpectedCallbacks, static_cast<uint32_t>(mResult))); + + mCallbackInitiated = true; + + // Invoke the callback if we are done + if (mExpectedCallbacks == 0) ExplicitCallback(mResult); +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::GetName(nsACString& aName) { + aName.AssignLiteral("nsAsyncRedirectVerifyHelper"); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::Run() { + /* If the channel got canceled after it fired AsyncOnChannelRedirect + * and before we got here, mostly because docloader load has been canceled, + * we must completely ignore this notification and prevent any further + * notification. + */ + if (IsOldChannelCanceled()) { + ExplicitCallback(NS_BINDING_ABORTED); + return NS_OK; + } + + // First, the global observer + NS_ASSERTION(gIOService, "Must have an IO service at this point"); + LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService...")); + nsresult rv = + gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan, mFlags, this); + if (NS_FAILED(rv)) { + ExplicitCallback(rv); + return NS_OK; + } + + // Now, the per-channel observers + nsCOMPtr<nsIChannelEventSink> sink; + NS_QueryNotificationCallbacks(mOldChan, sink); + if (sink) { + LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink...")); + rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags); + } + + // All invocations to AsyncOnChannelRedirect has been done - call + // InitCallback() to flag this + InitCallback(); + return NS_OK; +} + +bool nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() { + if (!mOldChan) { + return false; + } + bool canceled; + nsresult rv = mOldChan->GetCanceled(&canceled); + return NS_SUCCEEDED(rv) && canceled; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.h b/netwerk/base/nsAsyncRedirectVerifyHelper.h new file mode 100644 index 0000000000..3e1afa8c69 --- /dev/null +++ b/netwerk/base/nsAsyncRedirectVerifyHelper.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAsyncRedirectVerifyHelper_h +#define nsAsyncRedirectVerifyHelper_h + +#include "nsIRunnable.h" +#include "nsIChannelEventSink.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsINamed.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" + +class nsIChannel; + +namespace mozilla { +namespace net { + +/** + * This class simplifies call of OnChannelRedirect of IOService and + * the sink bound with the channel being redirected while the result of + * redirect decision is returned through the callback. + */ +class nsAsyncRedirectVerifyHelper final + : public nsIRunnable, + public nsINamed, + public nsIAsyncVerifyRedirectCallback { + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + NS_DECL_NSINAMED + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + + public: + nsAsyncRedirectVerifyHelper() = default; + + /* + * Calls AsyncOnChannelRedirect() on the given sink with the given + * channels and flags. Keeps track of number of async callbacks to expect. + */ + nsresult DelegateOnChannelRedirect(nsIChannelEventSink* sink, + nsIChannel* oldChannel, + nsIChannel* newChannel, uint32_t flags); + + /** + * Initialize and run the chain of AsyncOnChannelRedirect calls. OldChannel + * is QI'ed for nsIAsyncVerifyRedirectCallback. The result of the redirect + * decision is passed through this interface back to the oldChannel. + * + * @param oldChan + * channel being redirected, MUST implement + * nsIAsyncVerifyRedirectCallback + * @param newChan + * target of the redirect channel + * @param flags + * redirect flags + * @param mainThreadEventTarget + * a labeled event target for dispatching runnables + * @param synchronize + * set to TRUE if you want the Init method wait synchronously for + * all redirect callbacks + */ + nsresult Init(nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags, + nsIEventTarget* mainThreadEventTarget, + bool synchronize = false); + + protected: + nsCOMPtr<nsIChannel> mOldChan; + nsCOMPtr<nsIChannel> mNewChan; + uint32_t mFlags{0}; + bool mWaitingForRedirectCallback{false}; + nsCOMPtr<nsIEventTarget> mCallbackEventTarget; + bool mCallbackInitiated{false}; + int32_t mExpectedCallbacks{0}; + nsresult mResult{NS_OK}; // value passed to callback + + void InitCallback(); + + /** + * Calls back to |oldChan| as described in Init() + */ + void ExplicitCallback(nsresult result); + + private: + ~nsAsyncRedirectVerifyHelper(); + + bool IsOldChannelCanceled(); +}; + +/* + * Helper to make the call-stack handle some control-flow for us + */ +class nsAsyncRedirectAutoCallback { + public: + explicit nsAsyncRedirectAutoCallback( + nsIAsyncVerifyRedirectCallback* aCallback) + : mCallback(aCallback) { + mResult = NS_OK; + } + ~nsAsyncRedirectAutoCallback() { + if (mCallback) mCallback->OnRedirectVerifyCallback(mResult); + } + /* + * Call this is you want it to call back with a different result-code + */ + void SetResult(nsresult aRes) { mResult = aRes; } + /* + * Call this is you want to avoid the callback + */ + void DontCallback() { mCallback = nullptr; } + + private: + nsIAsyncVerifyRedirectCallback* mCallback; + nsresult mResult; +}; + +} // namespace net +} // namespace mozilla +#endif diff --git a/netwerk/base/nsAsyncStreamCopier.cpp b/netwerk/base/nsAsyncStreamCopier.cpp new file mode 100644 index 0000000000..de8363d17d --- /dev/null +++ b/netwerk/base/nsAsyncStreamCopier.cpp @@ -0,0 +1,405 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAsyncStreamCopier.h" +#include "nsComponentManagerUtils.h" +#include "nsIOService.h" +#include "nsIEventTarget.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "nsIBufferedStreams.h" +#include "nsIRequestObserver.h" +#include "mozilla/Logging.h" + +using namespace mozilla; +using namespace mozilla::net; + +#undef LOG +// +// MOZ_LOG=nsStreamCopier:5 +// +static LazyLogModule gStreamCopierLog("nsStreamCopier"); +#define LOG(args) MOZ_LOG(gStreamCopierLog, mozilla::LogLevel::Debug, args) + +/** + * An event used to perform initialization off the main thread. + */ +class AsyncApplyBufferingPolicyEvent final : public Runnable { + public: + /** + * @param aCopier + * The nsAsyncStreamCopier requesting the information. + */ + explicit AsyncApplyBufferingPolicyEvent(nsAsyncStreamCopier* aCopier) + : mozilla::Runnable("AsyncApplyBufferingPolicyEvent"), + mCopier(aCopier), + mTarget(GetCurrentSerialEventTarget()) {} + + NS_IMETHOD Run() override { + nsresult rv = mCopier->ApplyBufferingPolicy(); + if (NS_FAILED(rv)) { + mCopier->Cancel(rv); + return NS_OK; + } + + rv = mTarget->Dispatch( + NewRunnableMethod("nsAsyncStreamCopier::AsyncCopyInternal", mCopier, + &nsAsyncStreamCopier::AsyncCopyInternal), + NS_DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (NS_FAILED(rv)) { + mCopier->Cancel(rv); + } + return NS_OK; + } + + private: + RefPtr<nsAsyncStreamCopier> mCopier; + nsCOMPtr<nsIEventTarget> mTarget; +}; + +//----------------------------------------------------------------------------- + +nsAsyncStreamCopier::nsAsyncStreamCopier() + : mChunkSize(nsIOService::gDefaultSegmentSize) { + LOG(("Creating nsAsyncStreamCopier @%p\n", this)); +} + +nsAsyncStreamCopier::~nsAsyncStreamCopier() { + LOG(("Destroying nsAsyncStreamCopier @%p\n", this)); +} + +bool nsAsyncStreamCopier::IsComplete(nsresult* status) { + MutexAutoLock lock(mLock); + if (status) *status = mStatus; + return !mIsPending; +} + +nsIRequest* nsAsyncStreamCopier::AsRequest() { + return static_cast<nsIRequest*>(static_cast<nsIAsyncStreamCopier*>(this)); +} + +void nsAsyncStreamCopier::Complete(nsresult status) { + LOG(("nsAsyncStreamCopier::Complete [this=%p status=%" PRIx32 "]\n", this, + static_cast<uint32_t>(status))); + + nsCOMPtr<nsIRequestObserver> observer; + nsCOMPtr<nsISupports> ctx; + { + MutexAutoLock lock(mLock); + mCopierCtx = nullptr; + + if (mIsPending) { + mIsPending = false; + mStatus = status; + + // setup OnStopRequest callback and release references... + observer = mObserver; + mObserver = nullptr; + } + } + + if (observer) { + LOG((" calling OnStopRequest [status=%" PRIx32 "]\n", + static_cast<uint32_t>(status))); + observer->OnStopRequest(AsRequest(), status); + } +} + +void nsAsyncStreamCopier::OnAsyncCopyComplete(void* closure, nsresult status) { + // AddRef'd in AsyncCopy. Will be released at the end of the method. + RefPtr<nsAsyncStreamCopier> self = dont_AddRef((nsAsyncStreamCopier*)closure); + self->Complete(status); +} + +//----------------------------------------------------------------------------- +// nsISupports + +// We cannot use simply NS_IMPL_ISUPPORTSx as both +// nsIAsyncStreamCopier and nsIAsyncStreamCopier2 implement nsIRequest + +NS_IMPL_ADDREF(nsAsyncStreamCopier) +NS_IMPL_RELEASE(nsAsyncStreamCopier) +NS_INTERFACE_TABLE_HEAD(nsAsyncStreamCopier) + NS_INTERFACE_TABLE_BEGIN + NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier) + NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier2) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsIRequest, + nsIAsyncStreamCopier) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsISupports, + nsIAsyncStreamCopier) + NS_INTERFACE_TABLE_END +NS_INTERFACE_TABLE_TAIL + +//----------------------------------------------------------------------------- +// nsIRequest + +NS_IMETHODIMP +nsAsyncStreamCopier::GetName(nsACString& name) { + name.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::IsPending(bool* result) { + *result = !IsComplete(); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::GetStatus(nsresult* status) { + IsComplete(status); + return NS_OK; +} + +NS_IMETHODIMP nsAsyncStreamCopier::SetCanceledReason( + const nsACString& aReason) { + return nsIAsyncStreamCopier::SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsAsyncStreamCopier::GetCanceledReason(nsACString& aReason) { + return nsIAsyncStreamCopier::GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsAsyncStreamCopier::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return nsIAsyncStreamCopier::CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +nsAsyncStreamCopier::Cancel(nsresult status) { + nsCOMPtr<nsISupports> copierCtx; + { + MutexAutoLock lock(mLock); + if (!mIsPending) { + return NS_OK; + } + copierCtx.swap(mCopierCtx); + } + + if (NS_SUCCEEDED(status)) { + NS_WARNING("cancel with non-failure status code"); + status = NS_BASE_STREAM_CLOSED; + } + + if (copierCtx) NS_CancelAsyncCopy(copierCtx, status); + + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::Suspend() { + MOZ_ASSERT_UNREACHABLE("nsAsyncStreamCopier::Suspend"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::Resume() { + MOZ_ASSERT_UNREACHABLE("nsAsyncStreamCopier::Resume"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::GetLoadFlags(nsLoadFlags* aLoadFlags) { + *aLoadFlags = LOAD_NORMAL; + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } + +NS_IMETHODIMP +nsAsyncStreamCopier::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return nsIAsyncStreamCopier::GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsAsyncStreamCopier::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return nsIAsyncStreamCopier::SetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsAsyncStreamCopier::GetLoadGroup(nsILoadGroup** aLoadGroup) { + *aLoadGroup = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; } + +// Can't be accessed by multiple threads yet +nsresult nsAsyncStreamCopier::InitInternal( + nsIInputStream* source, nsIOutputStream* sink, nsIEventTarget* target, + uint32_t chunkSize, bool closeSource, + bool closeSink) MOZ_NO_THREAD_SAFETY_ANALYSIS { + NS_ASSERTION(!mSource && !mSink, "Init() called more than once"); + if (chunkSize == 0) { + chunkSize = nsIOService::gDefaultSegmentSize; + } + mChunkSize = chunkSize; + + mSource = source; + mSink = sink; + mCloseSource = closeSource; + mCloseSink = closeSink; + + if (target) { + mTarget = target; + } else { + nsresult rv; + mTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIAsyncStreamCopier + +NS_IMETHODIMP +nsAsyncStreamCopier::Init(nsIInputStream* source, nsIOutputStream* sink, + nsIEventTarget* target, bool sourceBuffered, + bool sinkBuffered, uint32_t chunkSize, + bool closeSource, bool closeSink) { + NS_ASSERTION(sourceBuffered || sinkBuffered, + "at least one stream must be buffered"); + mMode = sourceBuffered ? NS_ASYNCCOPY_VIA_READSEGMENTS + : NS_ASYNCCOPY_VIA_WRITESEGMENTS; + + return InitInternal(source, sink, target, chunkSize, closeSource, closeSink); +} + +//----------------------------------------------------------------------------- +// nsIAsyncStreamCopier2 + +NS_IMETHODIMP +nsAsyncStreamCopier::Init(nsIInputStream* source, nsIOutputStream* sink, + nsIEventTarget* target, uint32_t chunkSize, + bool closeSource, bool closeSink) { + mShouldSniffBuffering = true; + + return InitInternal(source, sink, target, chunkSize, closeSource, closeSink); +} + +/** + * Detect whether the input or the output stream is buffered, + * bufferize one of them if neither is buffered. + */ +nsresult nsAsyncStreamCopier::ApplyBufferingPolicy() { + // This function causes I/O, it must not be executed on the main + // thread. + MOZ_ASSERT(!NS_IsMainThread()); + + if (NS_OutputStreamIsBuffered(mSink)) { + // Sink is buffered, no need to perform additional buffering + mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS; + return NS_OK; + } + if (NS_InputStreamIsBuffered(mSource)) { + // Source is buffered, no need to perform additional buffering + mMode = NS_ASYNCCOPY_VIA_READSEGMENTS; + return NS_OK; + } + + // No buffering, let's buffer the sink + nsresult rv; + nsCOMPtr<nsIBufferedOutputStream> sink = + do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + rv = sink->Init(mSink, mChunkSize); + if (NS_FAILED(rv)) { + return rv; + } + + mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS; + mSink = sink; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// Both nsIAsyncStreamCopier and nsIAsyncStreamCopier2 + +NS_IMETHODIMP +nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver* observer, nsISupports* ctx) { + LOG(("nsAsyncStreamCopier::AsyncCopy [this=%p observer=%p]\n", this, + observer)); + + NS_ASSERTION(mSource && mSink, "not initialized"); + nsresult rv; + + if (observer) { + // build proxy for observer events + rv = NS_NewRequestObserverProxy(getter_AddRefs(mObserver), observer, ctx); + if (NS_FAILED(rv)) return rv; + } + + // from this point forward, AsyncCopy is going to return NS_OK. any errors + // will be reported via OnStopRequest. + { + MutexAutoLock lock(mLock); + mIsPending = true; + } + + if (mObserver) { + rv = mObserver->OnStartRequest(AsRequest()); + if (NS_FAILED(rv)) Cancel(rv); + } + + if (!mShouldSniffBuffering) { + // No buffer sniffing required, let's proceed + AsyncCopyInternal(); + return NS_OK; + } + + if (NS_IsMainThread()) { + // Don't perform buffer sniffing on the main thread + nsCOMPtr<nsIRunnable> event = new AsyncApplyBufferingPolicyEvent(this); + rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + Cancel(rv); + } + return NS_OK; + } + + // We're not going to block the main thread, so let's sniff here + rv = ApplyBufferingPolicy(); + if (NS_FAILED(rv)) { + Cancel(rv); + } + AsyncCopyInternal(); + return NS_OK; +} + +// Launch async copy. +// All errors are reported through the observer. +void nsAsyncStreamCopier::AsyncCopyInternal() { + MOZ_ASSERT(mMode == NS_ASYNCCOPY_VIA_READSEGMENTS || + mMode == NS_ASYNCCOPY_VIA_WRITESEGMENTS); + + nsresult rv; + // We want to receive progress notifications; release happens in + // OnAsyncCopyComplete. + RefPtr<nsAsyncStreamCopier> self = this; + { + MutexAutoLock lock(mLock); + rv = NS_AsyncCopy(mSource, mSink, mTarget, mMode, mChunkSize, + OnAsyncCopyComplete, this, mCloseSource, mCloseSink, + getter_AddRefs(mCopierCtx)); + } + if (NS_FAILED(rv)) { + Cancel(rv); + return; // release self + } + + Unused << self.forget(); // Will be released in OnAsyncCopyComplete +} diff --git a/netwerk/base/nsAsyncStreamCopier.h b/netwerk/base/nsAsyncStreamCopier.h new file mode 100644 index 0000000000..120218c3c7 --- /dev/null +++ b/netwerk/base/nsAsyncStreamCopier.h @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAsyncStreamCopier_h__ +#define nsAsyncStreamCopier_h__ + +#include "nsIAsyncStreamCopier.h" +#include "nsIAsyncStreamCopier2.h" +#include "mozilla/Mutex.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.h" + +class nsIRequestObserver; + +//----------------------------------------------------------------------------- + +class nsAsyncStreamCopier final : public nsIAsyncStreamCopier, + nsIAsyncStreamCopier2 { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSIASYNCSTREAMCOPIER + + // nsIAsyncStreamCopier2 + // We declare it by hand instead of NS_DECL_NSIASYNCSTREAMCOPIER2 + // as nsIAsyncStreamCopier2 duplicates methods of nsIAsyncStreamCopier + NS_IMETHOD Init(nsIInputStream* aSource, nsIOutputStream* aSink, + nsIEventTarget* aTarget, uint32_t aChunkSize, + bool aCloseSource, bool aCloseSink) override; + + nsAsyncStreamCopier(); + + //------------------------------------------------------------------------- + // these methods may be called on any thread + + bool IsComplete(nsresult* status = nullptr); + void Complete(nsresult status); + + private: + virtual ~nsAsyncStreamCopier(); + + nsresult InitInternal(nsIInputStream* source, nsIOutputStream* sink, + nsIEventTarget* target, uint32_t chunkSize, + bool closeSource, bool closeSink); + + static void OnAsyncCopyComplete(void*, nsresult); + + void AsyncCopyInternal(); + nsresult ApplyBufferingPolicy(); + nsIRequest* AsRequest(); + + nsCOMPtr<nsIInputStream> mSource; + nsCOMPtr<nsIOutputStream> mSink; + + nsCOMPtr<nsIRequestObserver> mObserver; + + nsCOMPtr<nsIEventTarget> mTarget; + + nsCOMPtr<nsISupports> mCopierCtx MOZ_GUARDED_BY(mLock); + + mozilla::Mutex mLock{"nsAsyncStreamCopier.mLock"}; + + nsAsyncCopyMode mMode{NS_ASYNCCOPY_VIA_READSEGMENTS}; + uint32_t mChunkSize; // only modified in Init + nsresult mStatus MOZ_GUARDED_BY(mLock){NS_OK}; + bool mIsPending MOZ_GUARDED_BY(mLock){false}; + bool mCloseSource MOZ_GUARDED_BY(mLock){false}; + bool mCloseSink MOZ_GUARDED_BY(mLock){false}; + bool mShouldSniffBuffering{false}; // only modified in Init + + friend class ProceedWithAsyncCopy; + friend class AsyncApplyBufferingPolicyEvent; +}; + +#endif // !nsAsyncStreamCopier_h__ diff --git a/netwerk/base/nsAuthInformationHolder.cpp b/netwerk/base/nsAuthInformationHolder.cpp new file mode 100644 index 0000000000..11cf7626ce --- /dev/null +++ b/netwerk/base/nsAuthInformationHolder.cpp @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAuthInformationHolder.h" + +NS_IMPL_ISUPPORTS(nsAuthInformationHolder, nsIAuthInformation) + +NS_IMETHODIMP +nsAuthInformationHolder::GetFlags(uint32_t* aFlags) { + *aFlags = mFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetRealm(nsAString& aRealm) { + aRealm = mRealm; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetAuthenticationScheme(nsACString& aScheme) { + aScheme = mAuthType; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetUsername(nsAString& aUserName) { + aUserName = mUser; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::SetUsername(const nsAString& aUserName) { + if (!(mFlags & ONLY_PASSWORD)) mUser = aUserName; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetPassword(nsAString& aPassword) { + aPassword = mPassword; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::SetPassword(const nsAString& aPassword) { + mPassword = aPassword; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::GetDomain(nsAString& aDomain) { + aDomain = mDomain; + return NS_OK; +} + +NS_IMETHODIMP +nsAuthInformationHolder::SetDomain(const nsAString& aDomain) { + if (mFlags & NEED_DOMAIN) mDomain = aDomain; + return NS_OK; +} diff --git a/netwerk/base/nsAuthInformationHolder.h b/netwerk/base/nsAuthInformationHolder.h new file mode 100644 index 0000000000..48054d9413 --- /dev/null +++ b/netwerk/base/nsAuthInformationHolder.h @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSAUTHINFORMATIONHOLDER_H_ +#define NSAUTHINFORMATIONHOLDER_H_ + +#include "nsIAuthInformation.h" +#include "nsString.h" + +class nsAuthInformationHolder : public nsIAuthInformation { + protected: + virtual ~nsAuthInformationHolder() = default; + + public: + // aAuthType must be ASCII + nsAuthInformationHolder(uint32_t aFlags, const nsString& aRealm, + const nsACString& aAuthType) + : mFlags(aFlags), mRealm(aRealm), mAuthType(aAuthType) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIAUTHINFORMATION + + const nsString& User() const { return mUser; } + const nsString& Password() const { return mPassword; } + const nsString& Domain() const { return mDomain; } + + /** + * This method can be used to initialize the username when the + * ONLY_PASSWORD flag is set. + */ + void SetUserInternal(const nsString& aUsername) { mUser = aUsername; } + + private: + nsString mUser; + nsString mPassword; + nsString mDomain; + + uint32_t mFlags; + nsString mRealm; + nsCString mAuthType; +}; + +#endif diff --git a/netwerk/base/nsBase64Encoder.cpp b/netwerk/base/nsBase64Encoder.cpp new file mode 100644 index 0000000000..621dd2c4ec --- /dev/null +++ b/netwerk/base/nsBase64Encoder.cpp @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBase64Encoder.h" + +#include "mozilla/Base64.h" + +NS_IMPL_ISUPPORTS(nsBase64Encoder, nsIOutputStream) + +NS_IMETHODIMP +nsBase64Encoder::Close() { return NS_OK; } + +NS_IMETHODIMP +nsBase64Encoder::Flush() { return NS_OK; } + +NS_IMETHODIMP +nsBase64Encoder::StreamStatus() { return NS_OK; } + +NS_IMETHODIMP +nsBase64Encoder::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) { + mData.Append(aBuf, aCount); + *_retval = aCount; + return NS_OK; +} + +NS_IMETHODIMP +nsBase64Encoder::WriteFrom(nsIInputStream* aStream, uint32_t aCount, + uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBase64Encoder::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBase64Encoder::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = false; + return NS_OK; +} + +nsresult nsBase64Encoder::Finish(nsACString& result) { + nsresult rv = mozilla::Base64Encode(mData, result); + if (NS_FAILED(rv)) { + return rv; + } + + // Free unneeded memory and allow reusing the object + mData.Truncate(); + return NS_OK; +} diff --git a/netwerk/base/nsBase64Encoder.h b/netwerk/base/nsBase64Encoder.h new file mode 100644 index 0000000000..b74d3c9b5a --- /dev/null +++ b/netwerk/base/nsBase64Encoder.h @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSBASE64ENCODER_H_ +#define NSBASE64ENCODER_H_ + +#include "nsIOutputStream.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +/** + * A base64 encoder. Usage: Instantiate class, write to it using + * Write(), then call Finish() to get the base64-encoded data. + */ +class nsBase64Encoder final : public nsIOutputStream { + public: + nsBase64Encoder() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + + nsresult Finish(nsACString& _result); + + private: + ~nsBase64Encoder() = default; + + /// The data written to this stream. nsCString can deal fine with + /// binary data. + nsCString mData; +}; + +#endif diff --git a/netwerk/base/nsBaseChannel.cpp b/netwerk/base/nsBaseChannel.cpp new file mode 100644 index 0000000000..df8aa23db4 --- /dev/null +++ b/netwerk/base/nsBaseChannel.cpp @@ -0,0 +1,1024 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 sts=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBaseChannel.h" +#include "nsContentUtils.h" +#include "nsURLHelper.h" +#include "nsNetCID.h" +#include "nsMimeTypes.h" +#include "nsUnknownDecoder.h" +#include "nsIScriptSecurityManager.h" +#include "nsMimeTypes.h" +#include "nsICancelable.h" +#include "nsIChannelEventSink.h" +#include "nsIStreamConverterService.h" +#include "nsChannelClassifier.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsProxyRelease.h" +#include "nsXULAppAPI.h" +#include "nsContentSecurityManager.h" +#include "LoadInfo.h" +#include "nsServiceManagerUtils.h" +#include "nsRedirectHistoryEntry.h" +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/BasePrincipal.h" + +using namespace mozilla; + +// This class is used to suspend a request across a function scope. +class ScopedRequestSuspender { + public: + explicit ScopedRequestSuspender(nsIRequest* request) : mRequest(request) { + if (mRequest && NS_FAILED(mRequest->Suspend())) { + NS_WARNING("Couldn't suspend pump"); + mRequest = nullptr; + } + } + ~ScopedRequestSuspender() { + if (mRequest) mRequest->Resume(); + } + + private: + nsIRequest* mRequest; +}; + +// Used to suspend data events from mRequest within a function scope. This is +// usually needed when a function makes callbacks that could process events. +#define SUSPEND_PUMP_FOR_SCOPE() \ + ScopedRequestSuspender pump_suspender__(mRequest) + +//----------------------------------------------------------------------------- +// nsBaseChannel + +nsBaseChannel::nsBaseChannel() : NeckoTargetHolder(nullptr) { + mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); +} + +nsBaseChannel::~nsBaseChannel() { + NS_ReleaseOnMainThread("nsBaseChannel::mLoadInfo", mLoadInfo.forget()); +} + +nsresult nsBaseChannel::Redirect(nsIChannel* newChannel, uint32_t redirectFlags, + bool openNewChannel) { + SUSPEND_PUMP_FOR_SCOPE(); + + // Transfer properties + + newChannel->SetLoadGroup(mLoadGroup); + newChannel->SetNotificationCallbacks(mCallbacks); + newChannel->SetLoadFlags(mLoadFlags | LOAD_REPLACE); + + // make a copy of the loadinfo, append to the redirectchain + // and set it on the new channel + nsSecurityFlags secFlags = + mLoadInfo->GetSecurityFlags() & ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + nsCOMPtr<nsILoadInfo> newLoadInfo = + static_cast<net::LoadInfo*>(mLoadInfo.get()) + ->CloneWithNewSecFlags(secFlags); + + bool isInternalRedirect = + (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE)); + + newLoadInfo->AppendRedirectHistoryEntry(this, isInternalRedirect); + + // Ensure the channel's loadInfo's result principal URI so that it's + // either non-null or updated to the redirect target URI. + // We must do this because in case the loadInfo's result principal URI + // is null, it would be taken from OriginalURI of the channel. But we + // overwrite it with the whole redirect chain first URI before opening + // the target channel, hence the information would be lost. + // If the protocol handler that created the channel wants to use + // the originalURI of the channel as the principal URI, it has left + // the result principal URI on the load info null. + nsCOMPtr<nsIURI> resultPrincipalURI; + + nsCOMPtr<nsILoadInfo> existingLoadInfo = newChannel->LoadInfo(); + if (existingLoadInfo) { + existingLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI)); + } + if (!resultPrincipalURI) { + newChannel->GetOriginalURI(getter_AddRefs(resultPrincipalURI)); + } + + newLoadInfo->SetResultPrincipalURI(resultPrincipalURI); + + newChannel->SetLoadInfo(newLoadInfo); + + // Preserve the privacy bit if it has been overridden + if (mPrivateBrowsingOverriden) { + nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel = + do_QueryInterface(newChannel); + if (newPBChannel) { + newPBChannel->SetPrivate(mPrivateBrowsing); + } + } + + if (nsCOMPtr<nsIWritablePropertyBag> bag = ::do_QueryInterface(newChannel)) { + nsHashPropertyBag::CopyFrom(bag, static_cast<nsIPropertyBag2*>(this)); + } + + // Notify consumer, giving chance to cancel redirect. + + auto redirectCallbackHelper = MakeRefPtr<net::nsAsyncRedirectVerifyHelper>(); + + bool checkRedirectSynchronously = !openNewChannel; + nsCOMPtr<nsIEventTarget> target = GetNeckoTarget(); + + mRedirectChannel = newChannel; + mRedirectFlags = redirectFlags; + mOpenRedirectChannel = openNewChannel; + nsresult rv = redirectCallbackHelper->Init( + this, newChannel, redirectFlags, target, checkRedirectSynchronously); + if (NS_FAILED(rv)) return rv; + + if (checkRedirectSynchronously && NS_FAILED(mStatus)) return mStatus; + + return NS_OK; +} + +nsresult nsBaseChannel::ContinueRedirect() { + // Make sure to do this _after_ making all the OnChannelRedirect calls + mRedirectChannel->SetOriginalURI(OriginalURI()); + + // If we fail to open the new channel, then we want to leave this channel + // unaffected, so we defer tearing down our channel until we have succeeded + // with the redirect. + + if (mOpenRedirectChannel) { + nsresult rv = NS_OK; + rv = mRedirectChannel->AsyncOpen(mListener); + NS_ENSURE_SUCCESS(rv, rv); + } + + mRedirectChannel = nullptr; + + // close down this channel + Cancel(NS_BINDING_REDIRECTED); + ChannelDone(); + + return NS_OK; +} + +bool nsBaseChannel::HasContentTypeHint() const { + NS_ASSERTION(!Pending(), "HasContentTypeHint called too late"); + return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE); +} + +nsresult nsBaseChannel::BeginPumpingData() { + nsresult rv; + + rv = BeginAsyncRead(this, getter_AddRefs(mRequest), + getter_AddRefs(mCancelableAsyncRequest)); + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(mRequest || mCancelableAsyncRequest, + "should have got a request or cancelable"); + mPumpingData = true; + return NS_OK; + } + if (rv != NS_ERROR_NOT_IMPLEMENTED) { + return rv; + } + + nsCOMPtr<nsIInputStream> stream; + nsCOMPtr<nsIChannel> channel; + rv = OpenContentStream(true, getter_AddRefs(stream), getter_AddRefs(channel)); + if (NS_FAILED(rv)) return rv; + + NS_ASSERTION(!stream || !channel, "Got both a channel and a stream?"); + + if (channel) { + nsCOMPtr<nsIRunnable> runnable = new RedirectRunnable(this, channel); + rv = Dispatch(runnable.forget()); + if (NS_SUCCEEDED(rv)) mWaitingOnAsyncRedirect = true; + return rv; + } + + // By assigning mPump, we flag this channel as pending (see Pending). It's + // important that the pending flag is set when we call into the stream (the + // call to AsyncRead results in the stream's AsyncWait method being called) + // and especially when we call into the loadgroup. Our caller takes care to + // release mPump if we return an error. + + nsCOMPtr<nsISerialEventTarget> target = GetNeckoTarget(); + rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, 0, 0, true, + target); + if (NS_FAILED(rv)) { + return rv; + } + + mPumpingData = true; + mRequest = mPump; + rv = mPump->AsyncRead(this); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr<BlockingPromise> promise; + rv = ListenerBlockingPromise(getter_AddRefs(promise)); + if (NS_FAILED(rv)) { + return rv; + } + + if (promise) { + mPump->Suspend(); + + RefPtr<nsBaseChannel> self(this); + + promise->Then( + target, __func__, + [self, this](nsresult rv) { + MOZ_ASSERT(mPump); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + mPump->Resume(); + }, + [self, this](nsresult rv) { + MOZ_ASSERT(mPump); + MOZ_ASSERT(NS_FAILED(rv)); + Cancel(rv); + mPump->Resume(); + }); + } + + return NS_OK; +} + +void nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel) { + NS_ASSERTION(!mPumpingData, "Shouldn't have gotten here"); + + nsresult rv = mStatus; + if (NS_SUCCEEDED(mStatus)) { + rv = Redirect(newChannel, nsIChannelEventSink::REDIRECT_TEMPORARY, true); + if (NS_SUCCEEDED(rv)) { + // OnRedirectVerifyCallback will be called asynchronously + return; + } + } + + ContinueHandleAsyncRedirect(rv); +} + +void nsBaseChannel::ContinueHandleAsyncRedirect(nsresult result) { + mWaitingOnAsyncRedirect = false; + + if (NS_FAILED(result)) Cancel(result); + + if (NS_FAILED(result) && mListener) { + // Notify our consumer ourselves + mListener->OnStartRequest(this); + mListener->OnStopRequest(this, mStatus); + ChannelDone(); + } + + if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus); + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + CallbacksChanged(); +} + +void nsBaseChannel::ClassifyURI() { + // For channels created in the child process, delegate to the parent to + // classify URIs. + if (!XRE_IsParentProcess()) { + return; + } + + if (NS_ShouldClassifyChannel(this)) { + auto classifier = MakeRefPtr<net::nsChannelClassifier>(this); + classifier->Start(); + } +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsISupports + +NS_IMPL_ADDREF(nsBaseChannel) +NS_IMPL_RELEASE(nsBaseChannel) + +NS_INTERFACE_MAP_BEGIN(nsBaseChannel) + NS_INTERFACE_MAP_ENTRY(nsIRequest) + NS_INTERFACE_MAP_ENTRY(nsIChannel) + NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsITransportEventSink) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) + NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel) +NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag) + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIRequest + +NS_IMETHODIMP +nsBaseChannel::GetName(nsACString& result) { + if (!mURI) { + result.Truncate(); + return NS_OK; + } + return mURI->GetSpec(result); +} + +NS_IMETHODIMP +nsBaseChannel::IsPending(bool* result) { + *result = Pending(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetStatus(nsresult* status) { + if (mRequest && NS_SUCCEEDED(mStatus)) { + mRequest->GetStatus(status); + } else { + *status = mStatus; + } + return NS_OK; +} + +NS_IMETHODIMP nsBaseChannel::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsBaseChannel::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsBaseChannel::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +nsBaseChannel::Cancel(nsresult status) { + // Ignore redundant cancelation + if (mCanceled) { + return NS_OK; + } + + mCanceled = true; + mStatus = status; + + if (mCancelableAsyncRequest) { + mCancelableAsyncRequest->Cancel(status); + } + + if (mRequest) { + mRequest->Cancel(status); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::Suspend() { + NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED); + return mRequest->Suspend(); +} + +NS_IMETHODIMP +nsBaseChannel::Resume() { + NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED); + return mRequest->Resume(); +} + +NS_IMETHODIMP +nsBaseChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) { + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) { + mLoadFlags = aLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsBaseChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsBaseChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) { + nsCOMPtr<nsILoadGroup> loadGroup(mLoadGroup); + loadGroup.forget(aLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) { + if (!CanSetLoadGroup(aLoadGroup)) { + return NS_ERROR_FAILURE; + } + + mLoadGroup = aLoadGroup; + CallbacksChanged(); + UpdatePrivateBrowsing(); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIChannel + +NS_IMETHODIMP +nsBaseChannel::GetOriginalURI(nsIURI** aURI) { + RefPtr<nsIURI> uri = OriginalURI(); + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetOriginalURI(nsIURI* aURI) { + NS_ENSURE_ARG_POINTER(aURI); + mOriginalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetURI(nsIURI** aURI) { + nsCOMPtr<nsIURI> uri(mURI); + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetOwner(nsISupports** aOwner) { + nsCOMPtr<nsISupports> owner(mOwner); + owner.forget(aOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetOwner(nsISupports* aOwner) { + mOwner = aOwner; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) { + MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null"); + mLoadInfo = aLoadInfo; + + // Need to update |mNeckoTarget| when load info has changed. + SetupNeckoTarget(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) { + nsCOMPtr<nsILoadInfo> loadInfo(mLoadInfo); + loadInfo.forget(aLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetIsDocument(bool* aIsDocument) { + return NS_GetIsDocumentChannel(this, aIsDocument); +} + +NS_IMETHODIMP +nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) { + nsCOMPtr<nsIInterfaceRequestor> callbacks(mCallbacks); + callbacks.forget(aCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) { + if (!CanSetCallbacks(aCallbacks)) { + return NS_ERROR_FAILURE; + } + + mCallbacks = aCallbacks; + CallbacksChanged(); + UpdatePrivateBrowsing(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) { + *aSecurityInfo = do_AddRef(mSecurityInfo).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentType(nsACString& aContentType) { + aContentType = mContentType; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentType(const nsACString& aContentType) { + // mContentCharset is unchanged if not parsed + bool dummy; + net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentCharset(nsACString& aContentCharset) { + aContentCharset = mContentCharset; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentCharset(const nsACString& aContentCharset) { + mContentCharset = aContentCharset; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentDisposition(uint32_t* aContentDisposition) { + // preserve old behavior, fail unless explicitly set. + if (mContentDispositionHint == UINT32_MAX) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aContentDisposition = mContentDispositionHint; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentDisposition(uint32_t aContentDisposition) { + mContentDispositionHint = aContentDisposition; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentDispositionFilename( + nsAString& aContentDispositionFilename) { + if (!mContentDispositionFilename) { + return NS_ERROR_NOT_AVAILABLE; + } + + aContentDispositionFilename = *mContentDispositionFilename; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentDispositionFilename( + const nsAString& aContentDispositionFilename) { + mContentDispositionFilename = + MakeUnique<nsString>(aContentDispositionFilename); + + // For safety reasons ensure the filename doesn't contain null characters and + // replace them with underscores. We may later pass the extension to system + // MIME APIs that expect null terminated strings. + mContentDispositionFilename->ReplaceChar(char16_t(0), '_'); + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentDispositionHeader( + nsACString& aContentDispositionHeader) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsBaseChannel::GetContentLength(int64_t* aContentLength) { + *aContentLength = mContentLength; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetContentLength(int64_t aContentLength) { + mContentLength = aContentLength; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::Open(nsIInputStream** aStream) { + nsCOMPtr<nsIStreamListener> listener; + nsresult rv = + nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS); + + nsCOMPtr<nsIChannel> chan; + rv = OpenContentStream(false, aStream, getter_AddRefs(chan)); + NS_ASSERTION(!chan || !*aStream, "Got both a channel and a stream?"); + if (NS_SUCCEEDED(rv) && chan) { + rv = Redirect(chan, nsIChannelEventSink::REDIRECT_INTERNAL, false); + if (NS_FAILED(rv)) return rv; + rv = chan->Open(aStream); + } else if (rv == NS_ERROR_NOT_IMPLEMENTED) { + return NS_ImplementChannelOpen(this, aStream); + } + + if (NS_SUCCEEDED(rv)) { + mWasOpened = true; + ClassifyURI(); + } + + return rv; +} + +NS_IMETHODIMP +nsBaseChannel::AsyncOpen(nsIStreamListener* aListener) { + nsCOMPtr<nsIStreamListener> listener = aListener; + + nsresult rv = + nsContentSecurityManager::doContentSecurityCheck(this, listener); + if (NS_FAILED(rv)) { + mCallbacks = nullptr; + return rv; + } + + MOZ_ASSERT( + mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone() || + (mLoadInfo->GetSecurityMode() == + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL && + mLoadInfo->GetLoadingPrincipal() && + mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()), + "security flags in loadInfo but doContentSecurityCheck() not called"); + + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); + NS_ENSURE_ARG(listener); + + SetupNeckoTarget(); + + // Skip checking for chrome:// sub-resources. + nsAutoCString scheme; + mURI->GetScheme(scheme); + if (!scheme.EqualsLiteral("file")) { + NS_CompareLoadInfoAndLoadContext(this); + } + + // Ensure that this is an allowed port before proceeding. + rv = NS_CheckPortSafety(mURI); + if (NS_FAILED(rv)) { + mCallbacks = nullptr; + return rv; + } + + AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(this); + + // Store the listener and context early so that OpenContentStream and the + // stream's AsyncWait method (called by AsyncRead) can have access to them + // via the StreamListener methods. However, since + // this typically introduces a reference cycle between this and the listener, + // we need to be sure to break the reference if this method does not succeed. + mListener = listener; + + // This method assigns mPump as a side-effect. We need to clear mPump if + // this method fails. + rv = BeginPumpingData(); + if (NS_FAILED(rv)) { + mPump = nullptr; + mRequest = nullptr; + mPumpingData = false; + ChannelDone(); + mCallbacks = nullptr; + return rv; + } + + // At this point, we are going to return success no matter what. + + mWasOpened = true; + + SUSPEND_PUMP_FOR_SCOPE(); + + if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr); + + ClassifyURI(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsITransportEventSink + +NS_IMETHODIMP +nsBaseChannel::OnTransportStatus(nsITransport* transport, nsresult status, + int64_t progress, int64_t progressMax) { + // In some cases, we may wish to suppress transport-layer status events. + + if (!mPumpingData || NS_FAILED(mStatus)) { + return NS_OK; + } + + SUSPEND_PUMP_FOR_SCOPE(); + + // Lazily fetch mProgressSink + if (!mProgressSink) { + if (mQueriedProgressSink) { + return NS_OK; + } + GetCallback(mProgressSink); + mQueriedProgressSink = true; + if (!mProgressSink) { + return NS_OK; + } + } + + if (!HasLoadFlag(LOAD_BACKGROUND)) { + nsAutoString statusArg; + if (GetStatusArg(status, statusArg)) { + mProgressSink->OnStatus(this, status, statusArg.get()); + } + } + + if (progress) { + mProgressSink->OnProgress(this, progress, progressMax); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIInterfaceRequestor + +NS_IMETHODIMP +nsBaseChannel::GetInterface(const nsIID& iid, void** result) { + NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, iid, result); + return *result ? NS_OK : NS_ERROR_NO_INTERFACE; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIRequestObserver + +static void CallTypeSniffers(void* aClosure, const uint8_t* aData, + uint32_t aCount) { + nsIChannel* chan = static_cast<nsIChannel*>(aClosure); + + nsAutoCString newType; + NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType); + if (!newType.IsEmpty()) { + chan->SetContentType(newType); + } +} + +static void CallUnknownTypeSniffer(void* aClosure, const uint8_t* aData, + uint32_t aCount) { + nsIChannel* chan = static_cast<nsIChannel*>(aClosure); + + RefPtr<nsUnknownDecoder> sniffer = new nsUnknownDecoder(); + + nsAutoCString detected; + nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected); + if (NS_SUCCEEDED(rv)) chan->SetContentType(detected); +} + +NS_IMETHODIMP +nsBaseChannel::OnStartRequest(nsIRequest* request) { + MOZ_ASSERT_IF(mRequest, request == mRequest); + MOZ_ASSERT_IF(mCancelableAsyncRequest, !mRequest); + + if (mPump) { + // If our content type is unknown, use the content type + // sniffer. If the sniffer is not available for some reason, then we just + // keep going as-is. + if (NS_SUCCEEDED(mStatus) && + mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) { + mPump->PeekStream(CallUnknownTypeSniffer, static_cast<nsIChannel*>(this)); + } + + // Now, the general type sniffers. Skip this if we have none. + if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { + mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this)); + } + } + + SUSPEND_PUMP_FOR_SCOPE(); + + if (mListener) { // null in case of redirect + return mListener->OnStartRequest(this); + } + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::OnStopRequest(nsIRequest* request, nsresult status) { + // If both mStatus and status are failure codes, we keep mStatus as-is since + // that is consistent with our GetStatus and Cancel methods. + if (NS_SUCCEEDED(mStatus)) mStatus = status; + + // Cause Pending to return false. + mPump = nullptr; + mRequest = nullptr; + mCancelableAsyncRequest = nullptr; + mPumpingData = false; + + if (mListener) { // null in case of redirect + mListener->OnStopRequest(this, mStatus); + } + ChannelDone(); + + // No need to suspend pump in this scope since we will not be receiving + // any more events from it. + + if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus); + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + CallbacksChanged(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsIStreamListener + +NS_IMETHODIMP +nsBaseChannel::OnDataAvailable(nsIRequest* request, nsIInputStream* stream, + uint64_t offset, uint32_t count) { + SUSPEND_PUMP_FOR_SCOPE(); + + nsresult rv = mListener->OnDataAvailable(this, stream, offset, count); + if (mSynthProgressEvents && NS_SUCCEEDED(rv)) { + int64_t prog = offset + count; + if (NS_IsMainThread()) { + OnTransportStatus(nullptr, NS_NET_STATUS_READING, prog, mContentLength); + } else { + class OnTransportStatusAsyncEvent : public Runnable { + RefPtr<nsBaseChannel> mChannel; + int64_t mProgress; + int64_t mContentLength; + + public: + OnTransportStatusAsyncEvent(nsBaseChannel* aChannel, int64_t aProgress, + int64_t aContentLength) + : Runnable("OnTransportStatusAsyncEvent"), + mChannel(aChannel), + mProgress(aProgress), + mContentLength(aContentLength) {} + + NS_IMETHOD Run() override { + return mChannel->OnTransportStatus(nullptr, NS_NET_STATUS_READING, + mProgress, mContentLength); + } + }; + + nsCOMPtr<nsIRunnable> runnable = + new OnTransportStatusAsyncEvent(this, prog, mContentLength); + Dispatch(runnable.forget()); + } + } + + return rv; +} + +NS_IMETHODIMP +nsBaseChannel::OnRedirectVerifyCallback(nsresult result) { + if (NS_SUCCEEDED(result)) result = ContinueRedirect(); + + if (NS_FAILED(result) && !mWaitingOnAsyncRedirect) { + if (NS_SUCCEEDED(mStatus)) mStatus = result; + return NS_OK; + } + + if (mWaitingOnAsyncRedirect) ContinueHandleAsyncRedirect(result); + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::RetargetDeliveryTo(nsISerialEventTarget* aEventTarget) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mRequest) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr<nsIThreadRetargetableRequest> req; + if (mAllowThreadRetargeting) { + req = do_QueryInterface(mRequest); + } + + NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED); + + return req->RetargetDeliveryTo(aEventTarget); +} + +NS_IMETHODIMP +nsBaseChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) { + MOZ_ASSERT(NS_IsMainThread()); + + NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsIThreadRetargetableRequest> req; + req = do_QueryInterface(mRequest); + + NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED); + return req->GetDeliveryTarget(aEventTarget); +} + +NS_IMETHODIMP +nsBaseChannel::CheckListenerChain() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mAllowThreadRetargeting) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsCOMPtr<nsIThreadRetargetableStreamListener> listener = + do_QueryInterface(mListener); + if (!listener) { + return NS_ERROR_NO_INTERFACE; + } + + return listener->CheckListenerChain(); +} + +NS_IMETHODIMP +nsBaseChannel::OnDataFinished(nsresult aStatus) { + if (!mListener) { + return NS_ERROR_FAILURE; + } + + if (!mAllowThreadRetargeting) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsCOMPtr<nsIThreadRetargetableStreamListener> listener = + do_QueryInterface(mListener); + if (listener) { + return listener->OnDataFinished(aStatus); + } + + return NS_OK; +} + +NS_IMETHODIMP nsBaseChannel::GetCanceled(bool* aCanceled) { + *aCanceled = mCanceled; + return NS_OK; +} + +void nsBaseChannel::SetupNeckoTarget() { + mNeckoTarget = GetMainThreadSerialEventTarget(); +} + +nsBaseChannel::ContentRange::ContentRange(const nsACString& aRangeHeader, + uint64_t aSize) + : mStart(0), mEnd(0), mSize(0) { + auto parsed = nsContentUtils::ParseSingleRangeRequest(aRangeHeader, true); + // https://fetch.spec.whatwg.org/#ref-for-simple-range-header-value%E2%91%A1 + // If rangeValue is failure, then return a network error. + if (!parsed) { + return; + } + + // Sanity check: ParseSingleRangeRequest should handle these two cases. + // If rangeEndValue and rangeStartValue are null, then return failure. + MOZ_ASSERT(parsed->Start().isSome() || parsed->End().isSome()); + // If rangeStartValue and rangeEndValue are numbers, and rangeStartValue + // is greater than rangeEndValue, then return failure. + MOZ_ASSERT(parsed->Start().isNothing() || parsed->End().isNothing() || + *parsed->Start() <= *parsed->End()); + + // https://fetch.spec.whatwg.org/#ref-for-simple-range-header-value%E2%91%A1 + // If rangeStart is null: + if (parsed->Start().isNothing()) { + // Set rangeStart to fullLength − rangeEnd. + mStart = aSize - *parsed->End(); + + // Set rangeEnd to rangeStart + rangeEnd − 1. + mEnd = mStart + *parsed->End() - 1; + + // Otherwise: + } else { + // If rangeStart is greater than or equal to fullLength, then return a + // network error. + if (*parsed->Start() >= aSize) { + return; + } + mStart = *parsed->Start(); + + // If rangeEnd is null or rangeEnd is greater than or equal to fullLength, + // then set rangeEnd to fullLength − 1. + if (parsed->End().isNothing() || *parsed->End() >= aSize) { + mEnd = aSize - 1; + } else { + mEnd = *parsed->End(); + } + } + mSize = aSize; +} + +void nsBaseChannel::ContentRange::AsHeader(nsACString& aOutString) const { + aOutString.Assign("bytes "_ns); + aOutString.AppendInt(mStart); + aOutString.AppendLiteral("-"); + aOutString.AppendInt(mEnd); + aOutString.AppendLiteral("/"); + aOutString.AppendInt(mSize); +} diff --git a/netwerk/base/nsBaseChannel.h b/netwerk/base/nsBaseChannel.h new file mode 100644 index 0000000000..179a24bf45 --- /dev/null +++ b/netwerk/base/nsBaseChannel.h @@ -0,0 +1,348 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBaseChannel_h__ +#define nsBaseChannel_h__ + +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/net/NeckoTargetHolder.h" +#include "mozilla/net/PrivateBrowsingChannel.h" +#include "nsHashPropertyBag.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIChannel.h" +#include "nsIInterfaceRequestor.h" +#include "nsILoadGroup.h" +#include "nsILoadInfo.h" +#include "nsIProgressEventSink.h" +#include "nsIStreamListener.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsITransport.h" +#include "nsITransportSecurityInfo.h" +#include "nsIURI.h" +#include "nsInputStreamPump.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsCOMPtr.h" + +class nsIInputStream; +class nsICancelable; + +//----------------------------------------------------------------------------- +// nsBaseChannel is designed to be subclassed. The subclass is responsible for +// implementing the OpenContentStream method, which will be called by the +// nsIChannel::AsyncOpen and nsIChannel::Open implementations. +// +// nsBaseChannel implements nsIInterfaceRequestor to provide a convenient way +// for subclasses to query both the nsIChannel::notificationCallbacks and +// nsILoadGroup::notificationCallbacks for supported interfaces. +// +// nsBaseChannel implements nsITransportEventSink to support progress & status +// notifications generated by the transport layer. + +class nsBaseChannel + : public nsHashPropertyBag, + public nsIChannel, + public nsIThreadRetargetableRequest, + public nsIInterfaceRequestor, + public nsITransportEventSink, + public nsIAsyncVerifyRedirectCallback, + public mozilla::net::PrivateBrowsingChannel<nsBaseChannel>, + public mozilla::net::NeckoTargetHolder, + protected nsIThreadRetargetableStreamListener { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIREQUEST + NS_DECL_NSICHANNEL + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSITRANSPORTEVENTSINK + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + NS_DECL_NSITHREADRETARGETABLEREQUEST + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + + nsBaseChannel(); + + protected: + // ----------------------------------------------- + // Methods to be implemented by the derived class: + + virtual ~nsBaseChannel(); + + using BlockingPromise = mozilla::MozPromise<nsresult, nsresult, true>; + + private: + // Implemented by subclass to supply data stream. The parameter, async, is + // true when called from nsIChannel::AsyncOpen and false otherwise. When + // async is true, the resulting stream will be used with a nsIInputStreamPump + // instance. This means that if it is a non-blocking stream that supports + // nsIAsyncInputStream that it will be read entirely on the main application + // thread, and its AsyncWait method will be called whenever ReadSegments + // returns NS_BASE_STREAM_WOULD_BLOCK. Otherwise, if the stream is blocking, + // then it will be read on one of the background I/O threads, and it does not + // need to implement ReadSegments. If async is false, this method may return + // NS_ERROR_NOT_IMPLEMENTED to cause the basechannel to implement Open in + // terms of AsyncOpen (see NS_ImplementChannelOpen). + // A callee is allowed to return an nsIChannel instead of an nsIInputStream. + // That case will be treated as a redirect to the new channel. By default + // *channel will be set to null by the caller, so callees who don't want to + // return one an just not touch it. + virtual nsresult OpenContentStream(bool async, nsIInputStream** stream, + nsIChannel** channel) = 0; + + // Implemented by subclass to begin pumping data for an async channel, in + // lieu of returning a stream. If implemented, OpenContentStream will never + // be called for async channels. If not implemented, AsyncOpen will fall + // back to OpenContentStream. + // + // On success, the callee must begin pumping data to the stream listener, + // and at some point call OnStartRequest followed by OnStopRequest. + // + // Additionally, when a successful nsresult is returned, then the subclass + // should be setting through its two out params either: + // - a request object, which may be used to suspend, resume, and cancel + // the underlying request. + // - or a cancelable object (e.g. when a request can't be returned right away + // due to some async work needed to retrieve it). which may be used to + // cancel the underlying request (e.g. because the channel has been + // canceled) + // + // Not returning a request or cancelable leads to potentially leaking the + // an underling stream pump (which would keep to be pumping data even after + // the channel has been canceled and nothing is going to handle the data + // available, e.g. see Bug 1706594). + virtual nsresult BeginAsyncRead(nsIStreamListener* listener, + nsIRequest** request, + nsICancelable** cancelableRequest) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // This method may return a promise that will keep the input stream pump + // suspended until the promise is resolved or rejected. On resolution the + // pump is resumed. On rejection the channel is canceled with the resulting + // error and then the pump is also resumed to propagate the error to the + // channel listener. Use it to do any asynchronous/background tasks you need + // to finish prior calling OnStartRequest of the listener. This method is + // called right after OpenContentStream() with async == true, after the input + // stream pump has already been called asyncRead(). + virtual nsresult ListenerBlockingPromise(BlockingPromise** aPromise) { + NS_ENSURE_ARG(aPromise); + *aPromise = nullptr; + return NS_OK; + } + + // The basechannel calls this method from its OnTransportStatus method to + // determine whether to call nsIProgressEventSink::OnStatus in addition to + // nsIProgressEventSink::OnProgress. This method may be overriden by the + // subclass to enable nsIProgressEventSink::OnStatus events. If this method + // returns true, then the statusArg out param specifies the "statusArg" value + // to pass to the OnStatus method. By default, OnStatus messages are + // suppressed. The status parameter passed to this method is the status value + // from the OnTransportStatus method. + virtual bool GetStatusArg(nsresult status, nsString& statusArg) { + return false; + } + + // Called when the callbacks available to this channel may have changed. + virtual void OnCallbacksChanged() {} + + // Called when our channel is done, to allow subclasses to drop resources. + virtual void OnChannelDone() {} + + public: + // ---------------------------------------------- + // Methods provided for use by the derived class: + + // Redirect to another channel. This method takes care of notifying + // observers of this redirect as well as of opening the new channel, if asked + // to do so. It also cancels |this| with the status code + // NS_BINDING_REDIRECTED. A failure return from this method means that the + // redirect could not be performed (no channel was opened; this channel + // wasn't canceled.) The redirectFlags parameter consists of the flag values + // defined on nsIChannelEventSink. + nsresult Redirect(nsIChannel* newChannel, uint32_t redirectFlags, + bool openNewChannel); + + // Tests whether a type hint was set. Subclasses can use this to decide + // whether to call SetContentType. + // NOTE: This is only reliable if the subclass didn't itself call + // SetContentType, and should also not be called after OpenContentStream. + bool HasContentTypeHint() const; + + // The URI member should be initialized before the channel is used, and then + // it should never be changed again until the channel is destroyed. + nsIURI* URI() { return mURI; } + void SetURI(nsIURI* uri) { + NS_ASSERTION(uri, "must specify a non-null URI"); + NS_ASSERTION(!mURI, "must not modify URI"); + NS_ASSERTION(!mOriginalURI, "how did that get set so early?"); + mURI = uri; + mOriginalURI = uri; + } + nsIURI* OriginalURI() { return mOriginalURI; } + + // The security info is a property of the transport-layer, which should be + // assigned by the subclass. + nsITransportSecurityInfo* SecurityInfo() { return mSecurityInfo; } + void SetSecurityInfo(nsITransportSecurityInfo* info) { mSecurityInfo = info; } + + // Test the load flags + bool HasLoadFlag(uint32_t flag) { return (mLoadFlags & flag) != 0; } + + // This is a short-cut to calling nsIRequest::IsPending() + virtual bool Pending() const { + return mPumpingData || mWaitingOnAsyncRedirect; + } + + // Blob requests may specify a range header. We must parse, validate, and + // store that info in a place where BlobURLInputStream::StoreBlobImplStream + // can access it. This class helps to encapsulate that logic. + class ContentRange { + private: + uint64_t mStart; + uint64_t mEnd; + uint64_t mSize; + + public: + uint64_t Start() const { return mStart; } + uint64_t End() const { return mEnd; } + uint64_t Size() const { return mSize; } + bool IsValid() const { return mStart < mSize; } + ContentRange() : mStart(0), mEnd(0), mSize(0) {} + ContentRange(uint64_t aStart, uint64_t aEnd, uint64_t aSize) + : mStart(aStart), mEnd(aEnd), mSize(aSize) {} + ContentRange(const nsACString& aRangeHeader, uint64_t aSize); + void AsHeader(nsACString& aOutString) const; + }; + + const mozilla::Maybe<ContentRange>& GetContentRange() const { + return mContentRange; + } + + void SetContentRange(uint64_t aStart, uint64_t aEnd, uint64_t aSize) { + mContentRange.emplace(ContentRange(aStart, aEnd, aSize)); + } + + bool SetContentRange(const nsACString& aRangeHeader, uint64_t aSize) { + auto range = ContentRange(aRangeHeader, aSize); + if (!range.IsValid()) { + return false; + } + mContentRange.emplace(range); + return true; + } + + // Helper function for querying the channel's notification callbacks. + template <class T> + void GetCallback(nsCOMPtr<T>& result) { + GetInterface(NS_GET_TEMPLATE_IID(T), getter_AddRefs(result)); + } + + // If a subclass does not want to feed transport-layer progress events to the + // base channel via nsITransportEventSink, then it may set this flag to cause + // the base channel to synthesize progress events when it receives data from + // the content stream. By default, progress events are not synthesized. + void EnableSynthesizedProgressEvents(bool enable) { + mSynthProgressEvents = enable; + } + + // Some subclasses may wish to manually insert a stream listener between this + // and the channel's listener. The following methods make that possible. + void SetStreamListener(nsIStreamListener* listener) { mListener = listener; } + nsIStreamListener* StreamListener() { return mListener; } + + protected: + void DisallowThreadRetargeting() { mAllowThreadRetargeting = false; } + + virtual void SetupNeckoTarget(); + + private: + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + // Called to setup mPump and call AsyncRead on it. + nsresult BeginPumpingData(); + + // Called when the callbacks available to this channel may have changed. + void CallbacksChanged() { + mProgressSink = nullptr; + mQueriedProgressSink = false; + OnCallbacksChanged(); + } + + // Called when our channel is done. This should drop no-longer-needed + // pointers. + void ChannelDone() { + mListener = nullptr; + OnChannelDone(); + } + + // Handle an async redirect callback. This will only be called if we + // returned success from AsyncOpen while posting a redirect runnable. + void HandleAsyncRedirect(nsIChannel* newChannel); + void ContinueHandleAsyncRedirect(nsresult result); + nsresult ContinueRedirect(); + + // start URI classifier if requested + void ClassifyURI(); + + class RedirectRunnable : public mozilla::Runnable { + public: + RedirectRunnable(nsBaseChannel* chan, nsIChannel* newChannel) + : mozilla::Runnable("nsBaseChannel::RedirectRunnable"), + mChannel(chan), + mNewChannel(newChannel) { + MOZ_ASSERT(newChannel, "Must have channel to redirect to"); + } + + NS_IMETHOD Run() override { + mChannel->HandleAsyncRedirect(mNewChannel); + return NS_OK; + } + + private: + RefPtr<nsBaseChannel> mChannel; + nsCOMPtr<nsIChannel> mNewChannel; + }; + friend class RedirectRunnable; + + RefPtr<nsInputStreamPump> mPump; + RefPtr<nsIRequest> mRequest; + nsCOMPtr<nsICancelable> mCancelableAsyncRequest; + bool mPumpingData{false}; + nsCOMPtr<nsIProgressEventSink> mProgressSink; + nsCOMPtr<nsIURI> mOriginalURI; + nsCOMPtr<nsISupports> mOwner; + nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo; + nsCOMPtr<nsIChannel> mRedirectChannel; + uint32_t mLoadFlags{LOAD_NORMAL}; + bool mQueriedProgressSink{true}; + bool mSynthProgressEvents{false}; + bool mAllowThreadRetargeting{true}; + bool mWaitingOnAsyncRedirect{false}; + bool mOpenRedirectChannel{false}; + uint32_t mRedirectFlags{0}; + mozilla::Maybe<ContentRange> mContentRange; + + protected: + nsCString mContentType; + nsCString mContentCharset; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsILoadGroup> mLoadGroup; + nsCOMPtr<nsILoadInfo> mLoadInfo; + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; + nsCOMPtr<nsIStreamListener> mListener; + nsresult mStatus{NS_OK}; + uint32_t mContentDispositionHint{UINT32_MAX}; + mozilla::UniquePtr<nsString> mContentDispositionFilename; + int64_t mContentLength{-1}; + bool mWasOpened{false}; + bool mCanceled{false}; + + friend class mozilla::net::PrivateBrowsingChannel<nsBaseChannel>; +}; + +#endif // !nsBaseChannel_h__ diff --git a/netwerk/base/nsBaseContentStream.cpp b/netwerk/base/nsBaseContentStream.cpp new file mode 100644 index 0000000000..d59145f463 --- /dev/null +++ b/netwerk/base/nsBaseContentStream.cpp @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBaseContentStream.h" +#include "nsStreamUtils.h" + +//----------------------------------------------------------------------------- + +void nsBaseContentStream::DispatchCallback(bool async) { + if (!mCallback) return; + + // It's important to clear mCallback and mCallbackTarget up-front because the + // OnInputStreamReady implementation may call our AsyncWait method. + + nsCOMPtr<nsIInputStreamCallback> callback; + if (async) { + callback = NS_NewInputStreamReadyEvent( + "nsBaseContentStream::DispatchCallback", mCallback, mCallbackTarget); + mCallback = nullptr; + } else { + callback.swap(mCallback); + } + mCallbackTarget = nullptr; + + callback->OnInputStreamReady(this); +} + +//----------------------------------------------------------------------------- +// nsBaseContentStream::nsISupports + +NS_IMPL_ADDREF(nsBaseContentStream) +NS_IMPL_RELEASE(nsBaseContentStream) + +// We only support nsIAsyncInputStream when we are in non-blocking mode. +NS_INTERFACE_MAP_BEGIN(nsBaseContentStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mNonBlocking) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +//----------------------------------------------------------------------------- +// nsBaseContentStream::nsIInputStream + +NS_IMETHODIMP +nsBaseContentStream::Close() { + return IsClosed() ? NS_OK : CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP +nsBaseContentStream::Available(uint64_t* result) { + *result = 0; + return mStatus; +} + +NS_IMETHODIMP +nsBaseContentStream::StreamStatus() { return mStatus; } + +NS_IMETHODIMP +nsBaseContentStream::Read(char* buf, uint32_t count, uint32_t* result) { + return ReadSegments(NS_CopySegmentToBuffer, buf, count, result); +} + +NS_IMETHODIMP +nsBaseContentStream::ReadSegments(nsWriteSegmentFun fun, void* closure, + uint32_t count, uint32_t* result) { + *result = 0; + + if (mStatus == NS_BASE_STREAM_CLOSED) return NS_OK; + + // No data yet + if (!IsClosed() && IsNonBlocking()) return NS_BASE_STREAM_WOULD_BLOCK; + + return mStatus; +} + +NS_IMETHODIMP +nsBaseContentStream::IsNonBlocking(bool* result) { + *result = mNonBlocking; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsBaseContentStream::nsIAsyncInputStream + +NS_IMETHODIMP +nsBaseContentStream::CloseWithStatus(nsresult status) { + if (IsClosed()) return NS_OK; + + NS_ENSURE_ARG(NS_FAILED(status)); + mStatus = status; + + DispatchCallback(); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseContentStream::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags, + uint32_t requestedCount, + nsIEventTarget* target) { + // Our _only_ consumer is nsInputStreamPump, so we simplify things here by + // making assumptions about how we will be called. + NS_ASSERTION(target, "unexpected parameter"); + NS_ASSERTION(flags == 0, "unexpected parameter"); + NS_ASSERTION(requestedCount == 0, "unexpected parameter"); + +#ifdef DEBUG + bool correctThread; + target->IsOnCurrentThread(&correctThread); + NS_ASSERTION(correctThread, "event target must be on the current thread"); +#endif + + mCallback = callback; + mCallbackTarget = target; + + if (!mCallback) return NS_OK; + + // If we're already closed, then dispatch this callback immediately. + if (IsClosed()) { + DispatchCallback(); + return NS_OK; + } + + OnCallbackPending(); + return NS_OK; +} diff --git a/netwerk/base/nsBaseContentStream.h b/netwerk/base/nsBaseContentStream.h new file mode 100644 index 0000000000..13a3766857 --- /dev/null +++ b/netwerk/base/nsBaseContentStream.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBaseContentStream_h__ +#define nsBaseContentStream_h__ + +#include "nsIAsyncInputStream.h" +#include "nsIEventTarget.h" +#include "nsCOMPtr.h" + +//----------------------------------------------------------------------------- +// nsBaseContentStream is designed to be subclassed with the intention of being +// used to satisfy the nsBaseChannel::OpenContentStream method. +// +// The subclass typically overrides the default Available, ReadSegments and +// CloseWithStatus methods. By default, Read is implemented in terms of +// ReadSegments, and Close is implemented in terms of CloseWithStatus. If +// CloseWithStatus is overriden, then the subclass will usually want to call +// the base class' CloseWithStatus method before returning. +// +// If the stream is non-blocking, then readSegments may return the exception +// NS_BASE_STREAM_WOULD_BLOCK if there is no data available and the stream is +// not at the "end-of-file" or already closed. This error code must not be +// returned from the Available implementation. When the caller receives this +// error code, he may choose to call the stream's AsyncWait method, in which +// case the base stream will have a non-null PendingCallback. When the stream +// has data or encounters an error, it should be sure to dispatch a pending +// callback if one exists (see DispatchCallback). The implementation of the +// base stream's CloseWithStatus (and Close) method will ensure that any +// pending callback is dispatched. It is the responsibility of the subclass +// to ensure that the pending callback is dispatched when it wants to have its +// ReadSegments method called again. + +class nsBaseContentStream : public nsIAsyncInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + explicit nsBaseContentStream(bool nonBlocking) + : mStatus(NS_OK), mNonBlocking(nonBlocking) {} + + nsresult Status() { return mStatus; } + bool IsNonBlocking() { return mNonBlocking; } + bool IsClosed() { return NS_FAILED(mStatus); } + + // Called to test if the stream has a pending callback. + bool HasPendingCallback() { return mCallback != nullptr; } + + // The current dispatch target (may be null) for the pending callback if any. + nsIEventTarget* CallbackTarget() { return mCallbackTarget; } + + // Called to dispatch a pending callback. If there is no pending callback, + // then this function does nothing. Pass true to this function to cause the + // callback to occur asynchronously; otherwise, the callback will happen + // before this function returns. + void DispatchCallback(bool async = true); + + // Helper function to make code more self-documenting. + void DispatchCallbackSync() { DispatchCallback(false); } + + protected: + virtual ~nsBaseContentStream() = default; + + private: + // Called from the base stream's AsyncWait method when a pending callback + // is installed on the stream. + virtual void OnCallbackPending() {} + + private: + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mCallbackTarget; + nsresult mStatus; + bool mNonBlocking; +}; + +#endif // nsBaseContentStream_h__ diff --git a/netwerk/base/nsBufferedStreams.cpp b/netwerk/base/nsBufferedStreams.cpp new file mode 100644 index 0000000000..2606352cc4 --- /dev/null +++ b/netwerk/base/nsBufferedStreams.cpp @@ -0,0 +1,1197 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsBufferedStreams.h" +#include "nsStreamUtils.h" +#include "nsNetCID.h" +#include "nsIClassInfoImpl.h" +#include "nsIEventTarget.h" +#include "nsThreadUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include <algorithm> + +#ifdef DEBUG_brendan +# define METERING +#endif + +#ifdef METERING +# include <stdio.h> +# define METER(x) x +# define MAX_BIG_SEEKS 20 + +static struct { + uint32_t mSeeksWithinBuffer; + uint32_t mSeeksOutsideBuffer; + uint32_t mBufferReadUponSeek; + uint32_t mBufferUnreadUponSeek; + uint32_t mBytesReadFromBuffer; + uint32_t mBigSeekIndex; + struct { + int64_t mOldOffset; + int64_t mNewOffset; + } mBigSeek[MAX_BIG_SEEKS]; +} bufstats; +#else +# define METER(x) /* nothing */ +#endif + +using namespace mozilla::ipc; +using namespace mozilla; + +//////////////////////////////////////////////////////////////////////////////// +// nsBufferedStream + +nsBufferedStream::~nsBufferedStream() { Close(); } + +NS_IMPL_ADDREF(nsBufferedStream) +NS_IMPL_RELEASE(nsBufferedStream) + +NS_INTERFACE_MAP_BEGIN(nsBufferedStream) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsITellableStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, mSeekable) +NS_INTERFACE_MAP_END + +nsresult nsBufferedStream::Init(nsISupports* aStream, uint32_t bufferSize) { + NS_ASSERTION(aStream, "need to supply a stream"); + NS_ASSERTION(mStream == nullptr, "already inited"); + mStream = aStream; // we keep a reference until nsBufferedStream::Close + mBufferSize = bufferSize; + mBufferStartOffset = 0; + mCursor = 0; + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream); + mSeekable = seekable; + RecursiveMutexAutoLock lock(mBufferMutex); + mBuffer = new (mozilla::fallible) char[bufferSize]; + if (mBuffer == nullptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void nsBufferedStream::Close() { + // Drop the reference from nsBufferedStream::Init() + mStream = nullptr; + RecursiveMutexAutoLock lock(mBufferMutex); + if (mBuffer) { + delete[] mBuffer; + mBuffer = nullptr; + mBufferSize = 0; + mBufferStartOffset = 0; + mCursor = 0; + mFillPoint = 0; + } +#ifdef METERING + { + static FILE* tfp; + if (!tfp) { + tfp = fopen("/tmp/bufstats", "w"); + if (tfp) { + setvbuf(tfp, nullptr, _IOLBF, 0); + } + } + if (tfp) { + fprintf(tfp, "seeks within buffer: %u\n", bufstats.mSeeksWithinBuffer); + fprintf(tfp, "seeks outside buffer: %u\n", + bufstats.mSeeksOutsideBuffer); + fprintf(tfp, "buffer read on seek: %u\n", + bufstats.mBufferReadUponSeek); + fprintf(tfp, "buffer unread on seek: %u\n", + bufstats.mBufferUnreadUponSeek); + fprintf(tfp, "bytes read from buffer: %u\n", + bufstats.mBytesReadFromBuffer); + for (uint32_t i = 0; i < bufstats.mBigSeekIndex; i++) { + fprintf(tfp, "bigseek[%u] = {old: %u, new: %u}\n", i, + bufstats.mBigSeek[i].mOldOffset, + bufstats.mBigSeek[i].mNewOffset); + } + } + } +#endif +} + +NS_IMETHODIMP +nsBufferedStream::Seek(int32_t whence, int64_t offset) { + if (mStream == nullptr) { + return NS_BASE_STREAM_CLOSED; + } + + // If the underlying stream isn't a random access store, then fail early. + // We could possibly succeed for the case where the seek position denotes + // something that happens to be read into the buffer, but that would make + // the failure data-dependent. + nsresult rv; + nsCOMPtr<nsISeekableStream> ras = do_QueryInterface(mStream, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("mStream doesn't QI to nsISeekableStream"); + return rv; + } + + int64_t absPos = 0; + switch (whence) { + case nsISeekableStream::NS_SEEK_SET: + absPos = offset; + break; + case nsISeekableStream::NS_SEEK_CUR: + absPos = mBufferStartOffset; + absPos += mCursor; + absPos += offset; + break; + case nsISeekableStream::NS_SEEK_END: + absPos = -1; + break; + default: + MOZ_ASSERT_UNREACHABLE("bogus seek whence parameter"); + return NS_ERROR_UNEXPECTED; + } + + // Let mCursor point into the existing buffer if the new position is + // between the current cursor and the mFillPoint "fencepost" -- the + // client may never get around to a Read or Write after this Seek. + // Read and Write worry about flushing and filling in that event. + // But if we're at EOF, make sure to pass the seek through to the + // underlying stream, because it may have auto-closed itself and + // needs to reopen. + uint32_t offsetInBuffer = uint32_t(absPos - mBufferStartOffset); + if (offsetInBuffer <= mFillPoint && !mEOF) { + METER(bufstats.mSeeksWithinBuffer++); + mCursor = offsetInBuffer; + return NS_OK; + } + + METER(bufstats.mSeeksOutsideBuffer++); + METER(bufstats.mBufferReadUponSeek += mCursor); + METER(bufstats.mBufferUnreadUponSeek += mFillPoint - mCursor); + rv = Flush(); + if (NS_FAILED(rv)) { +#ifdef DEBUG + NS_WARNING( + "(debug) Flush returned error within nsBufferedStream::Seek, so we " + "exit early."); +#endif + return rv; + } + + rv = ras->Seek(whence, offset); + if (NS_FAILED(rv)) { +#ifdef DEBUG + NS_WARNING( + "(debug) Error: ras->Seek() returned error within " + "nsBufferedStream::Seek, so we exit early."); +#endif + return rv; + } + + mEOF = false; + + // Recompute whether the offset we're seeking to is in our buffer. + // Note that we need to recompute because Flush() might have + // changed mBufferStartOffset. + offsetInBuffer = uint32_t(absPos - mBufferStartOffset); + if (offsetInBuffer <= mFillPoint) { + // It's safe to just set mCursor to offsetInBuffer. In particular, we + // want to avoid calling Fill() here since we already have the data that + // was seeked to and calling Fill() might auto-close our underlying + // stream in some cases. + mCursor = offsetInBuffer; + return NS_OK; + } + + METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS) + bufstats.mBigSeek[bufstats.mBigSeekIndex] + .mOldOffset = mBufferStartOffset + int64_t(mCursor)); + const int64_t minus1 = -1; + if (absPos == minus1) { + // then we had the SEEK_END case, above + int64_t tellPos; + rv = ras->Tell(&tellPos); + mBufferStartOffset = tellPos; + if (NS_FAILED(rv)) { + return rv; + } + } else { + mBufferStartOffset = absPos; + } + METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS) + bufstats.mBigSeek[bufstats.mBigSeekIndex++] + .mNewOffset = mBufferStartOffset); + + mFillPoint = mCursor = 0; + + // If we seeked back to the start, then don't fill the buffer + // right now in case this is a lazily-opened file stream. + // We'll fill on the first read, like we did initially. + if (whence == nsISeekableStream::NS_SEEK_SET && offset == 0) { + return NS_OK; + } + return Fill(); +} + +NS_IMETHODIMP +nsBufferedStream::Tell(int64_t* result) { + if (mStream == nullptr) { + return NS_BASE_STREAM_CLOSED; + } + + int64_t result64 = mBufferStartOffset; + result64 += mCursor; + *result = result64; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedStream::SetEOF() { + if (mStream == nullptr) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv; + nsCOMPtr<nsISeekableStream> ras = do_QueryInterface(mStream, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ras->SetEOF(); + if (NS_SUCCEEDED(rv)) { + mEOF = true; + } + + return rv; +} + +nsresult nsBufferedStream::GetData(nsISupports** aResult) { + nsCOMPtr<nsISupports> stream(mStream); + stream.forget(aResult); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsBufferedInputStream + +NS_IMPL_ADDREF_INHERITED(nsBufferedInputStream, nsBufferedStream) +NS_IMPL_RELEASE_INHERITED(nsBufferedInputStream, nsBufferedStream) + +NS_IMPL_CLASSINFO(nsBufferedInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_BUFFEREDINPUTSTREAM_CID) + +NS_INTERFACE_MAP_BEGIN(nsBufferedInputStream) + // Unfortunately there isn't a macro that combines ambiguous and conditional, + // and as far as I can tell, no other class would need such a macro. + if (mIsAsyncInputStream && aIID.Equals(NS_GET_IID(nsIInputStream))) { + foundInterface = + static_cast<nsIInputStream*>(static_cast<nsIAsyncInputStream*>(this)); + } else if (!mIsAsyncInputStream && aIID.Equals(NS_GET_IID(nsIInputStream))) { + foundInterface = static_cast<nsIInputStream*>( + static_cast<nsIBufferedInputStream*>(this)); + } else + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIBufferedInputStream) + NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream) + NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, + mIsIPCSerializable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mIsAsyncInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, + mIsAsyncInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, + mIsCloneableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, mIsInputStreamLength) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, + mIsAsyncInputStreamLength) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback, + mIsAsyncInputStreamLength) + NS_IMPL_QUERY_CLASSINFO(nsBufferedInputStream) +NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream) + +NS_IMPL_CI_INTERFACE_GETTER(nsBufferedInputStream, nsIInputStream, + nsIBufferedInputStream, nsISeekableStream, + nsITellableStream, nsIStreamBufferAccess) + +nsresult nsBufferedInputStream::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsBufferedInputStream> stream = new nsBufferedInputStream(); + return stream->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsBufferedInputStream::Init(nsIInputStream* stream, uint32_t bufferSize) { + nsresult rv = nsBufferedStream::Init(stream, bufferSize); + NS_ENSURE_SUCCESS(rv, rv); + + { + nsCOMPtr<nsIIPCSerializableInputStream> stream = do_QueryInterface(mStream); + mIsIPCSerializable = !!stream; + } + + { + nsCOMPtr<nsIAsyncInputStream> stream = do_QueryInterface(mStream); + mIsAsyncInputStream = !!stream; + } + + { + nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream); + mIsCloneableInputStream = !!stream; + } + + { + nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream); + mIsInputStreamLength = !!stream; + } + + { + nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream); + mIsAsyncInputStreamLength = !!stream; + } + + return NS_OK; +} + +already_AddRefed<nsIInputStream> nsBufferedInputStream::GetInputStream() { + // A non-null mStream implies Init() has been called. + MOZ_ASSERT(mStream); + + nsIInputStream* out = nullptr; + DebugOnly<nsresult> rv = QueryInterface(NS_GET_IID(nsIInputStream), + reinterpret_cast<void**>(&out)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(out); + + return already_AddRefed<nsIInputStream>(out); +} + +NS_IMETHODIMP +nsBufferedInputStream::Close() { + nsresult rv = NS_OK; + if (mStream) { + rv = Source()->Close(); + if (NS_FAILED(rv)) { + NS_WARNING( + "(debug) Error: Source()->Close() returned error in " + "bsBuffedInputStream::Close()."); + } + } + + nsBufferedStream::Close(); + return rv; +} + +NS_IMETHODIMP +nsBufferedInputStream::Available(uint64_t* result) { + *result = 0; + + if (!mStream) { + return NS_OK; + } + + uint64_t avail = mFillPoint - mCursor; + + uint64_t tmp; + nsresult rv = Source()->Available(&tmp); + if (NS_SUCCEEDED(rv)) { + avail += tmp; + } + + if (avail) { + *result = avail; + return NS_OK; + } + + return rv; +} + +NS_IMETHODIMP +nsBufferedInputStream::StreamStatus() { + if (!mStream) { + return NS_OK; + } + + if (mFillPoint - mCursor) { + return NS_OK; + } + + return Source()->StreamStatus(); +} + +NS_IMETHODIMP +nsBufferedInputStream::Read(char* buf, uint32_t count, uint32_t* result) { + if (mBufferDisabled) { + if (!mStream) { + *result = 0; + return NS_OK; + } + nsresult rv = Source()->Read(buf, count, result); + if (NS_SUCCEEDED(rv)) { + mBufferStartOffset += *result; // so nsBufferedStream::Tell works + if (*result == 0) { + mEOF = true; + } + } + return rv; + } + + return ReadSegments(NS_CopySegmentToBuffer, buf, count, result); +} + +NS_IMETHODIMP +nsBufferedInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t count, uint32_t* result) { + *result = 0; + + if (!mStream) { + return NS_OK; + } + + nsresult rv = NS_OK; + RecursiveMutexAutoLock lock(mBufferMutex); + while (count > 0) { + uint32_t amt = std::min(count, mFillPoint - mCursor); + if (amt > 0) { + uint32_t read = 0; + rv = writer(static_cast<nsIBufferedInputStream*>(this), closure, + mBuffer + mCursor, *result, amt, &read); + if (NS_FAILED(rv)) { + // errors returned from the writer end here! + rv = NS_OK; + break; + } + *result += read; + count -= read; + mCursor += read; + } else { + rv = Fill(); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + break; + } + if (NS_FAILED(rv)) { + return rv; + } + if (mFillPoint == mCursor) { + break; + } + } + } + return (*result > 0) ? NS_OK : rv; +} + +NS_IMETHODIMP +nsBufferedInputStream::IsNonBlocking(bool* aNonBlocking) { + if (mStream) { + return Source()->IsNonBlocking(aNonBlocking); + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsBufferedInputStream::Fill() { + if (mBufferDisabled) { + return NS_OK; + } + NS_ENSURE_TRUE(mStream, NS_ERROR_NOT_INITIALIZED); + + RecursiveMutexAutoLock lock(mBufferMutex); + + nsresult rv; + int32_t rem = int32_t(mFillPoint - mCursor); + if (rem > 0) { + // slide the remainder down to the start of the buffer + // |<------------->|<--rem-->|<--->| + // b c f s + memcpy(mBuffer, mBuffer + mCursor, rem); + } + mBufferStartOffset += mCursor; + mFillPoint = rem; + mCursor = 0; + + uint32_t amt; + rv = Source()->Read(mBuffer + mFillPoint, mBufferSize - mFillPoint, &amt); + if (NS_FAILED(rv)) { + return rv; + } + + if (amt == 0) { + mEOF = true; + } + + mFillPoint += amt; + return NS_OK; +} + +NS_IMETHODIMP_(char*) +nsBufferedInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) { + NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!"); + if (mGetBufferCount != 0) { + return nullptr; + } + + if (mBufferDisabled) { + return nullptr; + } + + RecursiveMutexAutoLock lock(mBufferMutex); + char* buf = mBuffer + mCursor; + uint32_t rem = mFillPoint - mCursor; + if (rem == 0) { + if (NS_FAILED(Fill())) { + return nullptr; + } + buf = mBuffer + mCursor; + rem = mFillPoint - mCursor; + } + + uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask); + if (mod) { + uint32_t pad = aAlignMask + 1 - mod; + if (pad > rem) { + return nullptr; + } + + memset(buf, 0, pad); + mCursor += pad; + buf += pad; + rem -= pad; + } + + if (aLength > rem) { + return nullptr; + } + mGetBufferCount++; + return buf; +} + +NS_IMETHODIMP_(void) +nsBufferedInputStream::PutBuffer(char* aBuffer, uint32_t aLength) { + NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!"); + if (--mGetBufferCount != 0) { + return; + } + + NS_ASSERTION(mCursor + aLength <= mFillPoint, "PutBuffer botch"); + mCursor += aLength; +} + +NS_IMETHODIMP +nsBufferedInputStream::DisableBuffering() { + NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!"); + NS_ASSERTION(mGetBufferCount == 0, + "DisableBuffer call between GetBuffer and PutBuffer!"); + if (mGetBufferCount != 0) { + return NS_ERROR_UNEXPECTED; + } + + // Empty the buffer so nsBufferedStream::Tell works. + mBufferStartOffset += mCursor; + mFillPoint = mCursor = 0; + mBufferDisabled = true; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedInputStream::EnableBuffering() { + NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!"); + mBufferDisabled = false; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedInputStream::GetUnbufferedStream(nsISupports** aStream) { + // Empty the buffer so subsequent i/o trumps any buffered data. + mBufferStartOffset += mCursor; + mFillPoint = mCursor = 0; + + nsCOMPtr<nsISupports> stream = mStream; + stream.forget(aStream); + return NS_OK; +} + +void nsBufferedInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + if (mStream) { + nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream); + MOZ_ASSERT(stream); + + InputStreamHelper::SerializedComplexity(stream, aMaxSize, aSizeUsed, aPipes, + aTransferables); + } +} + +void nsBufferedInputStream::Serialize(InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) { + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + BufferedInputStreamParams params; + + if (mStream) { + nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream); + MOZ_ASSERT(stream); + + InputStreamParams wrappedParams; + InputStreamHelper::SerializeInputStream(stream, wrappedParams, aMaxSize, + aSizeUsed); + + params.optionalStream().emplace(wrappedParams); + } + + params.bufferSize() = mBufferSize; + + aParams = params; +} + +bool nsBufferedInputStream::Deserialize(const InputStreamParams& aParams) { + if (aParams.type() != InputStreamParams::TBufferedInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const BufferedInputStreamParams& params = + aParams.get_BufferedInputStreamParams(); + const Maybe<InputStreamParams>& wrappedParams = params.optionalStream(); + + nsCOMPtr<nsIInputStream> stream; + if (wrappedParams.isSome()) { + stream = InputStreamHelper::DeserializeInputStream(wrappedParams.ref()); + if (!stream) { + NS_WARNING("Failed to deserialize wrapped stream!"); + return false; + } + } + + nsresult rv = Init(stream, params.bufferSize()); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + +NS_IMETHODIMP +nsBufferedInputStream::CloseWithStatus(nsresult aStatus) { return Close(); } + +NS_IMETHODIMP +nsBufferedInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + nsCOMPtr<nsIAsyncInputStream> stream = do_QueryInterface(mStream); + if (!stream) { + // Stream is probably closed. Callback, if not nullptr, can be executed + // immediately + if (!aCallback) { + return NS_OK; + } + + if (aEventTarget) { + nsCOMPtr<nsIInputStreamCallback> callable = NS_NewInputStreamReadyEvent( + "nsBufferedInputStream::OnInputStreamReady", aCallback, aEventTarget); + return callable->OnInputStreamReady(this); + } + + aCallback->OnInputStreamReady(this); + return NS_OK; + } + + nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr; + { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + } + + return stream->AsyncWait(callback, aFlags, aRequestedCount, aEventTarget); +} + +NS_IMETHODIMP +nsBufferedInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { + nsCOMPtr<nsIInputStreamCallback> callback; + { + MutexAutoLock lock(mMutex); + + // We have been canceled in the meanwhile. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + callback.swap(mAsyncWaitCallback); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamReady(this); +} + +NS_IMETHODIMP +nsBufferedInputStream::GetData(nsIInputStream** aResult) { + nsCOMPtr<nsISupports> stream; + nsBufferedStream::GetData(getter_AddRefs(stream)); + nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(stream); + inputStream.forget(aResult); + return NS_OK; +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +nsBufferedInputStream::GetCloneable(bool* aCloneable) { + *aCloneable = false; + + RecursiveMutexAutoLock lock(mBufferMutex); + + // If we don't have the buffer, the inputStream has been already closed. + // If mBufferStartOffset is not 0, the stream has been seeked or read. + // In both case the cloning is not supported. + if (!mBuffer || mBufferStartOffset) { + return NS_OK; + } + + nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream); + + // GetCloneable is infallible. + NS_ENSURE_TRUE(stream, NS_OK); + + return stream->GetCloneable(aCloneable); +} + +NS_IMETHODIMP +nsBufferedInputStream::Clone(nsIInputStream** aResult) { + RecursiveMutexAutoLock lock(mBufferMutex); + + if (!mBuffer || mBufferStartOffset) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream); + NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); + + nsCOMPtr<nsIInputStream> clonedStream; + nsresult rv = stream->Clone(getter_AddRefs(clonedStream)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIBufferedInputStream> bis = new nsBufferedInputStream(); + rv = bis->Init(clonedStream, mBufferSize); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = + static_cast<nsBufferedInputStream*>(bis.get())->GetInputStream().take(); + + return NS_OK; +} + +// nsIInputStreamLength + +NS_IMETHODIMP +nsBufferedInputStream::Length(int64_t* aLength) { + nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream); + NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); + + return stream->Length(aLength); +} + +// nsIAsyncInputStreamLength + +NS_IMETHODIMP +nsBufferedInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) { + nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream); + if (!stream) { + // Stream is probably closed. Callback, if not nullptr, can be executed + // immediately + if (aCallback) { + const RefPtr<nsBufferedInputStream> self = this; + const nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback; + nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction( + "nsBufferedInputStream::OnInputStreamLengthReady", + [self, callback] { callback->OnInputStreamLengthReady(self, -1); }); + + if (aEventTarget) { + aEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL); + } else { + runnable->Run(); + } + } + return NS_OK; + } + + nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback ? this : nullptr; + { + MutexAutoLock lock(mMutex); + mAsyncInputStreamLengthCallback = aCallback; + } + + MOZ_ASSERT(stream); + return stream->AsyncLengthWait(callback, aEventTarget); +} + +// nsIInputStreamLengthCallback + +NS_IMETHODIMP +nsBufferedInputStream::OnInputStreamLengthReady( + nsIAsyncInputStreamLength* aStream, int64_t aLength) { + nsCOMPtr<nsIInputStreamLengthCallback> callback; + { + MutexAutoLock lock(mMutex); + // We have been canceled in the meanwhile. + if (!mAsyncInputStreamLengthCallback) { + return NS_OK; + } + + callback.swap(mAsyncInputStreamLengthCallback); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamLengthReady(this, aLength); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsBufferedOutputStream + +NS_IMPL_ADDREF_INHERITED(nsBufferedOutputStream, nsBufferedStream) +NS_IMPL_RELEASE_INHERITED(nsBufferedOutputStream, nsBufferedStream) +// This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for +// non-nullness of mSafeStream. +NS_INTERFACE_MAP_BEGIN(nsBufferedOutputStream) + NS_INTERFACE_MAP_ENTRY(nsIOutputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISafeOutputStream, mSafeStream) + NS_INTERFACE_MAP_ENTRY(nsIBufferedOutputStream) + NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess) +NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream) + +nsresult nsBufferedOutputStream::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsBufferedOutputStream> stream = new nsBufferedOutputStream(); + return stream->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsBufferedOutputStream::Init(nsIOutputStream* stream, uint32_t bufferSize) { + // QI stream to an nsISafeOutputStream, to see if we should support it + mSafeStream = do_QueryInterface(stream); + + return nsBufferedStream::Init(stream, bufferSize); +} + +NS_IMETHODIMP +nsBufferedOutputStream::Close() { + if (!mStream) { + return NS_OK; + } + + nsresult rv1, rv2 = NS_OK; + + rv1 = Flush(); + +#ifdef DEBUG + if (NS_FAILED(rv1)) { + NS_WARNING( + "(debug) Flush() inside nsBufferedOutputStream::Close() returned error " + "(rv1)."); + } +#endif + + // If we fail to Flush all the data, then we close anyway and drop the + // remaining data in the buffer. We do this because it's what Unix does + // for fclose and close. However, we report the error from Flush anyway. + if (mStream) { + rv2 = Sink()->Close(); +#ifdef DEBUG + if (NS_FAILED(rv2)) { + NS_WARNING( + "(debug) Sink->Close() inside nsBufferedOutputStream::Close() " + "returned error (rv2)."); + } +#endif + } + nsBufferedStream::Close(); + + if (NS_FAILED(rv1)) { + return rv1; + } + if (NS_FAILED(rv2)) { + return rv2; + } + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::StreamStatus() { + return mStream ? Sink()->StreamStatus() : NS_BASE_STREAM_CLOSED; +} + +NS_IMETHODIMP +nsBufferedOutputStream::Write(const char* buf, uint32_t count, + uint32_t* result) { + nsresult rv = NS_OK; + uint32_t written = 0; + *result = 0; + if (!mStream) { + // We special case this situation. + // We should catch the failure, NS_BASE_STREAM_CLOSED ASAP, here. + // If we don't, eventually Flush() is called in the while loop below + // after so many writes. + // However, Flush() returns NS_OK when mStream is null (!!), + // and we don't get a meaningful error, NS_BASE_STREAM_CLOSED, + // soon enough when we use buffered output. +#ifdef DEBUG + NS_WARNING( + "(info) nsBufferedOutputStream::Write returns NS_BASE_STREAM_CLOSED " + "immediately (mStream==null)."); +#endif + return NS_BASE_STREAM_CLOSED; + } + + RecursiveMutexAutoLock lock(mBufferMutex); + while (count > 0) { + uint32_t amt = std::min(count, mBufferSize - mCursor); + if (amt > 0) { + memcpy(mBuffer + mCursor, buf + written, amt); + written += amt; + count -= amt; + mCursor += amt; + if (mFillPoint < mCursor) mFillPoint = mCursor; + } else { + NS_ASSERTION(mFillPoint, "loop in nsBufferedOutputStream::Write!"); + rv = Flush(); + if (NS_FAILED(rv)) { +#ifdef DEBUG + NS_WARNING( + "(debug) Flush() returned error in nsBufferedOutputStream::Write."); +#endif + break; + } + } + } + *result = written; + return (written > 0) ? NS_OK : rv; +} + +NS_IMETHODIMP +nsBufferedOutputStream::Flush() { + nsresult rv; + uint32_t amt; + if (!mStream) { + // Stream already cancelled/flushed; probably because of previous error. + return NS_OK; + } + // optimize : some code within C-C needs to call Seek -> Flush() often. + if (mFillPoint == 0) { + return NS_OK; + } + RecursiveMutexAutoLock lock(mBufferMutex); + rv = Sink()->Write(mBuffer, mFillPoint, &amt); + if (NS_FAILED(rv)) { + return rv; + } + mBufferStartOffset += amt; + if (amt == mFillPoint) { + mFillPoint = mCursor = 0; + return NS_OK; // flushed everything + } + + // slide the remainder down to the start of the buffer + // |<-------------->|<---|----->| + // b a c s + uint32_t rem = mFillPoint - amt; + memmove(mBuffer, mBuffer + amt, rem); + mFillPoint = mCursor = rem; + return NS_ERROR_FAILURE; // didn't flush all +} + +// nsISafeOutputStream +NS_IMETHODIMP +nsBufferedOutputStream::Finish() { + // flush the stream, to write out any buffered data... + nsresult rv1 = nsBufferedOutputStream::Flush(); + nsresult rv2 = NS_OK; + + if (NS_FAILED(rv1)) { + NS_WARNING( + "(debug) nsBufferedOutputStream::Flush() failed in " + "nsBufferedOutputStream::Finish()! Possible dataloss."); + + rv2 = Sink()->Close(); + if (NS_FAILED(rv2)) { + NS_WARNING( + "(debug) Sink()->Close() failed in nsBufferedOutputStream::Finish()! " + "Possible dataloss."); + } + } else { + rv2 = mSafeStream->Finish(); + if (NS_FAILED(rv2)) { + NS_WARNING( + "(debug) mSafeStream->Finish() failed within " + "nsBufferedOutputStream::Flush()! Possible dataloss."); + } + } + + // ... and close the buffered stream, so any further attempts to flush/close + // the buffered stream won't cause errors. + nsBufferedStream::Close(); + + // We want to return the errors precisely from Finish() + // and mimick the existing error handling in + // nsBufferedOutputStream::Close() as reference. + + if (NS_FAILED(rv1)) { + return rv1; + } + if (NS_FAILED(rv2)) { + return rv2; + } + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::WriteFrom(nsIInputStream* inStr, uint32_t count, + uint32_t* _retval) { + return WriteSegments(NS_CopyStreamToSegment, inStr, count, _retval); +} + +NS_IMETHODIMP +nsBufferedOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure, + uint32_t count, uint32_t* _retval) { + *_retval = 0; + nsresult rv; + RecursiveMutexAutoLock lock(mBufferMutex); + while (count > 0) { + uint32_t left = std::min(count, mBufferSize - mCursor); + if (left == 0) { + rv = Flush(); + if (NS_FAILED(rv)) { + return (*_retval > 0) ? NS_OK : rv; + } + + continue; + } + + uint32_t read = 0; + rv = reader(this, closure, mBuffer + mCursor, *_retval, left, &read); + + if (NS_FAILED(rv)) { // If we have read some data, return ok + return (*_retval > 0) ? NS_OK : rv; + } + mCursor += read; + *_retval += read; + count -= read; + mFillPoint = std::max(mFillPoint, mCursor); + } + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::IsNonBlocking(bool* aNonBlocking) { + if (mStream) { + return Sink()->IsNonBlocking(aNonBlocking); + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP_(char*) +nsBufferedOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) { + NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!"); + if (mGetBufferCount != 0) { + return nullptr; + } + + if (mBufferDisabled) { + return nullptr; + } + + RecursiveMutexAutoLock lock(mBufferMutex); + char* buf = mBuffer + mCursor; + uint32_t rem = mBufferSize - mCursor; + if (rem == 0) { + if (NS_FAILED(Flush())) { + return nullptr; + } + buf = mBuffer + mCursor; + rem = mBufferSize - mCursor; + } + + uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask); + if (mod) { + uint32_t pad = aAlignMask + 1 - mod; + if (pad > rem) { + return nullptr; + } + + memset(buf, 0, pad); + mCursor += pad; + buf += pad; + rem -= pad; + } + + if (aLength > rem) { + return nullptr; + } + mGetBufferCount++; + return buf; +} + +NS_IMETHODIMP_(void) +nsBufferedOutputStream::PutBuffer(char* aBuffer, uint32_t aLength) { + NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!"); + if (--mGetBufferCount != 0) { + return; + } + + NS_ASSERTION(mCursor + aLength <= mBufferSize, "PutBuffer botch"); + mCursor += aLength; + if (mFillPoint < mCursor) { + mFillPoint = mCursor; + } +} + +NS_IMETHODIMP +nsBufferedOutputStream::DisableBuffering() { + NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!"); + NS_ASSERTION(mGetBufferCount == 0, + "DisableBuffer call between GetBuffer and PutBuffer!"); + if (mGetBufferCount != 0) { + return NS_ERROR_UNEXPECTED; + } + + // Empty the buffer so nsBufferedStream::Tell works. + nsresult rv = Flush(); + if (NS_FAILED(rv)) { + return rv; + } + + mBufferDisabled = true; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::EnableBuffering() { + NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!"); + mBufferDisabled = false; + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::GetUnbufferedStream(nsISupports** aStream) { + // Empty the buffer so subsequent i/o trumps any buffered data. + if (mFillPoint) { + nsresult rv = Flush(); + if (NS_FAILED(rv)) { + return rv; + } + } + + nsCOMPtr<nsISupports> stream = mStream; + stream.forget(aStream); + return NS_OK; +} + +NS_IMETHODIMP +nsBufferedOutputStream::GetData(nsIOutputStream** aResult) { + nsCOMPtr<nsISupports> stream; + nsBufferedStream::GetData(getter_AddRefs(stream)); + nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(stream); + outputStream.forget(aResult); + return NS_OK; +} +#undef METER + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/nsBufferedStreams.h b/netwerk/base/nsBufferedStreams.h new file mode 100644 index 0000000000..929c544209 --- /dev/null +++ b/netwerk/base/nsBufferedStreams.h @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBufferedStreams_h__ +#define nsBufferedStreams_h__ + +#include "nsIBufferedStreams.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsISeekableStream.h" +#include "nsIStreamBufferAccess.h" +#include "nsCOMPtr.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStreamLength.h" +#include "mozilla/Mutex.h" +#include "mozilla/RecursiveMutex.h" + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedStream : public nsISeekableStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + + nsBufferedStream() = default; + + void Close(); + + protected: + virtual ~nsBufferedStream(); + + nsresult Init(nsISupports* stream, uint32_t bufferSize); + nsresult GetData(nsISupports** aResult); + NS_IMETHOD Fill() = 0; + NS_IMETHOD Flush() = 0; + + uint32_t mBufferSize{0}; + char* mBuffer MOZ_GUARDED_BY(mBufferMutex){nullptr}; + + mozilla::RecursiveMutex mBufferMutex{"nsBufferedStream::mBufferMutex"}; + + // mBufferStartOffset is the offset relative to the start of mStream. + int64_t mBufferStartOffset{0}; + + // mCursor is the read cursor for input streams, or write cursor for + // output streams, and is relative to mBufferStartOffset. + uint32_t mCursor{0}; + + // mFillPoint is the amount available in the buffer for input streams, + // or the high watermark of bytes written into the buffer, and therefore + // is relative to mBufferStartOffset. + uint32_t mFillPoint{0}; + + nsCOMPtr<nsISupports> mStream; // cast to appropriate subclass + + bool mBufferDisabled{false}; + bool mEOF{false}; // True if mStream is at EOF + bool mSeekable{true}; + uint8_t mGetBufferCount{0}; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedInputStream final : public nsBufferedStream, + public nsIBufferedInputStream, + public nsIStreamBufferAccess, + public nsIIPCSerializableInputStream, + public nsIAsyncInputStream, + public nsIInputStreamCallback, + public nsICloneableInputStream, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength, + public nsIInputStreamLengthCallback { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIBUFFEREDINPUTSTREAM + NS_DECL_NSISTREAMBUFFERACCESS + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIINPUTSTREAMLENGTH + NS_DECL_NSIASYNCINPUTSTREAMLENGTH + NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK + + nsBufferedInputStream() = default; + + static nsresult Create(REFNSIID aIID, void** aResult); + + nsIInputStream* Source() { return (nsIInputStream*)mStream.get(); } + + /** + * If there's a reference/pointer to an nsBufferedInputStream BEFORE calling + * Init() AND the intent is to ultimately convert/assign that + * reference/pointer to an nsIInputStream, DO NOT use that initial + * reference/pointer. Instead, use the value of QueryInterface-ing to an + * nsIInputStream (and, again, the QueryInterface must be performed after + * Init()). This is because nsBufferedInputStream has multiple underlying + * nsIInputStreams (one from nsIBufferedInputStream and one from + * nsIAsyncInputStream), and the correct base nsIInputStream to use will be + * unknown until the final value of mIsAsyncInputStream is set in Init(). + * + * This method, however, does just that but also hides the QI details and + * will assert if called before Init(). + */ + already_AddRefed<nsIInputStream> GetInputStream(); + + protected: + virtual ~nsBufferedInputStream() = default; + + NS_IMETHOD Fill() override; + NS_IMETHOD Flush() override { return NS_OK; } // no-op for input streams + + mozilla::Mutex mMutex MOZ_UNANNOTATED{"nsBufferedInputStream::mMutex"}; + + // This value is protected by mutex. + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback; + + // This value is protected by mutex. + nsCOMPtr<nsIInputStreamLengthCallback> mAsyncInputStreamLengthCallback; + + bool mIsIPCSerializable{true}; + bool mIsAsyncInputStream{false}; + bool mIsCloneableInputStream{false}; + bool mIsInputStreamLength{false}; + bool mIsAsyncInputStreamLength{false}; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedOutputStream : public nsBufferedStream, + public nsISafeOutputStream, + public nsIBufferedOutputStream, + public nsIStreamBufferAccess { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSISAFEOUTPUTSTREAM + NS_DECL_NSIBUFFEREDOUTPUTSTREAM + NS_DECL_NSISTREAMBUFFERACCESS + + nsBufferedOutputStream() = default; + + static nsresult Create(REFNSIID aIID, void** aResult); + + nsIOutputStream* Sink() { return (nsIOutputStream*)mStream.get(); } + + protected: + virtual ~nsBufferedOutputStream() { nsBufferedOutputStream::Close(); } + + NS_IMETHOD Fill() override { return NS_OK; } // no-op for output streams + + nsCOMPtr<nsISafeOutputStream> mSafeStream; // QI'd from mStream +}; + +//////////////////////////////////////////////////////////////////////////////// + +#endif // nsBufferedStreams_h__ diff --git a/netwerk/base/nsDNSPrefetch.cpp b/netwerk/base/nsDNSPrefetch.cpp new file mode 100644 index 0000000000..4c5a3a0fd6 --- /dev/null +++ b/netwerk/base/nsDNSPrefetch.cpp @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDNSPrefetch.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsThreadUtils.h" + +#include "nsIDNSAdditionalInfo.h" +#include "nsIDNSListener.h" +#include "nsIDNSService.h" +#include "nsIDNSByTypeRecord.h" +#include "nsICancelable.h" +#include "nsIURI.h" +#include "mozilla/Atomics.h" +#include "mozilla/Preferences.h" + +static mozilla::StaticRefPtr<nsIDNSService> sDNSService; + +nsresult nsDNSPrefetch::Initialize(nsIDNSService* aDNSService) { + MOZ_ASSERT(NS_IsMainThread()); + + sDNSService = aDNSService; + return NS_OK; +} + +nsresult nsDNSPrefetch::Shutdown() { + sDNSService = nullptr; + return NS_OK; +} + +nsDNSPrefetch::nsDNSPrefetch(nsIURI* aURI, + mozilla::OriginAttributes& aOriginAttributes, + nsIRequest::TRRMode aTRRMode, + nsIDNSListener* aListener, bool storeTiming) + : mOriginAttributes(aOriginAttributes), + mStoreTiming(storeTiming), + mTRRMode(aTRRMode), + mListener(do_GetWeakReference(aListener)) { + aURI->GetAsciiHost(mHostname); + aURI->GetPort(&mPort); +} + +nsDNSPrefetch::nsDNSPrefetch(nsIURI* aURI, + mozilla::OriginAttributes& aOriginAttributes, + nsIRequest::TRRMode aTRRMode) + : mOriginAttributes(aOriginAttributes), + mStoreTiming(false), + mTRRMode(aTRRMode), + mListener(nullptr) { + aURI->GetAsciiHost(mHostname); +} + +nsresult nsDNSPrefetch::Prefetch(nsIDNSService::DNSFlags flags) { + if (mHostname.IsEmpty()) return NS_ERROR_NOT_AVAILABLE; + + if (!sDNSService) return NS_ERROR_NOT_AVAILABLE; + + nsCOMPtr<nsICancelable> tmpOutstanding; + + if (mStoreTiming) mStartTimestamp = mozilla::TimeStamp::Now(); + // If AsyncResolve fails, for example because prefetching is disabled, + // then our timing will be useless. However, in such a case, + // mEndTimestamp will be a null timestamp and callers should check + // TimingsValid() before using the timing. + nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget(); + + flags |= nsIDNSService::GetFlagsFromTRRMode(mTRRMode); + + return sDNSService->AsyncResolveNative( + mHostname, nsIDNSService::RESOLVE_TYPE_DEFAULT, + flags | nsIDNSService::RESOLVE_SPECULATE, nullptr, this, target, + mOriginAttributes, getter_AddRefs(tmpOutstanding)); +} + +nsresult nsDNSPrefetch::PrefetchLow(nsIDNSService::DNSFlags aFlags) { + return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW | aFlags); +} + +nsresult nsDNSPrefetch::PrefetchMedium(nsIDNSService::DNSFlags aFlags) { + return Prefetch(nsIDNSService::RESOLVE_PRIORITY_MEDIUM | aFlags); +} + +nsresult nsDNSPrefetch::PrefetchHigh(nsIDNSService::DNSFlags aFlags) { + return Prefetch(aFlags); +} + +namespace { + +class HTTPSRRListener final : public nsIDNSListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + explicit HTTPSRRListener( + std::function<void(nsIDNSHTTPSSVCRecord*)>&& aCallback) + : mResultCallback(std::move(aCallback)) {} + + private: + ~HTTPSRRListener() = default; + std::function<void(nsIDNSHTTPSSVCRecord*)> mResultCallback; +}; + +NS_IMPL_ISUPPORTS(HTTPSRRListener, nsIDNSListener) + +NS_IMETHODIMP +HTTPSRRListener::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRec, + nsresult aStatus) { + if (NS_FAILED(aStatus)) { + mResultCallback(nullptr); + return NS_OK; + } + + nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord = do_QueryInterface(aRec); + mResultCallback(httpsRecord); + return NS_OK; +} + +}; // namespace + +nsresult nsDNSPrefetch::FetchHTTPSSVC( + bool aRefreshDNS, bool aPrefetch, + std::function<void(nsIDNSHTTPSSVCRecord*)>&& aCallback) { + if (!sDNSService) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget(); + nsIDNSService::DNSFlags flags = nsIDNSService::GetFlagsFromTRRMode(mTRRMode); + if (aRefreshDNS) { + flags |= nsIDNSService::RESOLVE_BYPASS_CACHE; + } + if (aPrefetch) { + flags |= nsIDNSService::RESOLVE_SPECULATE; + } + + nsCOMPtr<nsICancelable> tmpOutstanding; + nsCOMPtr<nsIDNSListener> listener = new HTTPSRRListener(std::move(aCallback)); + nsCOMPtr<nsIDNSAdditionalInfo> info; + if (mPort != -1) { + sDNSService->NewAdditionalInfo(""_ns, mPort, getter_AddRefs(info)); + } + return sDNSService->AsyncResolveNative( + mHostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, flags, info, listener, + target, mOriginAttributes, getter_AddRefs(tmpOutstanding)); +} + +NS_IMPL_ISUPPORTS(nsDNSPrefetch, nsIDNSListener) + +NS_IMETHODIMP +nsDNSPrefetch::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec, + nsresult status) { + if (mStoreTiming) { + mEndTimestamp = mozilla::TimeStamp::Now(); + } + nsCOMPtr<nsIDNSListener> listener = do_QueryReferent(mListener); + if (listener) { + listener->OnLookupComplete(request, rec, status); + } + + return NS_OK; +} diff --git a/netwerk/base/nsDNSPrefetch.h b/netwerk/base/nsDNSPrefetch.h new file mode 100644 index 0000000000..7cad00b588 --- /dev/null +++ b/netwerk/base/nsDNSPrefetch.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDNSPrefetch_h___ +#define nsDNSPrefetch_h___ + +#include <functional> + +#include "nsIWeakReferenceUtils.h" +#include "nsString.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Attributes.h" +#include "mozilla/BasePrincipal.h" + +#include "nsIDNSListener.h" +#include "nsIRequest.h" +#include "nsIDNSService.h" + +class nsIURI; +class nsIDNSHTTPSSVCRecord; + +class nsDNSPrefetch final : public nsIDNSListener { + ~nsDNSPrefetch() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + nsDNSPrefetch(nsIURI* aURI, mozilla::OriginAttributes& aOriginAttributes, + nsIRequest::TRRMode aTRRMode, nsIDNSListener* aListener, + bool storeTiming); + // For fetching HTTPS RR. + nsDNSPrefetch(nsIURI* aURI, mozilla::OriginAttributes& aOriginAttributes, + nsIRequest::TRRMode aTRRMode); + bool TimingsValid() const { + return !mStartTimestamp.IsNull() && !mEndTimestamp.IsNull(); + } + // Only use the two timings if TimingsValid() returns true + const mozilla::TimeStamp& StartTimestamp() const { return mStartTimestamp; } + const mozilla::TimeStamp& EndTimestamp() const { return mEndTimestamp; } + + static nsresult Initialize(nsIDNSService* aDNSService); + static nsresult Shutdown(); + + // Call one of the following methods to start the Prefetch. + nsresult PrefetchHigh( + nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS); + nsresult PrefetchMedium( + nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS); + nsresult PrefetchLow( + nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS); + + nsresult FetchHTTPSSVC( + bool aRefreshDNS, bool aPrefetch, + std::function<void(nsIDNSHTTPSSVCRecord*)>&& aCallback); + + private: + nsCString mHostname; + int32_t mPort{-1}; + mozilla::OriginAttributes mOriginAttributes; + bool mStoreTiming; + nsIRequest::TRRMode mTRRMode; + mozilla::TimeStamp mStartTimestamp; + mozilla::TimeStamp mEndTimestamp; + nsWeakPtr mListener; + + nsresult Prefetch(nsIDNSService::DNSFlags flags); +}; + +#endif diff --git a/netwerk/base/nsDirectoryIndexStream.cpp b/netwerk/base/nsDirectoryIndexStream.cpp new file mode 100644 index 0000000000..b9b840172e --- /dev/null +++ b/netwerk/base/nsDirectoryIndexStream.cpp @@ -0,0 +1,296 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set sw=2 sts=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + The converts a filesystem directory into an "HTTP index" stream per + Lou Montulli's original spec: + + http://www.mozilla.org/projects/netlib/dirindexformat.html + + */ + +#include "nsEscape.h" +#include "nsDirectoryIndexStream.h" +#include "mozilla/Logging.h" +#include "prtime.h" +#include "nsIFile.h" +#include "nsNativeCharsetUtils.h" + +// NOTE: This runs on the _file transport_ thread. +// The problem is that now that we're actually doing something with the data, +// we want to do stuff like i18n sorting. However, none of the collation stuff +// is threadsafe. +// So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current +// behaviour, though. See bug 99382. + +using namespace mozilla; +static LazyLogModule gLog("nsDirectoryIndexStream"); + +nsDirectoryIndexStream::nsDirectoryIndexStream() { + MOZ_LOG(gLog, LogLevel::Debug, ("nsDirectoryIndexStream[%p]: created", this)); +} + +static int compare(nsIFile* aElement1, nsIFile* aElement2) { + if (!NS_IsNativeUTF8()) { + // don't check for errors, because we can't report them anyway + nsAutoString name1, name2; + aElement1->GetLeafName(name1); + aElement2->GetLeafName(name2); + + // Note - we should do the collation to do sorting. Why don't we? + // Because that is _slow_. Using TestProtocols to list file:///dev/ + // goes from 3 seconds to 22. (This may be why nsXULSortService is + // so slow as well). + // Does this have bad effects? Probably, but since nsXULTree appears + // to use the raw RDF literal value as the sort key (which ammounts to an + // strcmp), it won't be any worse, I think. + // This could be made faster, by creating the keys once, + // but CompareString could still be smarter - see bug 99383 - bbaetz + // NB - 99393 has been WONTFIXed. So if the I18N code is ever made + // threadsafe so that this matters, we'd have to pass through a + // struct { nsIFile*, uint8_t* } with the pre-calculated key. + return Compare(name1, name2); + } + + nsAutoCString name1, name2; + aElement1->GetNativeLeafName(name1); + aElement2->GetNativeLeafName(name2); + + return Compare(name1, name2); +} + +nsresult nsDirectoryIndexStream::Init(nsIFile* aDir) { + nsresult rv; + bool isDir; + rv = aDir->IsDirectory(&isDir); + if (NS_FAILED(rv)) return rv; + MOZ_ASSERT(isDir, "not a directory"); + if (!isDir) return NS_ERROR_ILLEGAL_VALUE; + + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: initialized on %s", this, + aDir->HumanReadablePath().get())); + } + + // Sigh. We have to allocate on the heap because there are no + // assignment operators defined. + nsCOMPtr<nsIDirectoryEnumerator> iter; + rv = aDir->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) return rv; + + // Now lets sort, because clients expect it that way + // XXX - should we do so here, or when the first item is requested? + // XXX - use insertion sort instead? + + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) { + mArray.AppendObject(file); // addrefs + } + + mArray.Sort(compare); + + mBuf.AppendLiteral("200: filename content-length last-modified file-type\n"); + + return NS_OK; +} + +nsDirectoryIndexStream::~nsDirectoryIndexStream() { + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: destroyed", this)); +} + +nsresult nsDirectoryIndexStream::Create(nsIFile* aDir, + nsIInputStream** aResult) { + RefPtr<nsDirectoryIndexStream> result = new nsDirectoryIndexStream(); + if (!result) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = result->Init(aDir); + if (NS_FAILED(rv)) { + return rv; + } + + result.forget(aResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsDirectoryIndexStream, nsIInputStream) + +// The below routines are proxied to the UI thread! +NS_IMETHODIMP +nsDirectoryIndexStream::Close() { + mStatus = NS_BASE_STREAM_CLOSED; + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::Available(uint64_t* aLength) { + if (NS_FAILED(mStatus)) return mStatus; + + // If there's data in our buffer, use that + if (mOffset < (int32_t)mBuf.Length()) { + *aLength = mBuf.Length() - mOffset; + return NS_OK; + } + + // Returning one byte is not ideal, but good enough + *aLength = (mPos < mArray.Count()) ? 1 : 0; + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::StreamStatus() { return mStatus; } + +NS_IMETHODIMP +nsDirectoryIndexStream::Read(char* aBuf, uint32_t aCount, + uint32_t* aReadCount) { + if (mStatus == NS_BASE_STREAM_CLOSED) { + *aReadCount = 0; + return NS_OK; + } + if (NS_FAILED(mStatus)) return mStatus; + + uint32_t nread = 0; + + // If anything is enqueued (or left-over) in mBuf, then feed it to + // the reader first. + while (mOffset < (int32_t)mBuf.Length() && aCount != 0) { + *(aBuf++) = char(mBuf.CharAt(mOffset++)); + --aCount; + ++nread; + } + + // Room left? + if (aCount > 0) { + mOffset = 0; + mBuf.Truncate(); + + // Okay, now we'll suck stuff off of our iterator into the mBuf... + while (uint32_t(mBuf.Length()) < aCount) { + bool more = mPos < mArray.Count(); + if (!more) break; + + // don't addref, for speed - an addref happened when it + // was placed in the array, so it's not going to go stale + nsIFile* current = mArray.ObjectAt(mPos); + ++mPos; + + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: iterated %s", this, + current->HumanReadablePath().get())); + } + + // rjc: don't return hidden files/directories! + // bbaetz: why not? + nsresult rv; +#ifndef XP_UNIX + bool hidden = false; + current->IsHidden(&hidden); + if (hidden) { + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: skipping hidden file/directory", + this)); + continue; + } +#endif + + int64_t fileSize = 0; + current->GetFileSize(&fileSize); + + PRTime fileInfoModifyTime = 0; + current->GetLastModifiedTime(&fileInfoModifyTime); + fileInfoModifyTime *= PR_USEC_PER_MSEC; + + mBuf.AppendLiteral("201: "); + + // The "filename" field + if (!NS_IsNativeUTF8()) { + nsAutoString leafname; + rv = current->GetLeafName(leafname); + if (NS_FAILED(rv)) return rv; + + nsAutoCString escaped; + if (!leafname.IsEmpty() && + NS_Escape(NS_ConvertUTF16toUTF8(leafname), escaped, url_Path)) { + mBuf.Append(escaped); + mBuf.Append(' '); + } + } else { + nsAutoCString leafname; + rv = current->GetNativeLeafName(leafname); + if (NS_FAILED(rv)) return rv; + + nsAutoCString escaped; + if (!leafname.IsEmpty() && NS_Escape(leafname, escaped, url_Path)) { + mBuf.Append(escaped); + mBuf.Append(' '); + } + } + + // The "content-length" field + mBuf.AppendInt(fileSize, 10); + mBuf.Append(' '); + + // The "last-modified" field + PRExplodedTime tm; + PR_ExplodeTime(fileInfoModifyTime, PR_GMTParameters, &tm); + { + char buf[64]; + PR_FormatTimeUSEnglish( + buf, sizeof(buf), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm); + mBuf.Append(buf); + } + + // The "file-type" field + bool isFile = true; + current->IsFile(&isFile); + if (isFile) { + mBuf.AppendLiteral("FILE "); + } else { + bool isDir; + rv = current->IsDirectory(&isDir); + if (NS_FAILED(rv)) return rv; + if (isDir) { + mBuf.AppendLiteral("DIRECTORY "); + } else { + bool isLink; + rv = current->IsSymlink(&isLink); + if (NS_FAILED(rv)) return rv; + if (isLink) { + mBuf.AppendLiteral("SYMBOLIC-LINK "); + } + } + } + + mBuf.Append('\n'); + } + + // ...and once we've either run out of directory entries, or + // filled up the buffer, then we'll push it to the reader. + while (mOffset < (int32_t)mBuf.Length() && aCount != 0) { + *(aBuf++) = char(mBuf.CharAt(mOffset++)); + --aCount; + ++nread; + } + } + + *aReadCount = nread; + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t count, uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDirectoryIndexStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = false; + return NS_OK; +} diff --git a/netwerk/base/nsDirectoryIndexStream.h b/netwerk/base/nsDirectoryIndexStream.h new file mode 100644 index 0000000000..6bdebc7cfe --- /dev/null +++ b/netwerk/base/nsDirectoryIndexStream.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDirectoryIndexStream_h__ +#define nsDirectoryIndexStream_h__ + +#include "mozilla/Attributes.h" + +#include "nsString.h" +#include "nsIInputStream.h" +#include "nsCOMArray.h" + +class nsIFile; + +class nsDirectoryIndexStream final : public nsIInputStream { + private: + nsCString mBuf; + int32_t mOffset{0}; + nsresult mStatus{NS_OK}; + + int32_t mPos{0}; // position within mArray + nsCOMArray<nsIFile> mArray; // file objects within the directory + + nsDirectoryIndexStream(); + /** + * aDir will only be used on the calling thread. + */ + nsresult Init(nsIFile* aDir); + ~nsDirectoryIndexStream(); + + public: + /** + * aDir will only be used on the calling thread. + */ + static nsresult Create(nsIFile* aDir, nsIInputStream** aResult); + + // nsISupportsInterface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIInputStream interface + NS_DECL_NSIINPUTSTREAM +}; + +#endif // nsDirectoryIndexStream_h__ diff --git a/netwerk/base/nsDownloader.cpp b/netwerk/base/nsDownloader.cpp new file mode 100644 index 0000000000..b6d3240226 --- /dev/null +++ b/netwerk/base/nsDownloader.cpp @@ -0,0 +1,98 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDownloader.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsNetUtil.h" +#include "nsCRTGlue.h" + +nsDownloader::~nsDownloader() { + if (mLocation && mLocationIsTemp) { + // release the sink first since it may still hold an open file + // descriptor to mLocation. this needs to happen before the + // file can be removed otherwise the Remove call will fail. + if (mSink) { + mSink->Close(); + mSink = nullptr; + } + + nsresult rv = mLocation->Remove(false); + if (NS_FAILED(rv)) NS_ERROR("unable to remove temp file"); + } +} + +NS_IMPL_ISUPPORTS(nsDownloader, nsIDownloader, nsIStreamListener, + nsIRequestObserver) + +NS_IMETHODIMP +nsDownloader::Init(nsIDownloadObserver* observer, nsIFile* location) { + mObserver = observer; + mLocation = location; + return NS_OK; +} + +NS_IMETHODIMP +nsDownloader::OnStartRequest(nsIRequest* request) { + nsresult rv; + if (!mLocation) { + nsCOMPtr<nsIFile> location; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(location)); + if (NS_FAILED(rv)) return rv; + + char buf[13]; + NS_MakeRandomString(buf, 8); + memcpy(buf + 8, ".tmp", 5); + rv = location->AppendNative(nsDependentCString(buf, 12)); + if (NS_FAILED(rv)) return rv; + + rv = location->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_FAILED(rv)) return rv; + + location.swap(mLocation); + mLocationIsTemp = true; + } + + rv = NS_NewLocalFileOutputStream(getter_AddRefs(mSink), mLocation); + if (NS_FAILED(rv)) return rv; + + // we could wrap this output stream with a buffered output stream, + // but it shouldn't be necessary since we will be writing large + // chunks given to us via OnDataAvailable. + + return NS_OK; +} + +NS_IMETHODIMP +nsDownloader::OnStopRequest(nsIRequest* request, nsresult status) { + if (mSink) { + mSink->Close(); + mSink = nullptr; + } + + mObserver->OnDownloadComplete(this, request, status, mLocation); + mObserver = nullptr; + + return NS_OK; +} + +nsresult nsDownloader::ConsumeData(nsIInputStream* in, void* closure, + const char* fromRawSegment, + uint32_t toOffset, uint32_t count, + uint32_t* writeCount) { + nsDownloader* self = (nsDownloader*)closure; + if (self->mSink) return self->mSink->Write(fromRawSegment, count, writeCount); + + *writeCount = count; + return NS_OK; +} + +NS_IMETHODIMP +nsDownloader::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr, + uint64_t sourceOffset, uint32_t count) { + uint32_t n; + return inStr->ReadSegments(ConsumeData, this, count, &n); +} diff --git a/netwerk/base/nsDownloader.h b/netwerk/base/nsDownloader.h new file mode 100644 index 0000000000..fe95dd6ac2 --- /dev/null +++ b/netwerk/base/nsDownloader.h @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDownloader_h__ +#define nsDownloader_h__ + +#include "nsIDownloader.h" +#include "nsCOMPtr.h" + +class nsIFile; +class nsIOutputStream; + +class nsDownloader : public nsIDownloader { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOWNLOADER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsDownloader() = default; + + protected: + virtual ~nsDownloader(); + + static nsresult ConsumeData(nsIInputStream* in, void* closure, + const char* fromRawSegment, uint32_t toOffset, + uint32_t count, uint32_t* writeCount); + + nsCOMPtr<nsIDownloadObserver> mObserver; + nsCOMPtr<nsIFile> mLocation; + nsCOMPtr<nsIOutputStream> mSink; + bool mLocationIsTemp{false}; +}; + +#endif // nsDownloader_h__ diff --git a/netwerk/base/nsFileStreams.cpp b/netwerk/base/nsFileStreams.cpp new file mode 100644 index 0000000000..32281f6d02 --- /dev/null +++ b/netwerk/base/nsFileStreams.cpp @@ -0,0 +1,1031 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ipc/IPCMessageUtils.h" + +#if defined(XP_UNIX) +# include <unistd.h> +#elif defined(XP_WIN) +# include <windows.h> +# include "nsILocalFileWin.h" +#else +// XXX add necessary include file for ftruncate (or equivalent) +#endif + +#include "private/pprio.h" +#include "prerror.h" + +#include "IOActivityMonitor.h" +#include "nsFileStreams.h" +#include "nsIFile.h" +#include "nsReadLine.h" +#include "nsIClassInfoImpl.h" +#include "nsLiteralString.h" +#include "nsSocketTransport2.h" // for ErrorAccordingToNSPR() +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/RandomAccessStreamParams.h" +#include "mozilla/Unused.h" +#include "mozilla/FileUtils.h" +#include "mozilla/UniquePtr.h" +#include "nsNetCID.h" +#include "nsXULAppAPI.h" + +using FileHandleType = mozilla::ipc::FileDescriptor::PlatformHandleType; + +using namespace mozilla::ipc; +using namespace mozilla::net; + +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +//////////////////////////////////////////////////////////////////////////////// +// nsFileStreamBase + +nsFileStreamBase::~nsFileStreamBase() { + // We don't want to try to rewrind the stream when shutting down. + mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND; + + Close(); +} + +NS_IMPL_ISUPPORTS(nsFileStreamBase, nsISeekableStream, nsITellableStream, + nsIFileMetadata) + +NS_IMETHODIMP +nsFileStreamBase::Seek(int32_t whence, int64_t offset) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence); + if (cnt == int64_t(-1)) { + return NS_ErrorAccordingToNSPR(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::Tell(int64_t* result) { + if (mState == eDeferredOpen && !(mOpenParams.ioFlags & PR_APPEND)) { + *result = 0; + return NS_OK; + } + + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR); + if (cnt == int64_t(-1)) { + return NS_ErrorAccordingToNSPR(); + } + *result = cnt; + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::SetEOF() { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + +#if defined(XP_UNIX) + // Some system calls require an EOF offset. + int64_t offset; + rv = Tell(&offset); + if (NS_FAILED(rv)) return rv; +#endif + +#if defined(XP_UNIX) + if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) { + NS_ERROR("ftruncate failed"); + return NS_ERROR_FAILURE; + } +#elif defined(XP_WIN) + if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(mFD))) { + NS_ERROR("SetEndOfFile failed"); + return NS_ERROR_FAILURE; + } +#else + // XXX not implemented +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::GetSize(int64_t* _retval) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + PRFileInfo64 info; + if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) { + return NS_BASE_STREAM_OSERROR; + } + + *_retval = int64_t(info.size); + + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::GetLastModified(int64_t* _retval) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + PRFileInfo64 info; + if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) { + return NS_BASE_STREAM_OSERROR; + } + + int64_t modTime = int64_t(info.modifyTime); + if (modTime == 0) { + *_retval = 0; + } else { + *_retval = modTime / int64_t(PR_USEC_PER_MSEC); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFileStreamBase::GetFileDescriptor(PRFileDesc** _retval) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + *_retval = mFD; + return NS_OK; +} + +nsresult nsFileStreamBase::Close() { + if (mState == eClosed) { + return NS_OK; + } + + CleanUpOpen(); + + nsresult rv = NS_OK; + if (mFD) { + if (PR_Close(mFD) == PR_FAILURE) rv = NS_BASE_STREAM_OSERROR; + mFD = nullptr; + mState = eClosed; + } + return rv; +} + +nsresult nsFileStreamBase::Available(uint64_t* aResult) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + // PR_Available with files over 4GB returns an error, so we have to + // use the 64-bit version of PR_Available. + int64_t avail = PR_Available64(mFD); + if (avail == -1) { + return NS_ErrorAccordingToNSPR(); + } + + // If available is greater than 4GB, return 4GB + *aResult = (uint64_t)avail; + return NS_OK; +} + +nsresult nsFileStreamBase::Read(char* aBuf, uint32_t aCount, + uint32_t* aResult) { + nsresult rv = DoPendingOpen(); + if (rv == NS_BASE_STREAM_CLOSED) { + *aResult = 0; + return NS_OK; + } + + if (NS_FAILED(rv)) { + return rv; + } + + int32_t bytesRead = PR_Read(mFD, aBuf, aCount); + if (bytesRead == -1) { + return NS_ErrorAccordingToNSPR(); + } + + *aResult = bytesRead; + return NS_OK; +} + +nsresult nsFileStreamBase::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aResult) { + // ReadSegments is not implemented because it would be inefficient when + // the writer does not consume all data. If you want to call ReadSegments, + // wrap a BufferedInputStream around the file stream. That will call + // Read(). + + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsFileStreamBase::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = false; + return NS_OK; +} + +nsresult nsFileStreamBase::Flush(void) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t cnt = PR_Sync(mFD); + if (cnt == -1) { + return NS_ErrorAccordingToNSPR(); + } + return NS_OK; +} + +nsresult nsFileStreamBase::StreamStatus() { + switch (mState) { + case eUnitialized: + MOZ_CRASH("This should not happen."); + return NS_ERROR_FAILURE; + + case eDeferredOpen: + return NS_OK; + + case eOpened: + MOZ_ASSERT(mFD); + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_FAILURE; + } + return NS_OK; + + case eClosed: + MOZ_ASSERT(!mFD); + return NS_BASE_STREAM_CLOSED; + + case eError: + return mErrorValue; + } + + MOZ_CRASH("Invalid mState value."); + return NS_ERROR_FAILURE; +} + +nsresult nsFileStreamBase::Write(const char* buf, uint32_t count, + uint32_t* result) { + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t cnt = PR_Write(mFD, buf, count); + if (cnt == -1) { + return NS_ErrorAccordingToNSPR(); + } + *result = cnt; + return NS_OK; +} + +nsresult nsFileStreamBase::WriteFrom(nsIInputStream* inStr, uint32_t count, + uint32_t* _retval) { + MOZ_ASSERT_UNREACHABLE("WriteFrom (see source comment)"); + return NS_ERROR_NOT_IMPLEMENTED; + // File streams intentionally do not support this method. + // If you need something like this, then you should wrap + // the file stream using nsIBufferedOutputStream +} + +nsresult nsFileStreamBase::WriteSegments(nsReadSegmentFun reader, void* closure, + uint32_t count, uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; + // File streams intentionally do not support this method. + // If you need something like this, then you should wrap + // the file stream using nsIBufferedOutputStream +} + +nsresult nsFileStreamBase::MaybeOpen(nsIFile* aFile, int32_t aIoFlags, + int32_t aPerm, bool aDeferred) { + NS_ENSURE_STATE(aFile); + + mOpenParams.ioFlags = aIoFlags; + mOpenParams.perm = aPerm; + + if (aDeferred) { + // Clone the file, as it may change between now and the deferred open + nsCOMPtr<nsIFile> file; + nsresult rv = aFile->Clone(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + mOpenParams.localFile = std::move(file); + NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED); + + mState = eDeferredOpen; + return NS_OK; + } + + mOpenParams.localFile = aFile; + + // Following call open() at main thread. + // Main thread might be blocked, while open a remote file. + return DoOpen(); +} + +void nsFileStreamBase::CleanUpOpen() { mOpenParams.localFile = nullptr; } + +nsresult nsFileStreamBase::DoOpen() { + MOZ_ASSERT(mState == eDeferredOpen || mState == eUnitialized || + mState == eClosed); + NS_ASSERTION(!mFD, "Already have a file descriptor!"); + NS_ASSERTION(mOpenParams.localFile, "Must have a file to open"); + + PRFileDesc* fd; + nsresult rv; + + if (mOpenParams.ioFlags & PR_CREATE_FILE) { + nsCOMPtr<nsIFile> parent; + mOpenParams.localFile->GetParent(getter_AddRefs(parent)); + + // Result doesn't need to be checked. If the file's parent path does not + // exist, make it. If it does exist, do nothing. + if (parent) { + mozilla::Unused << parent->Create(nsIFile::DIRECTORY_TYPE, 0755); + } + } + +#ifdef XP_WIN + if (mBehaviorFlags & nsIFileInputStream::SHARE_DELETE) { + nsCOMPtr<nsILocalFileWin> file = do_QueryInterface(mOpenParams.localFile); + MOZ_ASSERT(file); + + rv = file->OpenNSPRFileDescShareDelete(mOpenParams.ioFlags, + mOpenParams.perm, &fd); + } else +#endif // XP_WIN + { + rv = mOpenParams.localFile->OpenNSPRFileDesc(mOpenParams.ioFlags, + mOpenParams.perm, &fd); + } + + if (rv == NS_OK && IOActivityMonitor::IsActive()) { + auto nativePath = mOpenParams.localFile->NativePath(); + if (!nativePath.IsEmpty()) { +// registering the file to the activity monitor +#ifdef XP_WIN + // 16 bits unicode + IOActivityMonitor::MonitorFile( + fd, NS_ConvertUTF16toUTF8(nativePath.get()).get()); +#else + // 8 bit unicode + IOActivityMonitor::MonitorFile(fd, nativePath.get()); +#endif + } + } + + CleanUpOpen(); + + if (NS_FAILED(rv)) { + mState = eError; + mErrorValue = rv; + return rv; + } + + mFD = fd; + mState = eOpened; + + return NS_OK; +} + +nsresult nsFileStreamBase::DoPendingOpen() { + switch (mState) { + case eUnitialized: + MOZ_CRASH("This should not happen."); + return NS_ERROR_FAILURE; + + case eDeferredOpen: + return DoOpen(); + + case eOpened: + MOZ_ASSERT(mFD); + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_FAILURE; + } + return NS_OK; + + case eClosed: + MOZ_ASSERT(!mFD); + return NS_BASE_STREAM_CLOSED; + + case eError: + return mErrorValue; + } + + MOZ_CRASH("Invalid mState value."); + return NS_ERROR_FAILURE; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFileInputStream + +NS_IMPL_ADDREF_INHERITED(nsFileInputStream, nsFileStreamBase) +NS_IMPL_RELEASE_INHERITED(nsFileInputStream, nsFileStreamBase) + +NS_IMPL_CLASSINFO(nsFileInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_LOCALFILEINPUTSTREAM_CID) + +NS_INTERFACE_MAP_BEGIN(nsFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsILineInputStream) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) + NS_IMPL_QUERY_CLASSINFO(nsFileInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, IsCloneable()) +NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase) + +NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream, nsIInputStream, + nsIFileInputStream, nsISeekableStream, + nsITellableStream, nsILineInputStream) + +nsresult nsFileInputStream::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsFileInputStream> stream = new nsFileInputStream(); + return stream->QueryInterface(aIID, aResult); +} + +nsresult nsFileInputStream::Open(nsIFile* aFile, int32_t aIOFlags, + int32_t aPerm) { + nsresult rv = NS_OK; + + // If the previous file is open, close it + if (mFD) { + rv = Close(); + if (NS_FAILED(rv)) return rv; + } + + // Open the file + if (aIOFlags == -1) aIOFlags = PR_RDONLY; + if (aPerm == -1) aPerm = 0; + + return MaybeOpen(aFile, aIOFlags, aPerm, + mBehaviorFlags & nsIFileInputStream::DEFER_OPEN); +} + +NS_IMETHODIMP +nsFileInputStream::Init(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm, + int32_t aBehaviorFlags) { + NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed, + NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = aBehaviorFlags; + mState = eUnitialized; + + mFile = aFile; + mIOFlags = aIOFlags; + mPerm = aPerm; + + return Open(aFile, aIOFlags, aPerm); +} + +NS_IMETHODIMP +nsFileInputStream::Close() { + // If this stream has already been closed, do nothing. + if (mState == eClosed) { + return NS_OK; + } + + // Get the cache position at the time the file was close. This allows + // NS_SEEK_CUR on a closed file that has been opened with + // REOPEN_ON_REWIND. + if (mBehaviorFlags & REOPEN_ON_REWIND) { + // Get actual position. Not one modified by subclasses + nsFileStreamBase::Tell(&mCachedPosition); + } + + // explicitly clear mLineBuffer in case this stream is reopened + mLineBuffer = nullptr; + return nsFileStreamBase::Close(); +} + +NS_IMETHODIMP +nsFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) { + nsresult rv = nsFileStreamBase::Read(aBuf, aCount, _retval); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + // Don't warn if this is a deffered file not found. + return rv; + } + + if (NS_FAILED(rv)) { + return rv; + } + + // Check if we're at the end of file and need to close + if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) { + Close(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult) { + if (!mLineBuffer) { + mLineBuffer = mozilla::MakeUnique<nsLineBuffer<char>>(); + } + return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult); +} + +NS_IMETHODIMP +nsFileInputStream::Seek(int32_t aWhence, int64_t aOffset) { + return SeekInternal(aWhence, aOffset); +} + +nsresult nsFileInputStream::SeekInternal(int32_t aWhence, int64_t aOffset, + bool aClearBuf) { + nsresult rv = DoPendingOpen(); + if (rv != NS_OK && rv != NS_BASE_STREAM_CLOSED) { + return rv; + } + + if (aClearBuf) { + mLineBuffer = nullptr; + } + + if (rv == NS_BASE_STREAM_CLOSED) { + if (mBehaviorFlags & REOPEN_ON_REWIND) { + rv = Open(mFile, mIOFlags, mPerm); + NS_ENSURE_SUCCESS(rv, rv); + + // If the file was closed, and we do a relative seek, use the + // position we cached when we closed the file to seek to the right + // location. + if (aWhence == NS_SEEK_CUR) { + aWhence = NS_SEEK_SET; + aOffset += mCachedPosition; + } + // If we're trying to seek to the start then we're done, so + // return early to avoid Seek from calling DoPendingOpen and + // opening the underlying file earlier than necessary. + if (aWhence == NS_SEEK_SET && aOffset == 0) { + return NS_OK; + } + } else { + return NS_BASE_STREAM_CLOSED; + } + } + + return nsFileStreamBase::Seek(aWhence, aOffset); +} + +NS_IMETHODIMP +nsFileInputStream::Tell(int64_t* aResult) { + return nsFileStreamBase::Tell(aResult); +} + +NS_IMETHODIMP +nsFileInputStream::Available(uint64_t* aResult) { + return nsFileStreamBase::Available(aResult); +} + +NS_IMETHODIMP +nsFileInputStream::StreamStatus() { return nsFileStreamBase::StreamStatus(); } + +void nsFileInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + *aTransferables = 1; +} + +void nsFileInputStream::Serialize(InputStreamParams& aParams, uint32_t aMaxSize, + uint32_t* aSizeUsed) { + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + FileInputStreamParams params; + + if (NS_SUCCEEDED(DoPendingOpen())) { + MOZ_ASSERT(mFD); + FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD)); + NS_ASSERTION(fd, "This should never be null!"); + + params.fileDescriptor() = FileDescriptor(fd); + + Close(); + } else { + NS_WARNING( + "This file has not been opened (or could not be opened). " + "Sending an invalid file descriptor to the other process!"); + + params.fileDescriptor() = FileDescriptor(); + } + + int32_t behaviorFlags = mBehaviorFlags; + + // The receiving process (or thread) is going to have an open file + // descriptor automatically so transferring this flag is meaningless. + behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN; + + params.behaviorFlags() = behaviorFlags; + params.ioFlags() = mIOFlags; + + aParams = params; +} + +bool nsFileInputStream::Deserialize(const InputStreamParams& aParams) { + NS_ASSERTION(!mFD, "Already have a file descriptor?!"); + NS_ASSERTION(mState == nsFileStreamBase::eUnitialized, "Deferring open?!"); + NS_ASSERTION(!mFile, "Should never have a file here!"); + NS_ASSERTION(!mPerm, "This should always be 0!"); + + if (aParams.type() != InputStreamParams::TFileInputStreamParams) { + NS_WARNING("Received unknown parameters from the other process!"); + return false; + } + + const FileInputStreamParams& params = aParams.get_FileInputStreamParams(); + + const FileDescriptor& fd = params.fileDescriptor(); + + if (fd.IsValid()) { + auto rawFD = fd.ClonePlatformHandle(); + PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release())); + if (!fileDesc) { + NS_WARNING("Failed to import file handle!"); + return false; + } + mFD = fileDesc; + mState = eOpened; + } else { + NS_WARNING("Received an invalid file descriptor!"); + mState = eError; + mErrorValue = NS_ERROR_FILE_NOT_FOUND; + } + + mBehaviorFlags = params.behaviorFlags(); + + if (!XRE_IsParentProcess()) { + // A child process shouldn't close when it reads the end because it will + // not be able to reopen the file later. + mBehaviorFlags &= ~nsIFileInputStream::CLOSE_ON_EOF; + + // A child process will not be able to reopen the file so this flag is + // meaningless. + mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND; + } + + mIOFlags = params.ioFlags(); + + return true; +} + +bool nsFileInputStream::IsCloneable() const { + // This inputStream is cloneable only if has been created using Init() and + // it owns a nsIFile. This is not true when it is deserialized from IPC. + return XRE_IsParentProcess() && mFile; +} + +NS_IMETHODIMP +nsFileInputStream::GetCloneable(bool* aCloneable) { + *aCloneable = IsCloneable(); + return NS_OK; +} + +NS_IMETHODIMP +nsFileInputStream::Clone(nsIInputStream** aResult) { + MOZ_ASSERT(IsCloneable()); + return NS_NewLocalFileInputStream(aResult, mFile, mIOFlags, mPerm, + mBehaviorFlags); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFileOutputStream + +NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream, nsFileStreamBase, + nsIOutputStream, nsIFileOutputStream) + +nsresult nsFileOutputStream::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsFileOutputStream> stream = new nsFileOutputStream(); + return stream->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) { + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed, + NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = behaviorFlags; + mState = eUnitialized; + + if (ioFlags == -1) ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE; + if (perm <= 0) perm = 0664; + + return MaybeOpen(file, ioFlags, perm, + mBehaviorFlags & nsIFileOutputStream::DEFER_OPEN); +} + +nsresult nsFileOutputStream::InitWithFileDescriptor( + const mozilla::ipc::FileDescriptor& aFd) { + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed, + NS_ERROR_ALREADY_INITIALIZED); + + if (aFd.IsValid()) { + auto rawFD = aFd.ClonePlatformHandle(); + PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release())); + if (!fileDesc) { + NS_WARNING("Failed to import file handle!"); + return NS_ERROR_FAILURE; + } + mFD = fileDesc; + mState = eOpened; + } else { + mState = eError; + mErrorValue = NS_ERROR_FILE_NOT_FOUND; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFileOutputStream::Preallocate(int64_t aLength) { + if (!mFD) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (!mozilla::fallocate(mFD, aLength)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsAtomicFileOutputStream + +NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream, nsFileOutputStream, + nsISafeOutputStream, nsIOutputStream, + nsIFileOutputStream) + +NS_IMETHODIMP +nsAtomicFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) { + // While `PR_APPEND` is not supported, `-1` is used as `ioFlags` parameter + // in some places, and `PR_APPEND | PR_TRUNCATE` does not require appending + // to existing file. So, throw an exception only if `PR_APPEND` is + // explicitly specified without `PR_TRUNCATE`. + if ((ioFlags & PR_APPEND) && !(ioFlags & PR_TRUNCATE)) { + return NS_ERROR_INVALID_ARG; + } + return nsFileOutputStream::Init(file, ioFlags, perm, behaviorFlags); +} + +nsresult nsAtomicFileOutputStream::DoOpen() { + // Make sure mOpenParams.localFile will be empty if we bail somewhere in + // this function + nsCOMPtr<nsIFile> file; + file.swap(mOpenParams.localFile); + + if (!file) { + return NS_ERROR_NOT_INITIALIZED; + } + nsresult rv = file->Exists(&mTargetFileExists); + if (NS_FAILED(rv)) { + NS_ERROR("Can't tell if target file exists"); + mTargetFileExists = + true; // Safer to assume it exists - we just do more work. + } + + // follow symlinks, for two reasons: + // 1) if a user has deliberately set up a profile file as a symlink, we + // honor it + // 2) to make the MoveToNative() in Finish() an atomic operation (which may + // not be the case if moving across directories on different + // filesystems). + nsCOMPtr<nsIFile> tempResult; + rv = file->Clone(getter_AddRefs(tempResult)); + if (NS_SUCCEEDED(rv) && mTargetFileExists) { + tempResult->Normalize(); + } + + if (NS_SUCCEEDED(rv) && mTargetFileExists) { + // Abort if |file| is not writable; it won't work as an output stream. + bool isWritable; + if (NS_SUCCEEDED(file->IsWritable(&isWritable)) && !isWritable) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + uint32_t origPerm; + if (NS_FAILED(file->GetPermissions(&origPerm))) { + NS_ERROR("Can't get permissions of target file"); + origPerm = mOpenParams.perm; + } + + // XXX What if |perm| is more restrictive then |origPerm|? + // This leaves the user supplied permissions as they were. + rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm); + } + if (NS_SUCCEEDED(rv)) { + // nsFileOutputStream::DoOpen will work on the temporary file, so we + // prepare it and place it in mOpenParams.localFile. + mOpenParams.localFile = tempResult; + mTempFile = tempResult; + mTargetFile = file; + rv = nsFileOutputStream::DoOpen(); + } + return rv; +} + +NS_IMETHODIMP +nsAtomicFileOutputStream::Close() { + nsresult rv = nsFileOutputStream::Close(); + + // the consumer doesn't want the original file overwritten - + // so clean up by removing the temp file. + if (mTempFile) { + mTempFile->Remove(false); + mTempFile = nullptr; + } + + return rv; +} + +NS_IMETHODIMP +nsAtomicFileOutputStream::Finish() { + nsresult rv = nsFileOutputStream::Close(); + + // if there is no temp file, don't try to move it over the original target. + // It would destroy the targetfile if close() is called twice. + if (!mTempFile) return rv; + + // Only overwrite if everything was ok, and the temp file could be closed. + if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) { + NS_ENSURE_STATE(mTargetFile); + + if (!mTargetFileExists) { + // If the target file did not exist when we were initialized, then the + // temp file we gave out was actually a reference to the target file. + // since we succeeded in writing to the temp file (and hence succeeded + // in writing to the target file), there is nothing more to do. +#ifdef DEBUG + bool equal; + if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal) { + NS_WARNING("mTempFile not equal to mTargetFile"); + } +#endif + } else { + nsAutoString targetFilename; + rv = mTargetFile->GetLeafName(targetFilename); + if (NS_SUCCEEDED(rv)) { + // This will replace target. + rv = mTempFile->MoveTo(nullptr, targetFilename); + if (NS_FAILED(rv)) mTempFile->Remove(false); + } + } + } else { + mTempFile->Remove(false); + + // if writing failed, propagate the failure code to the caller. + if (NS_FAILED(mWriteResult)) rv = mWriteResult; + } + mTempFile = nullptr; + return rv; +} + +NS_IMETHODIMP +nsAtomicFileOutputStream::Write(const char* buf, uint32_t count, + uint32_t* result) { + nsresult rv = nsFileOutputStream::Write(buf, count, result); + if (NS_SUCCEEDED(mWriteResult)) { + if (NS_FAILED(rv)) { + mWriteResult = rv; + } else if (count != *result) { + mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + + if (NS_FAILED(mWriteResult) && count > 0) { + NS_WARNING("writing to output stream failed! data may be lost"); + } + } + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsSafeFileOutputStream + +NS_IMETHODIMP +nsSafeFileOutputStream::Finish() { + (void)Flush(); + return nsAtomicFileOutputStream::Finish(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFileRandomAccessStream + +nsresult nsFileRandomAccessStream::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream(); + return stream->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS_INHERITED(nsFileRandomAccessStream, nsFileStreamBase, + nsIRandomAccessStream, nsIFileRandomAccessStream, + nsIInputStream, nsIOutputStream) + +NS_IMETHODIMP +nsFileRandomAccessStream::GetInputStream(nsIInputStream** aInputStream) { + nsCOMPtr<nsIInputStream> inputStream(this); + + inputStream.forget(aInputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsFileRandomAccessStream::GetOutputStream(nsIOutputStream** aOutputStream) { + nsCOMPtr<nsIOutputStream> outputStream(this); + + outputStream.forget(aOutputStream); + return NS_OK; +} + +nsIInputStream* nsFileRandomAccessStream::InputStream() { return this; } + +nsIOutputStream* nsFileRandomAccessStream::OutputStream() { return this; } + +RandomAccessStreamParams nsFileRandomAccessStream::Serialize( + nsIInterfaceRequestor* aCallbacks) { + FileRandomAccessStreamParams params; + + if (NS_SUCCEEDED(DoPendingOpen())) { + MOZ_ASSERT(mFD); + FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD)); + MOZ_ASSERT(fd, "This should never be null!"); + + params.fileDescriptor() = FileDescriptor(fd); + + Close(); + } else { + NS_WARNING( + "This file has not been opened (or could not be opened). " + "Sending an invalid file descriptor to the other process!"); + + params.fileDescriptor() = FileDescriptor(); + } + + int32_t behaviorFlags = mBehaviorFlags; + + // The receiving process (or thread) is going to have an open file + // descriptor automatically so transferring this flag is meaningless. + behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN; + + params.behaviorFlags() = behaviorFlags; + + return params; +} + +bool nsFileRandomAccessStream::Deserialize( + RandomAccessStreamParams& aStreamParams) { + MOZ_ASSERT(!mFD, "Already have a file descriptor?!"); + MOZ_ASSERT(mState == nsFileStreamBase::eUnitialized, "Deferring open?!"); + + if (aStreamParams.type() != + RandomAccessStreamParams::TFileRandomAccessStreamParams) { + NS_WARNING("Received unknown parameters from the other process!"); + return false; + } + + const FileRandomAccessStreamParams& params = + aStreamParams.get_FileRandomAccessStreamParams(); + + const FileDescriptor& fd = params.fileDescriptor(); + + if (fd.IsValid()) { + auto rawFD = fd.ClonePlatformHandle(); + PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release())); + if (!fileDesc) { + NS_WARNING("Failed to import file handle!"); + return false; + } + mFD = fileDesc; + mState = eOpened; + } else { + NS_WARNING("Received an invalid file descriptor!"); + mState = eError; + mErrorValue = NS_ERROR_FILE_NOT_FOUND; + } + + mBehaviorFlags = params.behaviorFlags(); + + return true; +} + +NS_IMETHODIMP +nsFileRandomAccessStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) { + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed, + NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = behaviorFlags; + mState = eUnitialized; + + if (ioFlags == -1) ioFlags = PR_RDWR; + if (perm <= 0) perm = 0; + + return MaybeOpen(file, ioFlags, perm, + mBehaviorFlags & nsIFileRandomAccessStream::DEFER_OPEN); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/nsFileStreams.h b/netwerk/base/nsFileStreams.h new file mode 100644 index 0000000000..d402bffb24 --- /dev/null +++ b/netwerk/base/nsFileStreams.h @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsFileStreams_h__ +#define nsFileStreams_h__ + +#include "mozilla/UniquePtr.h" +#include "nsIFileStreams.h" +#include "nsIFile.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIRandomAccessStream.h" +#include "nsISafeOutputStream.h" +#include "nsISeekableStream.h" +#include "nsILineInputStream.h" +#include "nsCOMPtr.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsReadLine.h" +#include <algorithm> + +namespace mozilla { +namespace ipc { +class FileDescriptor; +} // namespace ipc +} // namespace mozilla + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileStreamBase : public nsISeekableStream, public nsIFileMetadata { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIFILEMETADATA + + nsFileStreamBase() = default; + + protected: + virtual ~nsFileStreamBase(); + + nsresult Close(); + nsresult Available(uint64_t* aResult); + nsresult Read(char* aBuf, uint32_t aCount, uint32_t* aResult); + nsresult ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval); + nsresult IsNonBlocking(bool* aNonBlocking); + nsresult Flush(); + nsresult StreamStatus(); + nsresult Write(const char* aBuf, uint32_t aCount, uint32_t* result); + nsresult WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval); + nsresult WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval); + + PRFileDesc* mFD{nullptr}; + + /** + * Flags describing our behavior. See the IDL file for possible values. + */ + int32_t mBehaviorFlags{0}; + + enum { + // This is the default value. It will be changed by Deserialize or Init. + eUnitialized, + // The opening has been deferred. See DEFER_OPEN. + eDeferredOpen, + // The file has been opened. mFD is not null. + eOpened, + // The file has been closed. mFD is null. + eClosed, + // Something bad happen in the Open() or in Deserialize(). The actual + // error value is stored in mErrorValue. + eError + } mState{eUnitialized}; + + struct OpenParams { + nsCOMPtr<nsIFile> localFile; + int32_t ioFlags = 0; + int32_t perm = 0; + }; + + /** + * Data we need to do an open. + */ + OpenParams mOpenParams; + + nsresult mErrorValue{NS_ERROR_FAILURE}; + + /** + * Prepares the data we need to open the file, and either does the open now + * by calling DoOpen(), or leaves it to be opened later by a call to + * DoPendingOpen(). + */ + nsresult MaybeOpen(nsIFile* aFile, int32_t aIoFlags, int32_t aPerm, + bool aDeferred); + + /** + * Cleans up data prepared in MaybeOpen. + */ + void CleanUpOpen(); + + /** + * Open the file. This is called either from MaybeOpen (during Init) + * or from DoPendingOpen (if DEFER_OPEN is used when initializing this + * stream). The default behavior of DoOpen is to open the file and save the + * file descriptor. + */ + virtual nsresult DoOpen(); + + /** + * Based on mState, this method does the opening, return an error, or do + * nothing. If the return value is not NS_OK, please, return it back to the + * callee. + */ + inline nsresult DoPendingOpen(); +}; + +//////////////////////////////////////////////////////////////////////////////// + +// nsFileInputStream is cloneable only on the parent process because only there +// it can open the same file multiple times. + +class nsFileInputStream : public nsFileStreamBase, + public nsIFileInputStream, + public nsILineInputStream, + public nsIIPCSerializableInputStream, + public nsICloneableInputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILEINPUTSTREAM + NS_DECL_NSILINEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + + NS_IMETHOD Close() override; + NS_IMETHOD Tell(int64_t* aResult) override; + NS_IMETHOD Available(uint64_t* _retval) override; + NS_IMETHOD StreamStatus() override; + NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override; + NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval) override { + return nsFileStreamBase::ReadSegments(aWriter, aClosure, aCount, _retval); + } + NS_IMETHOD IsNonBlocking(bool* _retval) override { + return nsFileStreamBase::IsNonBlocking(_retval); + } + + // Overrided from nsFileStreamBase + NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override; + + nsFileInputStream() : mLineBuffer(nullptr) {} + + static nsresult Create(REFNSIID aIID, void** aResult); + + protected: + virtual ~nsFileInputStream() = default; + + nsresult SeekInternal(int32_t aWhence, int64_t aOffset, + bool aClearBuf = true); + + mozilla::UniquePtr<nsLineBuffer<char>> mLineBuffer; + + /** + * The file being opened. + */ + nsCOMPtr<nsIFile> mFile; + /** + * The IO flags passed to Init() for the file open. + */ + int32_t mIOFlags{0}; + /** + * The permissions passed to Init() for the file open. + */ + int32_t mPerm{0}; + + /** + * Cached position for Tell for automatically reopening streams. + */ + int64_t mCachedPosition{0}; + + protected: + /** + * Internal, called to open a file. Parameters are the same as their + * Init() analogues. + */ + nsresult Open(nsIFile* file, int32_t ioFlags, int32_t perm); + + bool IsCloneable() const; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileOutputStream : public nsFileStreamBase, public nsIFileOutputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILEOUTPUTSTREAM + NS_FORWARD_NSIOUTPUTSTREAM(nsFileStreamBase::) + + static nsresult Create(REFNSIID aIID, void** aResult); + nsresult InitWithFileDescriptor(const mozilla::ipc::FileDescriptor& aFd); + + protected: + virtual ~nsFileOutputStream() = default; +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * A safe file output stream that overwrites the destination file only + * once writing is complete. This protects against incomplete writes + * due to the process or the thread being interrupted or crashed. + */ +class nsAtomicFileOutputStream : public nsFileOutputStream, + public nsISafeOutputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISAFEOUTPUTSTREAM + + nsAtomicFileOutputStream() = default; + + virtual nsresult DoOpen() override; + + NS_IMETHOD Close() override; + NS_IMETHOD Write(const char* buf, uint32_t count, uint32_t* result) override; + NS_IMETHOD Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) override; + + protected: + virtual ~nsAtomicFileOutputStream() = default; + + nsCOMPtr<nsIFile> mTargetFile; + nsCOMPtr<nsIFile> mTempFile; + + bool mTargetFileExists{true}; + nsresult mWriteResult{NS_OK}; // Internally set in Write() +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * A safe file output stream that overwrites the destination file only + * once writing + flushing is complete. This protects against more + * classes of software/hardware errors than nsAtomicFileOutputStream, + * at the expense of being more costly to the disk, OS and battery. + */ +class nsSafeFileOutputStream : public nsAtomicFileOutputStream { + public: + NS_IMETHOD Finish() override; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileRandomAccessStream : public nsFileStreamBase, + public nsIFileRandomAccessStream, + public nsIInputStream, + public nsIOutputStream { + public: + static nsresult Create(REFNSIID aIID, void** aResult); + + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSITELLABLESTREAM(nsFileStreamBase::) + NS_FORWARD_NSISEEKABLESTREAM(nsFileStreamBase::) + NS_DECL_NSIRANDOMACCESSSTREAM + NS_DECL_NSIFILERANDOMACCESSSTREAM + NS_FORWARD_NSIINPUTSTREAM(nsFileStreamBase::) + + // Can't use NS_FORWARD_NSIOUTPUTSTREAM due to overlapping methods + // Close() and IsNonBlocking() + NS_IMETHOD Flush() override { return nsFileStreamBase::Flush(); } + NS_IMETHOD Write(const char* aBuf, uint32_t aCount, + uint32_t* _retval) override { + return nsFileStreamBase::Write(aBuf, aCount, _retval); + } + NS_IMETHOD WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval) override { + return nsFileStreamBase::WriteFrom(aFromStream, aCount, _retval); + } + NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval) override { + return nsFileStreamBase::WriteSegments(aReader, aClosure, aCount, _retval); + } + + protected: + virtual ~nsFileRandomAccessStream() = default; +}; + +//////////////////////////////////////////////////////////////////////////////// + +#endif // nsFileStreams_h__ diff --git a/netwerk/base/nsIArrayBufferInputStream.idl b/netwerk/base/nsIArrayBufferInputStream.idl new file mode 100644 index 0000000000..9ba1dc73fa --- /dev/null +++ b/netwerk/base/nsIArrayBufferInputStream.idl @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" + +%{C++ +#include "mozilla/UniquePtr.h" +%} +native Bytes(mozilla::UniquePtr<uint8_t[]>); + +/** + * nsIArrayBufferInputStream + * + * Provides scriptable methods for initializing a nsIInputStream + * implementation with an ArrayBuffer. + */ +[scriptable, builtinclass, uuid(3014dde6-aa1c-41db-87d0-48764a3710f6)] +interface nsIArrayBufferInputStream : nsIInputStream +{ + /** + * SetData - assign an ArrayBuffer to the input stream. + * + * @param buffer - stream data + * @param byteOffset - stream data offset + * @param byteLen - stream data length + */ + [binaryname(SetDataFromJS)] + void setData(in jsval buffer, in uint64_t byteOffset, in uint64_t byteLen); + + /** + * SetData - assign data to the input stream. + * + * @param aBytes - stream data + * @param byteLen - stream data length + */ + [noscript, nostdcall, binaryname(SetData)] + void setDataNative(in Bytes bytes, in uint64_t byteLen); +}; diff --git a/netwerk/base/nsIAsyncStreamCopier.idl b/netwerk/base/nsIAsyncStreamCopier.idl new file mode 100644 index 0000000000..633fe72b6e --- /dev/null +++ b/netwerk/base/nsIAsyncStreamCopier.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsIRequestObserver; +interface nsIEventTarget; + +// You should prefer nsIAsyncStreamCopier2 +[scriptable, uuid(5a19ca27-e041-4aca-8287-eb248d4c50c0)] +interface nsIAsyncStreamCopier : nsIRequest +{ + /** + * Initialize the stream copier. + * + * @param aSource + * contains the data to be copied. + * @param aSink + * specifies the destination for the data. + * @param aTarget + * specifies the thread on which the copy will occur. a null value + * is permitted and will cause the copy to occur on an unspecified + * background thread. + * @param aSourceBuffered + * true if aSource implements ReadSegments. + * @param aSinkBuffered + * true if aSink implements WriteSegments. + * @param aChunkSize + * specifies how many bytes to read/write at a time. this controls + * the granularity of the copying. it should match the segment size + * of the "buffered" streams involved. + * @param aCloseSource + * true if aSource should be closed after copying. + * @param aCloseSink + * true if aSink should be closed after copying. + * + * NOTE: at least one of the streams must be buffered. If you do not know + * whether your streams are buffered, you should use nsIAsyncStreamCopier2 + * instead. + */ + void init(in nsIInputStream aSource, + in nsIOutputStream aSink, + in nsIEventTarget aTarget, + in boolean aSourceBuffered, + in boolean aSinkBuffered, + in unsigned long aChunkSize, + in boolean aCloseSource, + in boolean aCloseSink); + + /** + * asyncCopy triggers the start of the copy. The observer will be notified + * when the copy completes. + * + * @param aObserver + * receives notifications. + * @param aObserverContext + * passed to observer methods. + */ + void asyncCopy(in nsIRequestObserver aObserver, + in nsISupports aObserverContext); +}; diff --git a/netwerk/base/nsIAsyncStreamCopier2.idl b/netwerk/base/nsIAsyncStreamCopier2.idl new file mode 100644 index 0000000000..7de793f51e --- /dev/null +++ b/netwerk/base/nsIAsyncStreamCopier2.idl @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsIRequestObserver; +interface nsIEventTarget; + +[scriptable, uuid(a5b2decf-4ede-4801-8b38-e5fe5db46bf2)] +interface nsIAsyncStreamCopier2 : nsIRequest +{ + /** + * Initialize the stream copier. + * + * If neither the source nor the sink are buffered, buffering will + * be automatically added to the sink. + * + * + * @param aSource + * contains the data to be copied. + * @param aSink + * specifies the destination for the data. + * @param aTarget + * specifies the thread on which the copy will occur. a null value + * is permitted and will cause the copy to occur on an unspecified + * background thread. + * @param aChunkSize + * specifies how many bytes to read/write at a time. this controls + * the granularity of the copying. it should match the segment size + * of the "buffered" streams involved. + * @param aCloseSource + * true if aSource should be closed after copying (this is generally + * the desired behavior). + * @param aCloseSink + * true if aSink should be closed after copying (this is generally + * the desired behavior). + */ + void init(in nsIInputStream aSource, + in nsIOutputStream aSink, + in nsIEventTarget aTarget, + in unsigned long aChunkSize, + in boolean aCloseSource, + in boolean aCloseSink); + + /** + * asyncCopy triggers the start of the copy. The observer will be notified + * when the copy completes. + * + * @param aObserver + * receives notifications. + * @param aObserverContext + * passed to observer methods. + */ + void asyncCopy(in nsIRequestObserver aObserver, + in nsISupports aObserverContext); +}; diff --git a/netwerk/base/nsIAsyncVerifyRedirectCallback.idl b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl new file mode 100644 index 0000000000..8c81a142f8 --- /dev/null +++ b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(8d171460-a716-41f1-92be-8c659db39b45)] +interface nsIAsyncVerifyRedirectCallback : nsISupports +{ + /** + * Complement to nsIChannelEventSink asynchronous callback. The result of + * the redirect decision is passed through this callback. + * + * @param result + * Result of the redirect veto decision. If FAILED the redirect has been + * vetoed. If SUCCEEDED the redirect has been allowed by all consumers. + */ + void onRedirectVerifyCallback(in nsresult result); +}; diff --git a/netwerk/base/nsIAuthInformation.idl b/netwerk/base/nsIAuthInformation.idl new file mode 100644 index 0000000000..a9a74891b1 --- /dev/null +++ b/netwerk/base/nsIAuthInformation.idl @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * A object that hold authentication information. The caller of + * nsIAuthPrompt2::promptUsernameAndPassword or + * nsIAuthPrompt2::promptPasswordAsync provides an object implementing this + * interface; the prompt implementation can then read the values here to prefill + * the dialog. After the user entered the authentication information, it should + * set the attributes of this object to indicate to the caller what was entered + * by the user. + */ +[scriptable, uuid(0d73639c-2a92-4518-9f92-28f71fea5f20)] +interface nsIAuthInformation : nsISupports +{ + /** @name Flags */ + /* @{ */ + /** + * This dialog belongs to a network host. + */ + const uint32_t AUTH_HOST = 1; + + /** + * This dialog belongs to a proxy. + */ + const uint32_t AUTH_PROXY = 2; + + /** + * This dialog needs domain information. The user interface should show a + * domain field, prefilled with the domain attribute's value. + */ + const uint32_t NEED_DOMAIN = 4; + + /** + * This dialog only asks for password information. Authentication prompts + * SHOULD NOT show a username field. Attempts to change the username field + * will have no effect. nsIAuthPrompt2 implementations should, however, show + * its initial value to the user in some form. For example, a paragraph in + * the dialog might say "Please enter your password for user jsmith at + * server intranet". + * + * This flag is mutually exclusive with #NEED_DOMAIN. + */ + const uint32_t ONLY_PASSWORD = 8; + + /** + * We have already tried to log in for this channel + * (with auth values from a previous promptAuth call), + * but it failed, so we now ask the user to provide a new, correct login. + * + * @see also RFC 2616, Section 10.4.2 + */ + const uint32_t PREVIOUS_FAILED = 16; + + /** + * A cross-origin sub-resource requests an authentication. + * The message presented to users must reflect that. + */ + const uint32_t CROSS_ORIGIN_SUB_RESOURCE = 32; + /* @} */ + + /** + * Flags describing this dialog. A bitwise OR of the flag values + * above. + * + * It is possible that neither #AUTH_HOST nor #AUTH_PROXY are set. + * + * Auth prompts should ignore flags they don't understand; especially, they + * should not throw an exception because of an unsupported flag. + */ + readonly attribute unsigned long flags; + + /** + * The server-supplied realm of the authentication as defined in RFC 2617. + * Can be the empty string if the protocol does not support realms. + * Otherwise, this is a human-readable string like "Secret files". + */ + readonly attribute AString realm; + + /** + * The authentication scheme used for this request, if applicable. If the + * protocol for this authentication does not support schemes, this will be + * the empty string. Otherwise, this will be a string such as "basic" or + * "digest". This string will always be in lowercase. + */ + readonly attribute AUTF8String authenticationScheme; + + /** + * The initial value should be used to prefill the dialog or be shown + * in some other way to the user. + * On return, this parameter should contain the username entered by + * the user. + * This field can only be changed if the #ONLY_PASSWORD flag is not set. + */ + attribute AString username; + + /** + * The initial value should be used to prefill the dialog or be shown + * in some other way to the user. + * The password should not be shown in clear. + * On return, this parameter should contain the password entered by + * the user. + */ + attribute AString password; + + /** + * The initial value should be used to prefill the dialog or be shown + * in some other way to the user. + * On return, this parameter should contain the domain entered by + * the user. + * This attribute is only used if flags include #NEED_DOMAIN. + */ + attribute AString domain; +}; diff --git a/netwerk/base/nsIAuthModule.idl b/netwerk/base/nsIAuthModule.idl new file mode 100644 index 0000000000..87985f2a57 --- /dev/null +++ b/netwerk/base/nsIAuthModule.idl @@ -0,0 +1,146 @@ +/* vim:set ts=4 sw=4 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +[uuid(6e35dbc0-49ef-4e2c-b1ea-b72ec64450a2)] +interface nsIAuthModule : nsISupports +{ + /** + * Default behavior. + */ + const unsigned long REQ_DEFAULT = 0; + + /** + * Client and server will be authenticated. + */ + const unsigned long REQ_MUTUAL_AUTH = (1 << 0); + + /** + * The server is allowed to impersonate the client. The REQ_MUTUAL_AUTH + * flag may also need to be specified in order for this flag to take + * effect. + */ + const unsigned long REQ_DELEGATE = (1 << 1); + + /** + * The authentication is required for a proxy connection. + */ + const unsigned long REQ_PROXY_AUTH = (1 << 2); + + /** + * Flags used for telemetry. + */ + const unsigned long NTLM_MODULE_SAMBA_AUTH_PROXY = 0; + const unsigned long NTLM_MODULE_SAMBA_AUTH_DIRECT = 1; + const unsigned long NTLM_MODULE_WIN_API_PROXY = 2; + const unsigned long NTLM_MODULE_WIN_API_DIRECT = 3; + const unsigned long NTLM_MODULE_GENERIC_PROXY = 4; + const unsigned long NTLM_MODULE_GENERIC_DIRECT = 5; + const unsigned long NTLM_MODULE_KERBEROS_PROXY = 6; + const unsigned long NTLM_MODULE_KERBEROS_DIRECT = 7; + + /** Other flags may be defined in the future */ + + /** + * Called to initialize an auth module. The other methods cannot be called + * unless this method succeeds. + * + * @param aServiceName + * the service name, which may be null if not applicable (e.g., for + * NTLM, this parameter should be null). + * @param aServiceFlags + * a bitwise-or of the REQ_ flags defined above (pass REQ_DEFAULT + * for default behavior). + * @param aDomain + * the authentication domain, which may be null if not applicable. + * @param aUsername + * the user's login name + * @param aPassword + * the user's password + */ + void init(in ACString aServiceName, + in unsigned long aServiceFlags, + in AString aDomain, + in AString aUsername, + in AString aPassword); + + /** + * Called to get the next token in a sequence of authentication steps. + * + * @param aInToken + * A buffer containing the input token (e.g., a challenge from a + * server). This may be null. + * @param aInTokenLength + * The length of the input token. + * @param aOutToken + * If getNextToken succeeds, then aOutToken will point to a buffer + * to be sent in response to the server challenge. The length of + * this buffer is given by aOutTokenLength. The buffer at aOutToken + * must be recycled with a call to free. + * @param aOutTokenLength + * If getNextToken succeeds, then aOutTokenLength contains the + * length of the buffer (number of bytes) pointed to by aOutToken. + */ + void getNextToken([const] in voidPtr aInToken, + in unsigned long aInTokenLength, + out voidPtr aOutToken, + out unsigned long aOutTokenLength); + /** + * Once a security context has been established through calls to GetNextToken() + * it may be used to protect data exchanged between client and server. Calls + * to Wrap() are used to protect items of data to be sent to the server. + * + * @param aInToken + * A buffer containing the data to be sent to the server + * @param aInTokenLength + * The length of the input token + * @param confidential + * If set to true, Wrap() will encrypt the data, otherwise data will + * just be integrity protected (checksummed) + * @param aOutToken + * A buffer containing the resulting data to be sent to the server + * @param aOutTokenLength + * The length of the output token buffer + * + * Wrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying authentication + * mechanism does not support security layers. + */ + void wrap([const] in voidPtr aInToken, + in unsigned long aInTokenLength, + in boolean confidential, + out voidPtr aOutToken, + out unsigned long aOutTokenLength); + + /** + * Unwrap() is used to unpack, decrypt, and verify the checksums on data + * returned by a server when security layers are in use. + * + * @param aInToken + * A buffer containing the data received from the server + * @param aInTokenLength + * The length of the input token + * @param aOutToken + * A buffer containing the plaintext data from the server + * @param aOutTokenLength + * The length of the output token buffer + * + * Unwrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying + * authentication mechanism does not support security layers. + */ + void unwrap([const] in voidPtr aInToken, + in unsigned long aInTokenLength, + out voidPtr aOutToken, + out unsigned long aOutTokenLength); + +%{C++ + /** + * Create a new instance of an auth module. + * + * @param aType + * The type of the auth module to be constructed. + */ + static already_AddRefed<nsIAuthModule> CreateInstance(const char* aType); +%} +}; diff --git a/netwerk/base/nsIAuthPrompt.idl b/netwerk/base/nsIAuthPrompt.idl new file mode 100644 index 0000000000..fbbac6ee9f --- /dev/null +++ b/netwerk/base/nsIAuthPrompt.idl @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIPrompt; + +[scriptable, uuid(358089f9-ee4b-4711-82fd-bcd07fc62061)] +interface nsIAuthPrompt : nsISupports +{ + const uint32_t SAVE_PASSWORD_NEVER = 0; + const uint32_t SAVE_PASSWORD_FOR_SESSION = 1; + const uint32_t SAVE_PASSWORD_PERMANENTLY = 2; + + /** + * Puts up a text input dialog with OK and Cancel buttons. + * Note: prompt uses separate args for the "in" and "out" values of the + * input field, whereas the other functions use a single inout arg. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param defaultText The default text to display in the text input box. + * @param result The value entered by the user if OK was + * selected. + * @return true for OK, false for Cancel + */ + boolean prompt(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + in wstring defaultText, + out wstring result); + + /** + * Puts up a username/password dialog with OK and Cancel buttons. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param user The username entered in the dialog. + * @param pwd The password entered by the user if OK was + * selected. + * @return true for OK, false for Cancel + */ + boolean promptUsernameAndPassword(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + inout wstring user, + inout wstring pwd); + + /** + * Puts up an async username/password dialog with OK and Cancel buttons. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param user The username entered in the dialog. + * @param pwd The password entered by the user if OK was + * selected. + * @return promise resolving to true for OK, false for Cancel + */ + Promise asyncPromptUsernameAndPassword(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + inout wstring user, + inout wstring pwd); + + /** + * Puts up a password dialog with OK and Cancel buttons. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test. If a username is + * specified (http://user@site.com) it will be used + * when matching existing logins or saving new ones. + * If no username is specified, only password-only + * logins will be matched or saved. + * Note: if a username is specified, the username + * should be escaped. + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param pwd The password entered by the user if OK was + * selected. + * @return true for OK, false for Cancel + */ + boolean promptPassword(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + inout wstring pwd); + + /** + * Puts up an async password dialog with OK and Cancel buttons. + * @param dialogText The title for the dialog. + * @param text The text to display in the dialog. + * @param passwordRealm The "realm" the password belongs to: e.g. + * ldap://localhost/dc=test. If a username is + * specified (http://user@site.com) it will be used + * when matching existing logins or saving new ones. + * If no username is specified, only password-only + * logins will be matched or saved. + * Note: if a username is specified, the username + * should be escaped. + * @param savePassword One of the SAVE_PASSWORD_* options above. + * @param pwd The password entered by the user if OK was + * selected. + * @return promise resolving to true for OK, false for Cancel + */ + Promise asyncPromptPassword(in wstring dialogTitle, + in wstring text, + in wstring passwordRealm, + in uint32_t savePassword, + inout wstring pwd); +}; diff --git a/netwerk/base/nsIAuthPrompt2.idl b/netwerk/base/nsIAuthPrompt2.idl new file mode 100644 index 0000000000..d94d50dfcc --- /dev/null +++ b/netwerk/base/nsIAuthPrompt2.idl @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAuthPromptCallback; +interface nsIChannel; +interface nsICancelable; +interface nsIAuthInformation; + +/** + * An interface allowing to prompt for a username and password. This interface + * is usually acquired using getInterface on notification callbacks or similar. + * It can be used to prompt users for authentication information, either + * synchronously or asynchronously. + */ +[scriptable, uuid(651395EB-8612-4876-8AC0-A88D4DCE9E1E)] +interface nsIAuthPrompt2 : nsISupports +{ + /** @name Security Levels */ + /* @{ */ + /** + * The password will be sent unencrypted. No security provided. + */ + const uint32_t LEVEL_NONE = 0; + /** + * Password will be sent encrypted, but the connection is otherwise + * insecure. + */ + const uint32_t LEVEL_PW_ENCRYPTED = 1; + /** + * The connection, both for password and data, is secure. + */ + const uint32_t LEVEL_SECURE = 2; + /* @} */ + + /** + * Requests a username and a password. Implementations will commonly show a + * dialog with a username and password field, depending on flags also a + * domain field. + * + * @param aChannel + * The channel that requires authentication. + * @param level + * One of the level constants from above. See there for descriptions + * of the levels. + * @param authInfo + * Authentication information object. The implementation should fill in + * this object with the information entered by the user before + * returning. + * + * @retval true + * Authentication can proceed using the values in the authInfo + * object. + * @retval false + * Authentication should be cancelled, usually because the user did + * not provide username/password. + * + * @note Exceptions thrown from this function will be treated like a + * return value of false. + * @deprecated use asyncPromptAuth + */ + boolean promptAuth(in nsIChannel aChannel, + in uint32_t level, + in nsIAuthInformation authInfo); + + /** + * Asynchronously prompt the user for a username and password. + * This has largely the same semantics as promptUsernameAndPassword(), + * but must return immediately after calling and return the entered + * data in a callback. + * + * If the user closes the dialog using a cancel button or similar, + * the callback's nsIAuthPromptCallback::onAuthCancelled method must be + * called. + * Calling nsICancelable::cancel on the returned object SHOULD close the + * dialog and MUST call nsIAuthPromptCallback::onAuthCancelled on the provided + * callback. + * + * This implementation may: + * + * 1) Coalesce identical prompts. This means prompts that are guaranteed to + * want the same auth information from the user. A single prompt will be + * shown; then the callbacks for all the coalesced prompts will be notified + * with the resulting auth information. + * 2) Serialize prompts that are all in the same "context" (this might mean + * application-wide, for a given window, or something else depending on + * the user interface) so that the user is not deluged with prompts. + * + * @throw + * This method may throw any exception when the prompt fails to queue e.g + * because of out-of-memory error. It must not throw when the prompt + * could already be potentially shown to the user. In that case information + * about the failure has to come through the callback. This way we + * prevent multiple dialogs shown to the user because consumer may fall + * back to synchronous prompt on synchronous failure of this method. + */ + nsICancelable asyncPromptAuth(in nsIChannel aChannel, + in nsIAuthPromptCallback aCallback, + in nsISupports aContext, + in uint32_t level, + in nsIAuthInformation authInfo); +}; diff --git a/netwerk/base/nsIAuthPromptAdapterFactory.idl b/netwerk/base/nsIAuthPromptAdapterFactory.idl new file mode 100644 index 0000000000..e763d47146 --- /dev/null +++ b/netwerk/base/nsIAuthPromptAdapterFactory.idl @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAuthPrompt; +interface nsIAuthPrompt2; + +/** + * An interface for wrapping nsIAuthPrompt interfaces to make + * them usable via an nsIAuthPrompt2 interface. + */ +[scriptable, uuid(60e46383-bb9a-4860-8962-80d9c5c05ddc)] +interface nsIAuthPromptAdapterFactory : nsISupports +{ + /** + * Wrap an object implementing nsIAuthPrompt so that it's usable via + * nsIAuthPrompt2. + */ + nsIAuthPrompt2 createAdapter(in nsIAuthPrompt aPrompt); +}; diff --git a/netwerk/base/nsIAuthPromptCallback.idl b/netwerk/base/nsIAuthPromptCallback.idl new file mode 100644 index 0000000000..27f89f04c0 --- /dev/null +++ b/netwerk/base/nsIAuthPromptCallback.idl @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAuthInformation; + +/** + * Interface for callback methods for the asynchronous nsIAuthPrompt2 method. + * Callers MUST call exactly one method if nsIAuthPrompt2::promptPasswordAsync + * returns successfully. They MUST NOT call any method on this interface before + * promptPasswordAsync returns. + */ +[scriptable, uuid(bdc387d7-2d29-4cac-92f1-dd75d786631d)] +interface nsIAuthPromptCallback : nsISupports +{ + /** + * Authentication information is available. + * + * @param aContext + * The context as passed to promptPasswordAsync + * @param aAuthInfo + * Authentication information. Must be the same object that was passed + * to promptPasswordAsync. + * + * @note Any exceptions thrown from this method should be ignored. + */ + void onAuthAvailable(in nsISupports aContext, + in nsIAuthInformation aAuthInfo); + + /** + * Notification that the prompt was cancelled. + * + * @param aContext + * The context that was passed to promptPasswordAsync. + * @param userCancel + * If false, this prompt was cancelled by calling the + * the cancel method on the nsICancelable; otherwise, + * it was cancelled by the user. + */ + void onAuthCancelled(in nsISupports aContext, in boolean userCancel); +}; diff --git a/netwerk/base/nsIAuthPromptProvider.idl b/netwerk/base/nsIAuthPromptProvider.idl new file mode 100644 index 0000000000..e8ff122ec2 --- /dev/null +++ b/netwerk/base/nsIAuthPromptProvider.idl @@ -0,0 +1,34 @@ +/* -*- Mode: idl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(bd9dc0fa-68ce-47d0-8859-6418c2ae8576)] +interface nsIAuthPromptProvider : nsISupports +{ + /** + * Normal (non-proxy) prompt request. + */ + const uint32_t PROMPT_NORMAL = 0; + + /** + * Proxy auth request. + */ + const uint32_t PROMPT_PROXY = 1; + + /** + * Request a prompt interface for the given prompt reason; + * @throws NS_ERROR_NOT_AVAILABLE if no prompt is allowed or + * available for the given reason. + * + * @param aPromptReason The reason for the auth prompt; + * one of #PROMPT_NORMAL or #PROMPT_PROXY + * @param iid The desired interface, e.g. + * NS_GET_IID(nsIAuthPrompt2). + * @returns an nsIAuthPrompt2 interface, or throws NS_ERROR_NOT_AVAILABLE + */ + void getAuthPrompt(in uint32_t aPromptReason, in nsIIDRef iid, + [iid_is(iid),retval] out nsQIResult result); +}; diff --git a/netwerk/base/nsIBackgroundFileSaver.idl b/netwerk/base/nsIBackgroundFileSaver.idl new file mode 100644 index 0000000000..0b26852c28 --- /dev/null +++ b/netwerk/base/nsIBackgroundFileSaver.idl @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIArray; +interface nsIBackgroundFileSaverObserver; +interface nsIFile; + +/** + * Allows saving data to a file, while handling all the input/output on a + * background thread, including the initial file name assignment and any + * subsequent renaming of the target file. + * + * This interface is designed for file downloads. Generally, they start in the + * temporary directory, while the user is selecting the final name. Then, they + * are moved to the chosen target directory with a ".part" extension appended to + * the file name. Finally, they are renamed when the download is completed. + * + * Components implementing both nsIBackgroundFileSaver and nsIStreamListener + * allow data to be fed using an implementation of OnDataAvailable that never + * blocks the calling thread. They suspend the request that drives the stream + * listener in case too much data is being fed, and resume it when the data has + * been written. Calling OnStopRequest does not necessarily close the target + * file, and the Finish method must be called to complete the operation. + * + * Components implementing both nsIBackgroundFileSaver and nsIAsyncOutputStream + * allow data to be fed directly to the non-blocking output stream, that however + * may return NS_BASE_STREAM_WOULD_BLOCK in case too much data is being fed. + * Closing the output stream does not necessarily close the target file, and the + * Finish method must be called to complete the operation. + * + * @remarks Implementations may require the consumer to always call Finish. If + * the object reference is released without calling Finish, a memory + * leak may occur, and the target file might be kept locked. All + * public methods of the interface may only be called from the main + * thread. + */ +[scriptable, uuid(c43544a4-682c-4262-b407-2453d26e660d)] +interface nsIBackgroundFileSaver : nsISupports +{ + /** + * This observer receives notifications when the target file name changes and + * when the operation completes, successfully or not. + * + * @remarks A strong reference to the observer is held. Notification events + * are dispatched to the thread that created the object that + * implements nsIBackgroundFileSaver. + */ + attribute nsIBackgroundFileSaverObserver observer; + + /** + * An Array of Array of Array of bytes, representing a chain of + * X.509 certificates, the last of which signed the downloaded file. + * Each list may belong to a different signer and contain certificates + * all the way up to the root. + * + * @throws NS_ERROR_NOT_AVAILABLE + * In case this is called before the onSaveComplete method has been + * called to notify success, or enableSignatureInfo has not been + * called. + */ + readonly attribute Array<Array<Array<uint8_t> > > signatureInfo; + + /** + * The SHA-256 hash, in raw bytes, associated with the data that was saved. + * + * In case the enableAppend method has been called, the hash computation + * includes the contents of the existing file, if any. + * + * @throws NS_ERROR_NOT_AVAILABLE + * In case the enableSha256 method has not been called, or before the + * onSaveComplete method has been called to notify success. + */ + readonly attribute ACString sha256Hash; + + /** + * Instructs the component to compute the signatureInfo of the target file, + * and make it available in the signatureInfo property. + * + * @remarks This must be set on the main thread before the first call to + * setTarget. + */ + void enableSignatureInfo(); + + /** + * Instructs the component to compute the SHA-256 hash of the target file, and + * make it available in the sha256Hash property. + * + * @remarks This must be set on the main thread before the first call to + * setTarget. + */ + void enableSha256(); + + /** + * Instructs the component to append data to the initial target file, that + * will be specified by the first call to the setTarget method, instead of + * overwriting the file. + * + * If the initial target file does not exist, this method has no effect. + * + * @remarks This must be set on the main thread before the first call to + * setTarget. + */ + void enableAppend(); + + /** + * Sets the name of the output file to be written. The target can be changed + * after data has already been fed, in which case the existing file will be + * moved to the new destination. + * + * In case the specified file already exists, and this method is called for + * the first time, the file may be either overwritten or appended to, based on + * whether the enableAppend method was called. Subsequent calls always + * overwrite the specified target file with the previously saved data. + * + * No file will be written until this function is called at least once. It's + * recommended not to feed any data until the output file is set. + * + * If an input/output error occurs with the specified file, the save operation + * fails. Failure is notified asynchronously through the observer. + * + * @param aTarget + * New output file to be written. + * @param aKeepPartial + * Indicates whether aFile should be kept as partially completed, + * rather than deleted, if the operation fails or is canceled. This is + * generally set for downloads that use temporary ".part" files. + */ + void setTarget(in nsIFile aTarget, in bool aKeepPartial); + + /** + * Terminates access to the output file, then notifies the observer with the + * specified status code. A failure code will force the operation to be + * canceled, in which case the output file will be deleted if requested. + * + * This forces the involved streams to be closed, thus no more data should be + * fed to the component after this method has been called. + * + * This is the last method that should be called on this object, and the + * target file name cannot be changed anymore after this method has been + * called. Conversely, before calling this method, the file can still be + * renamed even if all the data has been fed. + * + * @param aStatus + * Result code that determines whether the operation should succeed or + * be canceled, and is notified to the observer. If the operation + * fails meanwhile for other reasons, or the observer has been already + * notified of completion, this status code is ignored. + */ + void finish(in nsresult aStatus); +}; + +[scriptable, uuid(ee7058c3-6e54-4411-b76b-3ce87b76fcb6)] +interface nsIBackgroundFileSaverObserver : nsISupports +{ + /** + * Called when the name of the output file has been determined. This function + * may be called more than once if the target file is renamed while saving. + * + * @param aSaver + * Reference to the object that raised the notification. + * @param aTarget + * Name of the file that is being written. + */ + void onTargetChange(in nsIBackgroundFileSaver aSaver, in nsIFile aTarget); + + /** + * Called when the operation completed, and the target file has been closed. + * If the operation succeeded, the target file is ready to be used, otherwise + * it might have been already deleted. + * + * @param aSaver + * Reference to the object that raised the notification. + * @param aStatus + * Result code that determines whether the operation succeeded or + * failed, as well as the failure reason. + */ + void onSaveComplete(in nsIBackgroundFileSaver aSaver, in nsresult aStatus); +}; diff --git a/netwerk/base/nsIBufferedStreams.idl b/netwerk/base/nsIBufferedStreams.idl new file mode 100644 index 0000000000..46f0112900 --- /dev/null +++ b/netwerk/base/nsIBufferedStreams.idl @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" +#include "nsIOutputStream.idl" + +/** + * An input stream that reads ahead and keeps a buffer coming from another input + * stream so that fewer accesses to the underlying stream are necessary. + */ +[scriptable, builtinclass, uuid(616f5b48-da09-11d3-8cda-0060b0fc14a3)] +interface nsIBufferedInputStream : nsIInputStream +{ + /** + * @param fillFromStream - add buffering to this stream + * @param bufferSize - specifies the maximum buffer size + */ + void init(in nsIInputStream fillFromStream, + in unsigned long bufferSize); + + /** + * Get the wrapped data stream + */ + readonly attribute nsIInputStream data; +}; + +/** + * An output stream that stores up data to write out to another output stream + * and does the entire write only when the buffer is full, so that fewer writes + * to the underlying output stream are necessary. + */ +[scriptable, builtinclass, uuid(6476378a-da09-11d3-8cda-0060b0fc14a3)] +interface nsIBufferedOutputStream : nsIOutputStream +{ + /** + * @param sinkToStream - add buffering to this stream + * @param bufferSize - specifies the maximum buffer size + */ + void init(in nsIOutputStream sinkToStream, + in unsigned long bufferSize); + + /** + * Get the wrapped data stream + */ + readonly attribute nsIOutputStream data; +}; diff --git a/netwerk/base/nsIByteRangeRequest.idl b/netwerk/base/nsIByteRangeRequest.idl new file mode 100644 index 0000000000..4a10126d69 --- /dev/null +++ b/netwerk/base/nsIByteRangeRequest.idl @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(C1B1F426-7E83-4759-9F88-0E1B17F49366)] +interface nsIByteRangeRequest : nsISupports +{ + /** + * Returns true IFF this request is a byte range request, otherwise it + * returns false (This is effectively the same as checking to see if + * |startRequest| is zero and |endRange| is the content length.) + */ + readonly attribute boolean isByteRangeRequest; + + /** + * Absolute start position in remote file for this request. + */ + readonly attribute long long startRange; + + /** + * Absolute end postion in remote file for this request + */ + readonly attribute long long endRange; +}; diff --git a/netwerk/base/nsICacheInfoChannel.idl b/netwerk/base/nsICacheInfoChannel.idl new file mode 100644 index 0000000000..b7bb44093c --- /dev/null +++ b/netwerk/base/nsICacheInfoChannel.idl @@ -0,0 +1,207 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAsyncOutputStream; +interface nsIInputStream; + +%{C++ +#include "nsTArray.h" +namespace mozilla { +namespace net { +class PreferredAlternativeDataTypeParams; +} +} // namespace mozilla +%} + +[ref] native ConstPreferredAlternativeDataTypeArray(const nsTArray<mozilla::net::PreferredAlternativeDataTypeParams>); + +[scriptable, uuid(1fb8ccf2-5fa5-45ec-bc57-8c8022a5d0d3)] +interface nsIInputStreamReceiver : nsISupports +{ + void onInputStreamReady(in nsIInputStream aStream); +}; + +[scriptable, builtinclass, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)] +interface nsICacheInfoChannel : nsISupports +{ + /** + * Get the number of times the cache entry has been opened. This attribute is + * equivalent to nsICachingChannel.cacheToken.fetchCount. + * + * @throws NS_ERROR_NOT_AVAILABLE if the cache entry or the alternate data + * cache entry cannot be read. + */ + readonly attribute uint32_t cacheTokenFetchCount; + + /** + * Get expiration time from cache token. This attribute is equivalent to + * nsICachingChannel.cacheToken.expirationTime. + */ + readonly attribute uint32_t cacheTokenExpirationTime; + + /** + * TRUE if this channel's data is being loaded from the cache. This value + * is undefined before the channel fires its OnStartRequest notification + * and after the channel fires its OnStopRequest notification. + */ + boolean isFromCache(); + + /** + * Returns true if the channel raced the cache and network requests. + * In order to determine if the response is coming from the cache or the + * network, the consumer can check isFromCache(). + * The method can only be called after the channel fires its OnStartRequest + * notification. + */ + boolean isRacing(); + + /** + * The unique ID of the corresponding nsICacheEntry from which the response is + * retrieved. By comparing the returned value, we can judge whether the data + * of two distinct nsICacheInfoChannels is from the same nsICacheEntry. This + * scenario could be useful when verifying whether the alternative data from + * one nsICacheInfochannel matches the main data from another one. + * + * Note: NS_ERROR_NOT_AVAILABLE is thrown when a nsICacheInfoChannel has no + * valid corresponding nsICacheEntry. + */ + uint64_t getCacheEntryId(); + + /** + * Set/get the cache key. This integer uniquely identifies the data in + * the cache for this channel. + * + * A cache key retrieved from a particular instance of nsICacheInfoChannel + * could be set on another instance of nsICacheInfoChannel provided the + * underlying implementations are compatible and provided the new + * channel instance was created with the same URI. The implementation of + * nsICacheInfoChannel would be expected to use the cache entry identified + * by the cache token. Depending on the value of nsIRequest::loadFlags, + * the cache entry may be validated, overwritten, or simply read. + * + * The cache key may be 0 indicating that the URI of the channel is + * sufficient to locate the same cache entry. Setting a 0 cache key + * is likewise valid. + */ + attribute unsigned long cacheKey; + + /** + * Tells the channel to behave as if the LOAD_FROM_CACHE flag has been set, + * but without affecting the loads for the entire loadGroup in case of this + * channel being the default load group's channel. + */ + attribute boolean allowStaleCacheContent; + + /** + * Tells the priority for LOAD_CACHE is raised over LOAD_BYPASS_CACHE or + * LOAD_BYPASS_LOCAL_CACHE in case those flags are set at the same time. + */ + attribute boolean preferCacheLoadOverBypass; + + cenum PreferredAlternativeDataDeliveryType : 8 { + /** + * Do not deliver alternative data stream. + */ + NONE = 0, + + /** + * Deliver alternative data stream upon additional request. + */ + ASYNC = 1, + + /** + * Deliver alternative data stream during IPC parent/child serialization. + */ + SERIALIZE = 2, + }; + + /** + * Tells the channel to be force validated during soft reload. + */ + attribute boolean forceValidateCacheContent; + + /** + * Calling this method instructs the channel to serve the alternative data + * if that was previously saved in the cache, otherwise it will serve the + * real data. + * @param type + * a string identifying the alt-data format + * @param contentType + * the contentType for which the preference applies. + * an empty contentType means the preference applies for ANY contentType + * @param deliverAltData + * if false, also if alt-data is available, the channel will deliver + * the original data. + * + * The method may be called several times, with different type and contentType. + * + * Must be called before AsyncOpen. + */ + void preferAlternativeDataType(in ACString type, in ACString contentType, + in nsICacheInfoChannel_PreferredAlternativeDataDeliveryType deliverAltData); + + /** + * Get the preferred alternative data type set by preferAlternativeDataType(). + * The returned types stand for the desired data type instead of the type of the + * information retrieved from the network stack. + */ + [noscript, notxpcom, nostdcall] + ConstPreferredAlternativeDataTypeArray preferredAlternativeDataTypes(); + + /** + * Holds the type of the alternative data representation that the channel + * is returning. + * Is empty string if no alternative data representation was requested, or + * if the requested representation wasn't found in the cache. + * Can only be called during or after OnStartRequest. + */ + readonly attribute ACString alternativeDataType; + + /** + * If preferAlternativeDataType() has been called passing deliverAltData + * equal to false, this attribute will expose the alt-data inputStream if + * avaiable. + */ + readonly attribute nsIInputStream alternativeDataInputStream; + + /** + * Sometimes when the channel is delivering alt-data, we may want to somehow + * access the original content too. This method asynchronously opens the + * input stream and delivers it to the receiver. + */ + void getOriginalInputStream(in nsIInputStreamReceiver aReceiver); + + /** + * Opens and returns an output stream that a consumer may use to save an + * alternate representation of the data. + * Must be called after the OnStopRequest that delivered the real data. + * The consumer may choose to replace the saved alt representation. + * Opening the output stream will fail if there are any open input streams + * reading the already saved alt representation. After successfully opening + * an output stream, if there is an error before the entire alt data can be + * written successfully, the client must signal failure by passing an error + * code to CloseWithStatus(). + * + * @param type + * type of the alternative data representation + * @param predictedSize + * Predicted size of the data that will be written. It's used to decide + * whether the resulting entry would exceed size limit, in which case + * an error is thrown. If the size isn't known in advance, -1 should be + * passed. + */ + nsIAsyncOutputStream openAlternativeOutputStream(in ACString type, in long long predictedSize); +}; + +%{ C++ +namespace mozilla { +namespace net { + +using PreferredAlternativeDataDeliveryTypeIPC = nsICacheInfoChannel::PreferredAlternativeDataDeliveryType; + +} +} // namespace mozilla +%} diff --git a/netwerk/base/nsICachingChannel.idl b/netwerk/base/nsICachingChannel.idl new file mode 100644 index 0000000000..3623423ff7 --- /dev/null +++ b/netwerk/base/nsICachingChannel.idl @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsICacheInfoChannel.idl" + +interface nsIFile; + +/** + * A channel may optionally implement this interface to allow clients + * to affect its behavior with respect to how it uses the cache service. + * + * This interface provides: + * 1) Support for "stream as file" semantics (for JAR and plugins). + * 2) Support for "pinning" cached data in the cache (for printing and save-as). + * 3) Support for uniquely identifying cached data in cases when the URL + * is insufficient (e.g., HTTP form submission). + */ +[scriptable, builtinclass, uuid(dd1d6122-5ecf-4fe4-8f0f-995e7ab3121a)] +interface nsICachingChannel : nsICacheInfoChannel +{ + /** + * Set/get the cache token... uniquely identifies the data in the cache. + * Holding a reference to this token prevents the cached data from being + * removed. + * + * A cache token retrieved from a particular instance of nsICachingChannel + * could be set on another instance of nsICachingChannel provided the + * underlying implementations are compatible. The implementation of + * nsICachingChannel would be expected to only read from the cache entry + * identified by the cache token and not try to validate it. + * + * The cache token can be QI'd to a nsICacheEntryInfo if more detail + * about the cache entry is needed (e.g., expiration time). + */ + attribute nsISupports cacheToken; + + /** + * Instructs the channel to only store the metadata of the entry, and not + * the content. When reading an existing entry, this automatically sets + * LOAD_ONLY_IF_MODIFIED flag. + * Must be called before asyncOpen(). + */ + attribute boolean cacheOnlyMetadata; + + /** + * Tells the channel to use the pinning storage. + */ + attribute boolean pin; + + /** + * Overrides cache validation for a time specified in seconds. + * + * @param aSecondsToTheFuture + * + */ + void forceCacheEntryValidFor(in unsigned long aSecondsToTheFuture); + + /************************************************************************** + * Caching channel specific load flags: + */ + + /** + * This load flag inhibits fetching from the net. An error of + * NS_ERROR_DOCUMENT_NOT_CACHED will be sent to the listener's + * onStopRequest if network IO is necessary to complete the request. + * + * This flag can be used to find out whether fetching this URL would + * cause validation of the cache entry via the network. + * + * Combining this flag with LOAD_BYPASS_LOCAL_CACHE will cause all + * loads to fail. + */ + const unsigned long LOAD_NO_NETWORK_IO = 1 << 26; + + /** + * This load flag causes the local cache to be skipped when fetching a + * request. Unlike LOAD_BYPASS_CACHE, it does not force an end-to-end load + * (i.e., it does not affect proxy caches). + */ + const unsigned long LOAD_BYPASS_LOCAL_CACHE = 1 << 28; + + /** + * This load flag causes the local cache to be skipped if the request + * would otherwise block waiting to access the cache. + */ + const unsigned long LOAD_BYPASS_LOCAL_CACHE_IF_BUSY = 1 << 29; + + /** + * This load flag inhibits fetching from the net if the data in the cache + * has been evicted. An error of NS_ERROR_DOCUMENT_NOT_CACHED will be sent + * to the listener's onStopRequest in this case. This flag is set + * automatically when the application is offline. + */ + const unsigned long LOAD_ONLY_FROM_CACHE = 1 << 30; + + /** + * This load flag controls what happens when a document would be loaded + * from the cache to satisfy a call to AsyncOpen. If this attribute is + * set to TRUE, then the document will not be loaded from the cache. A + * stream listener can check nsICachingChannel::isFromCache to determine + * if the AsyncOpen will actually result in data being streamed. + * + * If this flag has been set, and the request can be satisfied via the + * cache, then the OnDataAvailable events will be skipped. The listener + * will only see OnStartRequest followed by OnStopRequest. + */ + const unsigned long LOAD_ONLY_IF_MODIFIED = 1 << 31; +}; diff --git a/netwerk/base/nsICancelable.idl b/netwerk/base/nsICancelable.idl new file mode 100644 index 0000000000..c558dc6e27 --- /dev/null +++ b/netwerk/base/nsICancelable.idl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface provides a means to cancel an operation that is in progress. + */ +[scriptable, uuid(d94ac0a0-bb18-46b8-844e-84159064b0bd)] +interface nsICancelable : nsISupports +{ + /** + * Call this method to request that this object abort whatever operation it + * may be performing. + * + * @param aReason + * Pass a failure code to indicate the reason why this operation is + * being canceled. It is an error to pass a success code. + */ + void cancel(in nsresult aReason); +}; diff --git a/netwerk/base/nsICaptivePortalService.idl b/netwerk/base/nsICaptivePortalService.idl new file mode 100644 index 0000000000..e4867765d7 --- /dev/null +++ b/netwerk/base/nsICaptivePortalService.idl @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(b5fd5629-d04c-4138-9529-9311f291ecd4)] +interface nsICaptivePortalServiceCallback : nsISupports +{ + /** + * Invoke callbacks after captive portal detection finished. + */ + void complete(in bool success, in nsresult error); +}; + +/** + * Service used for captive portal detection. + * The service is only active in the main process. It is also available in the + * content process, but only to mirror the captive portal state from the main + * process. + */ +[scriptable, uuid(bdbe0555-fc3d-4f7b-9205-c309ceb2d641)] +interface nsICaptivePortalService : nsISupports +{ + const long UNKNOWN = 0; + const long NOT_CAPTIVE = 1; + const long UNLOCKED_PORTAL = 2; + const long LOCKED_PORTAL = 3; + + /** + * Called from XPCOM to trigger a captive portal recheck. + * A network request will only be performed if no other checks are currently + * ongoing. + * Will not do anything if called in the content process. + */ + void recheckCaptivePortal(); + + /** + * Returns the state of the captive portal. + */ + readonly attribute long state; + +%{C++ + int32_t State() { + int32_t result = nsICaptivePortalService::UNKNOWN; + GetState(&result); + return result; + } +%} + + /** + * Returns the time difference between NOW and the last time a request was + * completed in milliseconds. + */ + readonly attribute unsigned long long lastChecked; +}; + +%{C++ +/** + * This observer notification will be emitted when the captive portal state + * changes. After receiving it, the ContentParent will send an IPC message + * to the ContentChild, which will set the state in the captive portal service + * in the child. + */ +#define NS_IPC_CAPTIVE_PORTAL_SET_STATE "ipc:network:captive-portal-set-state" + +/** + * This notification will be emitted when the captive portal service has + * determined that we can connect to the internet. + * The service will pass either "captive" if there is an unlocked captive portal + * present, or "clear" if no captive portal was detected. + * Note: this notification only gets sent in the parent process. + */ +#define NS_CAPTIVE_PORTAL_CONNECTIVITY "network:captive-portal-connectivity" + +/** + * Similar to NS_CAPTIVE_PORTAL_CONNECTIVITY but only gets dispatched when + * the connectivity changes from UNKNOWN to NOT_CAPTIVE or from LOCKED_PORTAL + * to UNLOCKED_PORTAL. + */ +#define NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED "network:captive-portal-connectivity-changed" + +%} diff --git a/netwerk/base/nsIChannel.idl b/netwerk/base/nsIChannel.idl new file mode 100644 index 0000000000..2269c4faa8 --- /dev/null +++ b/netwerk/base/nsIChannel.idl @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" +#include "nsILoadInfo.idl" + +interface nsIInputStream; +interface nsIInterfaceRequestor; +interface nsIStreamListener; +interface nsITransportSecurityInfo; +interface nsIURI; + +%{C++ +#include "nsCOMPtr.h" +%} + +/** + * The nsIChannel interface allows clients to construct "GET" requests for + * specific protocols, and manage them in a uniform way. Once a channel is + * created (via nsIIOService::newChannel), parameters for that request may + * be set by using the channel attributes, or by QI'ing to a subclass of + * nsIChannel for protocol-specific parameters. Then, the URI can be fetched + * by calling nsIChannel::open or nsIChannel::asyncOpen. + * + * After a request has been completed, the channel is still valid for accessing + * protocol-specific results. For example, QI'ing to nsIHttpChannel allows + * response headers to be retrieved for the corresponding http transaction. + * + * This interface must be used only from the XPCOM main thread. + */ +[scriptable, uuid(2c389865-23db-4aa7-9fe5-60cc7b00697e)] +interface nsIChannel : nsIRequest +{ + /** + * The original URI used to construct the channel. This is used in + * the case of a redirect or URI "resolution" (e.g. resolving a + * resource: URI to a file: URI) so that the original pre-redirect + * URI can still be obtained. This is never null. Attempts to + * set it to null must throw. + * + * NOTE: this is distinctly different from the http Referer (referring URI), + * which is typically the page that contained the original URI (accessible + * from nsIHttpChannel). + * + * NOTE: originalURI isn't yet set on the new channel when + * asyncOnChannelRedirect is called. + */ + attribute nsIURI originalURI; + + /** + * The URI corresponding to the channel. Its value is immutable. + */ + readonly attribute nsIURI URI; + + /** + * The owner, corresponding to the entity that is responsible for this + * channel. Used by the security manager to grant or deny privileges to + * mobile code loaded from this channel. + * + * NOTE: this is a strong reference to the owner, so if the owner is also + * holding a strong reference to the channel, care must be taken to + * explicitly drop its reference to the channel. + */ + attribute nsISupports owner; + + /** + * The notification callbacks for the channel. This is set by clients, who + * wish to provide a means to receive progress, status and protocol-specific + * notifications. If this value is NULL, the channel implementation may use + * the notification callbacks from its load group. The channel may also + * query the notification callbacks from its load group if its notification + * callbacks do not supply the requested interface. + * + * Interfaces commonly requested include: nsIProgressEventSink, nsIPrompt, + * and nsIAuthPrompt/nsIAuthPrompt2. + * + * When the channel is done, it must not continue holding references to + * this object. + * + * NOTE: A channel implementation should take care when "caching" an + * interface pointer queried from its notification callbacks. If the + * notification callbacks are changed, then a cached interface pointer may + * become invalid and may therefore need to be re-queried. + */ + attribute nsIInterfaceRequestor notificationCallbacks; + + /** + * Transport-level security information (if any) corresponding to the + * channel. + * + * NOTE: In some circumstances TLS information is propagated onto + * non-nsIHttpChannel objects to indicate that their contents were likely + * delivered over TLS all the same. + * + * FIXME(bz, bug 1528449) is that still true now that + * document.open() doesn't do this? + */ + readonly attribute nsITransportSecurityInfo securityInfo; + + /** + * The MIME type of the channel's content if available. + * + * NOTE: the content type can often be wrongly specified (e.g., wrong file + * extension, wrong MIME type, wrong document type stored on a server, etc.), + * and the caller most likely wants to verify with the actual data. + * + * Setting contentType before the channel has been opened provides a hint + * to the channel as to what the MIME type is. The channel may ignore this + * hint in deciding on the actual MIME type that it will report. + * + * Setting contentType after onStartRequest has been fired or after open() + * is called will override the type determined by the channel. + * + * Setting contentType between the time that asyncOpen() is called and the + * time when onStartRequest is fired has undefined behavior at this time. + * + * The value of the contentType attribute is a lowercase string. A value + * assigned to this attribute will be parsed and normalized as follows: + * 1- any parameters (delimited with a ';') will be stripped. + * 2- if a charset parameter is given, then its value will replace the + * the contentCharset attribute of the channel. + * 3- the stripped contentType will be lowercased. + * Any implementation of nsIChannel must follow these rules. + */ + attribute ACString contentType; + + /** + * The character set of the channel's content if available and if applicable. + * This attribute only applies to textual data. + * + * The value of the contentCharset attribute is a mixedcase string. + */ + attribute ACString contentCharset; + + /** + * The length of the data associated with the channel if available. A value + * of -1 indicates that the content length is unknown. Note that this is a + * 64-bit value and obsoletes the "content-length" property used on some + * channels. + */ + attribute int64_t contentLength; + + /** + * Synchronously open the channel. + * + * @return blocking input stream to the channel's data. + * + * NOTE: nsIChannel implementations are not required to implement this + * method. Moreover, since this method may block the calling thread, it + * should not be called on a thread that processes UI events. Like any + * other nsIChannel method it must not be called on any thread other + * than the XPCOM main thread. + * + * NOTE: Implementations should throw NS_ERROR_IN_PROGRESS if the channel + * is reopened. + */ + nsIInputStream open(); + + /** + * Asynchronously open this channel. Data is fed to the specified stream + * listener as it becomes available. The stream listener's methods are + * called on the thread that calls asyncOpen and are not called until + * after asyncOpen returns. If asyncOpen returns successfully, the + * channel promises to call at least onStartRequest and onStopRequest. + * + * If the nsIRequest object passed to the stream listener's methods is not + * this channel, an appropriate onChannelRedirect notification needs to be + * sent to the notification callbacks before onStartRequest is called. + * Once onStartRequest is called, all following method calls on aListener + * will get the request that was passed to onStartRequest. + * + * If the channel's and loadgroup's notification callbacks do not provide + * an nsIChannelEventSink when onChannelRedirect would be called, that's + * equivalent to having called onChannelRedirect. + * + * If asyncOpen returns successfully, the channel is responsible for + * keeping itself alive until it has called onStopRequest on aListener or + * called onChannelRedirect. + * + * Implementations are allowed to synchronously add themselves to the + * associated load group (if any). + * + * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the + * channel is reopened. + * NOTE: Implementations should throw an error if the channel has been + * cancelled prior asyncOpen being called. + * + * @param aListener the nsIStreamListener implementation + * @see nsIChannelEventSink for onChannelRedirect + */ + void asyncOpen(in nsIStreamListener aListener); + + /** + * True if the channel has been canceled. + */ + [must_use] readonly attribute boolean canceled; + + /************************************************************************** + * Channel specific load flags: + * + * Bits 16-31 are reserved for future use by this interface or one of its + * derivatives (e.g., see nsICachingChannel). + */ + + /** + * Set (e.g., by the docshell) to indicate whether or not the channel + * corresponds to a document URI. + * While setting this flag is sufficient to mark a channel as a document + * load, _checking_ whether the channel is a document load requires the use + * of the new channel.isDocument + */ + const unsigned long LOAD_DOCUMENT_URI = 1 << 16; + + /** + * If the end consumer for this load has been retargeted after discovering + * its content, this flag will be set: + */ + const unsigned long LOAD_RETARGETED_DOCUMENT_URI = 1 << 17; + + /** + * This flag is set to indicate that this channel is replacing another + * channel. This means that: + * + * 1) the stream listener this channel will be notifying was initially + * passed to the asyncOpen method of some other channel + * + * and + * + * 2) this channel's URI is a better identifier of the resource being + * accessed than this channel's originalURI. + * + * This flag can be set, for example, for redirects or for cases when a + * single channel has multiple parts to it (and thus can follow + * onStopRequest with another onStartRequest/onStopRequest pair, each pair + * for a different request). + */ + const unsigned long LOAD_REPLACE = 1 << 18; + + /** + * Set (e.g., by the docshell) to indicate whether or not the channel + * corresponds to an initial document URI load (e.g., link click). + */ + const unsigned long LOAD_INITIAL_DOCUMENT_URI = 1 << 19; + + /** + * Set (e.g., by the URILoader) to indicate whether or not the end consumer + * for this load has been determined. + */ + const unsigned long LOAD_TARGETED = 1 << 20; + + /** + * If this flag is set, the channel should call the content sniffers as + * described in nsNetCID.h about NS_CONTENT_SNIFFER_CATEGORY. + * + * Note: Channels may ignore this flag; however, new channel implementations + * should only do so with good reason. + */ + const unsigned long LOAD_CALL_CONTENT_SNIFFERS = 1 << 21; + + /** + * This flag tells the channel to bypass URL classifier service check + * when opening the channel. + */ + const unsigned long LOAD_BYPASS_URL_CLASSIFIER = 1 << 22; + + /** + * If this flag is set, the media-type content sniffer will be allowed + * to override any server-set content-type. Otherwise it will only + * be allowed to override "no content type" and application/octet-stream. + */ + const unsigned long LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE = 1 << 23; + + /** + * Set to let explicitely provided credentials be used over credentials + * we have cached previously. In some situations like form login using HTTP + * auth via XMLHttpRequest we need to let consumers override the cached + * credentials explicitely. For form login 403 response instead of 401 is + * usually used to prevent an auth dialog. But any code other then 401/7 + * will leave original credentials in the cache and there is then no way + * to override them for the same user name. + */ + const unsigned long LOAD_EXPLICIT_CREDENTIALS = 1 << 24; + + /** + * Set to force bypass of any service worker interception of the channel. + */ + const unsigned long LOAD_BYPASS_SERVICE_WORKER = 1 << 25; + + // nsICachingChannel load flags begin at bit 26. + + /** + * Access to the type implied or stated by the Content-Disposition header + * if available and if applicable. This allows determining inline versus + * attachment. + * + * Setting contentDisposition provides a hint to the channel about the + * disposition. If the hint is DISPOSITION_ATTACHMENT and a normal + * Content-Disposition header is present, the hinted value will always be + * used. If the hint is DISPOSITION_FORCE_INLINE then the disposition is + * inline and the header is not used. The value from Content-Disposition + * header is only used when the hinted value is not DISPOSITION_INLINE or + * DISPOSITION_FORCE_INLINE. + * If the header is missing the hinted value will be used if set. + * + * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either + * doesn't exist for this type of channel or is empty, and return + * DISPOSITION_ATTACHMENT if an invalid/noncompliant value is present. + */ + attribute unsigned long contentDisposition; + const unsigned long DISPOSITION_INLINE = 0; + const unsigned long DISPOSITION_ATTACHMENT = 1; + const unsigned long DISPOSITION_FORCE_INLINE = 2; + + /** + * Access to the filename portion of the Content-Disposition header if + * available and if applicable. This allows getting the preferred filename + * without having to parse it out yourself. + * + * Setting contentDispositionFilename provides a hint to the channel about + * the disposition. If a normal Content-Disposition header is present its + * value will always be used. If it is missing the hinted value will be + * used if set. + * + * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header doesn't + * exist for this type of channel, if the header is empty, if the header + * doesn't contain a filename portion, or the value of the filename + * attribute is empty/missing. + */ + attribute AString contentDispositionFilename; + + /** + * Access to the raw Content-Disposition header if available and applicable. + * + * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either + * doesn't exist for this type of channel or is empty. + * + * @deprecated Use contentDisposition/contentDispositionFilename instead. + */ + readonly attribute ACString contentDispositionHeader; + + /** + * The LoadInfo object contains information about a network load, why it + * was started, and how we plan on using the resulting response. + * If a network request is redirected, the new channel will receive a new + * LoadInfo object. The new object will contain mostly the same + * information as the pre-redirect one, but updated as appropriate. + * For detailed information about what parts of LoadInfo are updated on + * redirect, see documentation on individual properties. + */ + attribute nsILoadInfo loadInfo; + + /** + * Returns true if the channel is used to create a document. + * It returns true if the loadFlags have LOAD_DOCUMENT_URI set, or if + * LOAD_HTML_OBJECT_DATA is set and the channel has the appropriate + * MIME type. + * Note: May have the wrong value if called before OnStartRequest as we + * don't know the MIME type yet. + */ + readonly attribute bool isDocument; + +%{ C++ + inline bool IsDocument() + { + bool isDocument = false; + if (NS_SUCCEEDED(GetIsDocument(&isDocument)) && isDocument) { + return true; + } + return false; + } + + inline already_AddRefed<nsILoadInfo> LoadInfo() + { + nsCOMPtr<nsILoadInfo> result; + mozilla::DebugOnly<nsresult> rv = GetLoadInfo(getter_AddRefs(result)); + MOZ_ASSERT(NS_SUCCEEDED(rv) && result); + return result.forget(); + } +%} + +}; + +[builtinclass, scriptable, uuid(1ebbff64-d742-4f4a-aad5-4df2d1eb937a)] +interface nsIIdentChannel : nsIChannel +{ + /** + * Unique ID of the channel, shared between parent and child. Needed if + * the channel activity needs to be monitored across process boundaries, + * like in devtools net monitor. See bug 1274556. + */ + [must_use] attribute uint64_t channelId; + +%{ C++ + inline uint64_t ChannelId() + { + uint64_t value = 0; + if (NS_SUCCEEDED(GetChannelId(&value))) { + return value; + } + return 0; + } +%} +}; diff --git a/netwerk/base/nsIChannelEventSink.idl b/netwerk/base/nsIChannelEventSink.idl new file mode 100644 index 0000000000..56484e933f --- /dev/null +++ b/netwerk/base/nsIChannelEventSink.idl @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIChannel; +interface nsIAsyncVerifyRedirectCallback; + +/** + * Implement this interface to receive control over various channel events. + * Channels will try to get this interface from a channel's + * notificationCallbacks or, if not available there, from the loadGroup's + * notificationCallbacks. + * + * These methods are called before onStartRequest. + */ +[scriptable, uuid(0197720d-37ed-4e75-8956-d0d296e4d8a6)] +interface nsIChannelEventSink : nsISupports +{ + /** + * This is a temporary redirect. New requests for this resource should + * continue to use the URI of the old channel. + * + * The new URI may be identical to the old one. + */ + const unsigned long REDIRECT_TEMPORARY = 1 << 0; + + /** + * This is a permanent redirect. New requests for this resource should use + * the URI of the new channel (This might be an HTTP 301 reponse). + * If this flag is not set, this is a temporary redirect. + * + * The new URI may be identical to the old one. + */ + const unsigned long REDIRECT_PERMANENT = 1 << 1; + + /** + * This is an internal redirect, i.e. it was not initiated by the remote + * server, but is specific to the channel implementation. + * + * The new URI may be identical to the old one. + */ + const unsigned long REDIRECT_INTERNAL = 1 << 2; + + /** + * This is a special-cased redirect coming from hitting HSTS upgrade + * redirect from http to https only. In some cases this type of redirect + * may be considered as safe despite not being the-same-origin redirect. + */ + const unsigned long REDIRECT_STS_UPGRADE = 1 << 3; + + /** + * This is a internal redirect used to handle http authentication retries. + * Upon receiving a 401 or 407 the channel gets redirected to a new channel + * (same URL) that performs the request with the appropriate credentials. + * Auth retry to the server must be made after redirecting to a new channel + */ + const unsigned long REDIRECT_AUTH_RETRY = 1 << 4; + + /** + * Called when a redirect occurs. This may happen due to an HTTP 3xx status + * code. The purpose of this method is to notify the sink that a redirect + * is about to happen, but also to give the sink the right to veto the + * redirect by throwing or passing a failure-code in the callback. + * + * Note that vetoing the redirect simply means that |newChannel| will not + * be opened. It is important to understand that |oldChannel| will continue + * loading as if it received a HTTP 200, which includes notifying observers + * and possibly display or process content attached to the HTTP response. + * If the sink wants to prevent this loading it must explicitly deal with + * it, e.g. by calling |oldChannel->Cancel()| + * + * There is a certain freedom in implementing this method: + * + * If the return-value indicates success, a callback on |callback| is + * required. This callback can be done from within asyncOnChannelRedirect + * (effectively making the call synchronous) or at some point later + * (making the call asynchronous). Repeat: A callback must be done + * if this method returns successfully. + * + * If the return value indicates error (method throws an exception) + * the redirect is vetoed and no callback must be done. Repeat: No + * callback must be done if this method throws! + * + * NOTE: originalURI isn't yet set on the new channel when + * asyncOnChannelRedirect is called. + * + * @see nsIAsyncVerifyRedirectCallback::onRedirectVerifyCallback() + * + * @param oldChannel + * The channel that's being redirected. + * @param newChannel + * The new channel. This channel is not opened yet. + * @param flags + * Flags indicating the type of redirect. A bitmask consisting + * of flags from above. + * One of REDIRECT_TEMPORARY and REDIRECT_PERMANENT will always be + * set. + * @param callback + * Object to inform about the async result of this method + * + * @throw <any> Throwing an exception will cause the redirect to be + * cancelled + */ + void asyncOnChannelRedirect(in nsIChannel oldChannel, + in nsIChannel newChannel, + in unsigned long flags, + in nsIAsyncVerifyRedirectCallback callback); +}; diff --git a/netwerk/base/nsIChildChannel.idl b/netwerk/base/nsIChildChannel.idl new file mode 100644 index 0000000000..3b57013536 --- /dev/null +++ b/netwerk/base/nsIChildChannel.idl @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIStreamListener; + +/** + * Implemented by content side of IPC protocols. + */ + +[scriptable, uuid(c45b92ae-4f07-41dd-b0ef-aa044eeabb1e)] +interface nsIChildChannel : nsISupports +{ + /** + * Create the chrome side of the IPC protocol and join an existing 'real' + * channel on the parent process. The id is provided by + * nsIRedirectChannelRegistrar on the chrome process and pushed to the child + * protocol as an argument to event starting a redirect. + * + * Primarilly used in HttpChannelChild::Redirect1Begin on a newly created + * child channel, where the new channel is intended to be created on the + * child process. + */ + void connectParent(in uint32_t registrarId); + + /** + * As AsyncOpen is called on the chrome process for redirect target channels, + * we have to inform the child side of the protocol of that fact by a special + * method. + */ + void completeRedirectSetup(in nsIStreamListener aListener); +}; diff --git a/netwerk/base/nsIClassOfService.idl b/netwerk/base/nsIClassOfService.idl new file mode 100644 index 0000000000..5426eabbcc --- /dev/null +++ b/netwerk/base/nsIClassOfService.idl @@ -0,0 +1,103 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIClassOfService.idl + * + * Used to express class dependencies and characteristics - complimentary to + * nsISupportsPriority which is used to express weight + * + * Channels that implement this interface may make use of this + * information in different ways. + */ + +// convenience class for passing around the class of service +%{C++ +namespace mozilla::net { +class ClassOfService; +} +%} +native ClassOfService(mozilla::net::ClassOfService); + +[scriptable, uuid(1ccb58ec-5e07-4cf9-a30d-ac5490d23b41)] +interface nsIClassOfService : nsISupports +{ + attribute unsigned long classFlags; + attribute bool incremental; + + void clearClassFlags(in unsigned long flags); + void addClassFlags(in unsigned long flags); + void setClassOfService(in ClassOfService s); + + // All these flags have a (de)prioritization effect. + + // In the HTTP/1 world the priority is considered for all requests inside a so + // called 'Request Context' which is a context common to all sub-resources + // belonging to a single top level window (RequestContextService). Requests + // marked with the Leader flag are blocking (preventing from being sent to the + // server) all other resource loads except those marked with the Unblocked + // flag. Other classes run in parallel - neither being blocked no ;r blocking. + // The Leader flag is used only for <head> blocking resources (sync and + // defer javascript resources and stylesheets.) Purpose is to deliver these + // first-paint and domcontentloaded blocking resources as soon as possbile. + + // In the HTTP/2 world it's different. Priorities are done only per HTTP/2 + // session, normally we have one session per one origin (including origin + // attributes!) Requests are dispatched (sent) immediately on an HTTP/2 + // session. Each session has artificial streams (groups) relating to the class + // of service flags (Leader, Other, Background, Speculative, Follower, + // UrgentStart), each such a stream is given a different weight (only way to + // give a stream a priority in HTTP/2) reflecting the desired request group + // priority. Actual request streams are then dependent on these artificial + // streams (groups). nsISupportsPriority of each request is passed as a weight + // on the HTTP/2 stream to prioritize streams in the same group. A stream can + // also be dependent on other stream. We have dependency of Followers on + // Leaders, hence everything set the Follower flag should be processed by the + // server after Leaders. Same for Speculative being dependent on Background. The + // tree is created and used here: + // https://searchfox.org/mozilla-central/rev/cc280c4be94ff8cf64a27cc9b3d6831ffa49fa45/netwerk/protocol/http/Http2Session.cpp#1053-1070 + // https://searchfox.org/mozilla-central/rev/cc280c4be94ff8cf64a27cc9b3d6831ffa49fa45/netwerk/protocol/http/Http2Stream.cpp#1338 + // For detailed description of how HTTP/2 server should handle the priorities + // and dependencies see: + // https://developers.google.com/web/fundamentals/performance/http2/#stream_prioritization + // Please note that the dependecies and weights we are sending to the server + // are only suggestions, the server may completely ignore it. + + // Leaders (should) block all other resources except Unblocked. This flag + // also priortizes HTTP cache reading queue by blocking all other cache + // requests. + const unsigned long Leader = 1 << 0; + // The Follower flag is currently unused! + const unsigned long Follower = 1 << 1; + // The Speculative flag is currently unused! + const unsigned long Speculative = 1 << 2; + // The Background flag is currently only used for Beacon. + const unsigned long Background = 1 << 3; + // Requests marked with this flag are not blocked by Leaders. This is mainly + // used for probing-like XMLHttpRequests that may block delivery of head + // blocking resources, e.g. CSS files tailored for the UA. + const unsigned long Unblocked = 1 << 4; + // Throttleable flag allows response throttling of the resource load. Note + // that this functionality is currently disabled. + const unsigned long Throttleable = 1 << 5; + // UrgentStart makes the request temporarily extend HTTP/1 connection + // parallelism limits. Used mainly for navigational requests (top level html) + // and any request considered coming from a user interaction to make reaction + // of the browser as fast as possible and not blocked. + const unsigned long UrgentStart = 1 << 6; + // Specifically disables throttling under any circumstances, used for media + // responses mainly. + const unsigned long DontThrottle = 1 << 7; + // Enforce tailing on this load; any of Leader, Unblocked, UrgentStart, + // TailForbidden overrule this flag (disable tailing.) + const unsigned long Tail = 1 << 8; + // Tailing may be engaged regardless if the load is marked Unblocked when some + // other conditions are met later, like when the load is found to be a + // tracker. + const unsigned long TailAllowed = 1 << 9; + // Tailing not allowed under any circumstances or combination of flags. + const unsigned long TailForbidden = 1 << 10; +}; diff --git a/netwerk/base/nsIClassifiedChannel.idl b/netwerk/base/nsIClassifiedChannel.idl new file mode 100644 index 0000000000..482c524cbf --- /dev/null +++ b/netwerk/base/nsIClassifiedChannel.idl @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIClassifiedChannel + * + * A channel may optionally implement this interface if it carries classified + * result information of channel classifier. The information contains, for + * example, the name of matched table and the name of matched provider. + */ +[builtinclass, scriptable, uuid(70cf6091-a1de-4aa8-8224-058f8964be31)] +interface nsIClassifiedChannel : nsISupports +{ + /** + * Sets matched info of the classified channel. + * + * @param aList + * Name of the Safe Browsing list that matched (e.g. goog-phish-shavar). + * @param aProvider + * Name of the Safe Browsing provider that matched (e.g. google) + * @param aFullHash + * Full hash of URL that matched Safe Browsing list. + */ + void setMatchedInfo(in ACString aList, + in ACString aProvider, + in ACString aFullHash); + + /** + * Name of the list that matched + */ + readonly attribute ACString matchedList; + + /** + * Name of provider that matched + */ + readonly attribute ACString matchedProvider; + + /** + * Full hash of URL that matched + */ + readonly attribute ACString matchedFullHash; + + /** + * Sets matched tracking info of the classified channel. + * + * @param aLists + * Name of the Tracking Protection list that matched (e.g. content-track-digest256). + * @param aFullHash + * Full hash of URLs that matched Tracking Protection list. + */ + void setMatchedTrackingInfo(in Array<ACString> aLists, + in Array<ACString> aFullHashes); + + /** + * Name of the lists that matched + */ + readonly attribute Array<ACString> matchedTrackingLists; + + /** + * Full hash of URLs that matched + */ + readonly attribute Array<ACString> matchedTrackingFullHashes; + + /** + * Returns the classification flags if the channel has been processed by + * URL-Classifier features and is considered first-party. + */ + [infallible] readonly attribute unsigned long firstPartyClassificationFlags; + + /** + * Returns the classification flags if the channel has been processed by + * URL-Classifier features and is considered third-party with the top + * window URI. + */ + [infallible] readonly attribute unsigned long thirdPartyClassificationFlags; + + /* + * Returns the classification flags if the channel has been processed by + * URL-Classifier features. This value is equal to + * "firstPartyClassificationFlags || thirdPartyClassificationFlags". + * + * Note that top-level channels could be classified as well. + * In order to identify third-party resources specifically, use + * classificationThirdPartyFlags; + */ + [infallible] readonly attribute unsigned long classificationFlags; + + cenum ClassificationFlags : 32 { + /** + * The resource is on the fingerprinting list. + */ + CLASSIFIED_FINGERPRINTING = 0x0001, + CLASSIFIED_FINGERPRINTING_CONTENT = 0x0080, + + /** + * The resource is on the cryptomining list. + */ + CLASSIFIED_CRYPTOMINING = 0x0002, + CLASSIFIED_CRYPTOMINING_CONTENT = 0x0100, + + /** + * The following are about tracking annotation and are available only + * if the privacy.trackingprotection.annotate_channels pref. + * CLASSIFIED_TRACKING is set if we are not able to identify the + * type of classification. + */ + CLASSIFIED_TRACKING = 0x0004, + CLASSIFIED_TRACKING_AD = 0x0008, + CLASSIFIED_TRACKING_ANALYTICS = 0x0010, + CLASSIFIED_TRACKING_SOCIAL = 0x0020, + CLASSIFIED_TRACKING_CONTENT = 0x0040, + + /** + * The following are about social tracking. + */ + CLASSIFIED_SOCIALTRACKING = 0x0200, + CLASSIFIED_SOCIALTRACKING_FACEBOOK = 0x0400, + CLASSIFIED_SOCIALTRACKING_LINKEDIN = 0x0800, + CLASSIFIED_SOCIALTRACKING_TWITTER = 0x1000, + + /** + * The following are about email tracking. + */ + CLASSIFIED_EMAILTRACKING = 0x2000, + CLASSIFIED_EMAILTRACKING_CONTENT = 0x4000, + + /** + * This is exposed to help to identify tracking classification using the + * basic lists. + */ + CLASSIFIED_ANY_BASIC_TRACKING = CLASSIFIED_TRACKING | + CLASSIFIED_TRACKING_AD | CLASSIFIED_TRACKING_ANALYTICS | + CLASSIFIED_TRACKING_SOCIAL | CLASSIFIED_FINGERPRINTING, + + /** + * This is exposed to help to identify tracking classification using the + * strict lists. + */ + CLASSIFIED_ANY_STRICT_TRACKING = CLASSIFIED_ANY_BASIC_TRACKING | + CLASSIFIED_TRACKING_CONTENT | CLASSIFIED_FINGERPRINTING_CONTENT, + + /** + * This is exposed to help to identify social tracking classification + * flags. + */ + CLASSIFIED_ANY_SOCIAL_TRACKING = CLASSIFIED_SOCIALTRACKING | + CLASSIFIED_SOCIALTRACKING_FACEBOOK | + CLASSIFIED_SOCIALTRACKING_LINKEDIN | CLASSIFIED_SOCIALTRACKING_TWITTER, + }; + + /** + * Returns true if the channel has been processed by URL-Classifier features + * and is considered third-party with the top window URI, and if it has loaded + * a resource that is classified as a tracker. + * + * This is a helper attribute which returns the same value of + * (thirdPartyClassificationFlags & CLASSIFIED_ANY_BASIC_TRACKING) or + * (thirdPartyClassificationFlags & CLASSIFIED_ANY_STRICT_TRACKING) or + * (thirdPartyClassificationFlags & CLASSIFIED_ANY_SOCIAL_TRACKING) + */ + boolean isThirdPartyTrackingResource(); + +%{ C++ + inline bool IsThirdPartyTrackingResource() + { + bool value = false; + if (NS_SUCCEEDED(IsThirdPartyTrackingResource(&value)) && value) { + return true; + } + return false; + } +%} + + /** + * Returns true if the channel has loaded a 3rd party resource that is + * classified as a social tracker. + * + * This is a helper attribute which returns the same value of + * (classificationFlags & CLASSIFIED_ANY_SOCIAL_TRACKING) + * + * Note that top-level channels could be marked as tracking + * resources. In order to identify third-party social tracking resources + * specifically, check the flags manually or add a new helper here. + */ + boolean isThirdPartySocialTrackingResource(); + +%{ C++ + inline bool IsThirdPartySocialTrackingResource() + { + bool value = false; + if (NS_SUCCEEDED(IsThirdPartySocialTrackingResource(&value)) && value) { + return true; + } + return false; + } +%} +}; diff --git a/netwerk/base/nsIContentSniffer.idl b/netwerk/base/nsIContentSniffer.idl new file mode 100644 index 0000000000..f9052b8e60 --- /dev/null +++ b/netwerk/base/nsIContentSniffer.idl @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIRequest; + +/** + * Content sniffer interface. Components implementing this interface can + * determine a MIME type from a chunk of bytes. + */ +[scriptable, uuid(a5772d1b-fc63-495e-a169-96e8d3311af0)] +interface nsIContentSniffer : nsISupports +{ + /** + * Given a chunk of data, determines a MIME type. Information from the given + * request may be used in order to make a better decision. + * + * @param aRequest The request where this data came from. May be null. + * @param aData Data to check + * @param aLength Length of the data + * + * @return The content type + * + * @throw NS_ERROR_NOT_AVAILABLE if no MIME type could be determined. + * + * @note Implementations should consider the request read-only. Especially, + * they should not attempt to set the content type property that subclasses of + * nsIRequest might offer. + */ + ACString getMIMETypeFromContent(in nsIRequest aRequest, + [const,array,size_is(aLength)] in octet aData, + in unsigned long aLength); +}; diff --git a/netwerk/base/nsIDHCPClient.idl b/netwerk/base/nsIDHCPClient.idl new file mode 100644 index 0000000000..1d986ebd52 --- /dev/null +++ b/netwerk/base/nsIDHCPClient.idl @@ -0,0 +1,19 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface allows the proxy code to access the DHCP Options in a platform-specific way + */ +[scriptable, uuid(aee75dc0-be1a-46b9-9e0c-31a6899c175c)] +interface nsIDHCPClient : nsISupports +{ + + /** + * returns the DHCP Option designated by the option parameter + */ + ACString getOption(in uint8_t option); +}; diff --git a/netwerk/base/nsIDashboard.idl b/netwerk/base/nsIDashboard.idl new file mode 100644 index 0000000000..ae80a9e458 --- /dev/null +++ b/netwerk/base/nsIDashboard.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/* A JavaScript callback function that takes a JSON as its parameter. + * The returned JSON contains arrays with data + */ +[scriptable, function, uuid(19d7f24f-a95a-4fd9-87e2-d96e9e4b1f6d)] +interface nsINetDashboardCallback : nsISupports +{ + void onDashboardDataAvailable(in jsval data); +}; + +/* The dashboard service. + * The async API returns JSONs, which hold arrays with the required info. + * Only one request of each type may be pending at any time. + */ +[scriptable, uuid(c79eb3c6-091a-45a6-8544-5a8d1ab79537)] +interface nsIDashboard : nsISupports +{ + /* Arrays: host, port, tcp, active, socksent, sockreceived + * Values: sent, received */ + void requestSockets(in nsINetDashboardCallback cb); + + /* Arrays: host, port, spdy, ssl + * Array of arrays: active, idle */ + void requestHttpConnections(in nsINetDashboardCallback cb); + + /* Arrays: hostport, encrypted, msgsent, msgreceived, sentsize, receivedsize */ + void requestWebsocketConnections(in nsINetDashboardCallback cb); + + /* Arrays: hostname, family, hostaddr, expiration */ + void requestDNSInfo(in nsINetDashboardCallback cb); + + /* aProtocol: a transport layer protocol: + * ex: "ssl", "tcp", default is "tcp". + * aHost: the host's name + * aPort: the port which the connection will open on + * aTimeout: the timespan before the connection will be timed out */ + void requestConnection(in ACString aHost, in unsigned long aPort, + in string aProtocol, in unsigned long aTimeout, + in nsINetDashboardCallback cb); + + /* When true, the service will log websocket events */ + attribute boolean enableLogging; + + /* DNS resolver for host name + * aHost: host name */ + void requestDNSLookup(in ACString aHost, in nsINetDashboardCallback cb); + + /* Resolve HTTPS RRs for host name + * aHost: host name */ + void requestDNSHTTPSRRLookup(in ACString aHost, + in nsINetDashboardCallback cb); + + /** + * Asyncly returns stats regarding the "Race Cache With Network" feature. + */ + void requestRcwnStats(in nsINetDashboardCallback cb); + + AUTF8String getLogPath(); +}; diff --git a/netwerk/base/nsIDashboardEventNotifier.idl b/netwerk/base/nsIDashboardEventNotifier.idl new file mode 100644 index 0000000000..933caf4371 --- /dev/null +++ b/netwerk/base/nsIDashboardEventNotifier.idl @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[uuid(24fdfcbe-54cb-4997-8392-3c476126ea3b)] +interface nsIDashboardEventNotifier : nsISupports +{ + /* These methods are called to register a websocket event with the dashboard + * + * A host is identified by the (aHost, aSerial) pair. + * aHost: the host's name: example.com + * aSerial: a number that uniquely identifies the websocket + * + * aEncrypted: if the connection is encrypted + * aLength: the length of the message in bytes + */ + void addHost(in ACString aHost, in unsigned long aSerial, in boolean aEncrypted); + void removeHost(in ACString aHost, in unsigned long aSerial); + void newMsgSent(in ACString aHost, in unsigned long aSerial, in unsigned long aLength); + void newMsgReceived(in ACString aHost, in unsigned long aSerial, in unsigned long aLength); +}; diff --git a/netwerk/base/nsIDownloader.idl b/netwerk/base/nsIDownloader.idl new file mode 100644 index 0000000000..c887c32c74 --- /dev/null +++ b/netwerk/base/nsIDownloader.idl @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStreamListener.idl" + +interface nsIFile; +interface nsIDownloadObserver; + +/** + * nsIDownloader + * + * A downloader is a special implementation of a nsIStreamListener that will + * make the contents of the stream available as a file. This may utilize the + * disk cache as an optimization to avoid an extra copy of the data on disk. + * The resulting file is valid from the time the downloader completes until + * the last reference to the downloader is released. + */ +[scriptable, uuid(fafe41a9-a531-4d6d-89bc-588a6522fb4e)] +interface nsIDownloader : nsIStreamListener +{ + /** + * Initialize this downloader + * + * @param observer + * the observer to be notified when the download completes. + * @param downloadLocation + * the location where the stream contents should be written. + * if null, the downloader will select a location and the + * resulting file will be deleted (or otherwise made invalid) + * when the downloader object is destroyed. if an explicit + * download location is specified then the resulting file will + * not be deleted, and it will be the callers responsibility + * to keep track of the file, etc. + */ + void init(in nsIDownloadObserver observer, + in nsIFile downloadLocation); +}; + +[scriptable, uuid(44b3153e-a54e-4077-a527-b0325e40924e)] +interface nsIDownloadObserver : nsISupports +{ + /** + * Called to signal a download that has completed. + */ + void onDownloadComplete(in nsIDownloader downloader, + in nsIRequest request, + in nsresult status, + in nsIFile result); +}; diff --git a/netwerk/base/nsIEncodedChannel.idl b/netwerk/base/nsIEncodedChannel.idl new file mode 100644 index 0000000000..e1db3993c7 --- /dev/null +++ b/netwerk/base/nsIEncodedChannel.idl @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIUTF8StringEnumerator; +interface nsIStreamListener; +interface nsISupports; + +/** + * A channel interface which allows special handling of encoded content + */ + +[scriptable, uuid(29c29ce6-8ce4-45e6-8d60-36c8fa3e255b)] +interface nsIEncodedChannel : nsISupports +{ + /** + * This attribute holds the MIME types corresponding to the content + * encodings on the channel. The enumerator returns nsISupportsCString + * objects. The first one corresponds to the outermost encoding on the + * channel and then we work our way inward. "identity" is skipped and not + * represented on the list. Unknown encodings make the enumeration stop. + * If you want the actual Content-Encoding value, use + * getResponseHeader("Content-Encoding"). + * + * When there is no Content-Encoding header, this property is null. + * + * Modifying the Content-Encoding header on the channel will cause + * this enumerator to have undefined behavior. Don't do it. + * + * Also note that contentEncodings only exist during or after OnStartRequest. + * Calling contentEncodings before OnStartRequest is an error. + */ + readonly attribute nsIUTF8StringEnumerator contentEncodings; + + /** + * This attribute controls whether or not content conversion should be + * done per the Content-Encoding response header. applyConversion can only + * be set before or during OnStartRequest. Calling this during + * OnDataAvailable is an error. + * + * TRUE by default. + */ + attribute boolean applyConversion; + + /** + * This attribute indicates the content has been decompressed in + * the parent process. + */ + attribute boolean hasContentDecompressed; + /** + * This function will start converters if they are available. + * aNewNextListener will be nullptr if no converter is available. + */ + void doApplyContentConversions(in nsIStreamListener aNextListener, + out nsIStreamListener aNewNextListener, + in nsISupports aCtxt); +}; diff --git a/netwerk/base/nsIExternalProtocolHandler.idl b/netwerk/base/nsIExternalProtocolHandler.idl new file mode 100644 index 0000000000..6f86dbb05a --- /dev/null +++ b/netwerk/base/nsIExternalProtocolHandler.idl @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIProtocolHandler.idl" + +[scriptable, uuid(0e61f3b2-34d7-4c79-bfdc-4860bc7341b7)] +interface nsIExternalProtocolHandler: nsIProtocolHandler +{ + /** + * This method checks if the external handler exists for a given scheme. + * + * @param scheme external scheme. + * @return TRUE if the external handler exists for the input scheme, FALSE otherwise. + */ + boolean externalAppExistsForScheme(in ACString scheme); +}; diff --git a/netwerk/base/nsIFileStreams.idl b/netwerk/base/nsIFileStreams.idl new file mode 100644 index 0000000000..026fa41bcf --- /dev/null +++ b/netwerk/base/nsIFileStreams.idl @@ -0,0 +1,239 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" +#include "nsIOutputStream.idl" +#include "nsIRandomAccessStream.idl" + +interface nsIEventTarget; +interface nsIFile; +interface nsIFileMetadataCallback; + +%{C++ +struct PRFileDesc; +%} + +[ptr] native PRFileDescPtr(PRFileDesc); + +/** + * An input stream that allows you to read from a file. + */ +[scriptable, builtinclass, uuid(e3d56a20-c7ec-11d3-8cda-0060b0fc14a3)] +interface nsIFileInputStream : nsIInputStream +{ + /** + * @param file file to read from + * @param ioFlags file open flags listed in prio.h (see + * PR_Open documentation) or -1 to open the + * file in default mode (PR_RDONLY). + * @param perm file mode bits listed in prio.h or -1 to + * use the default value (0) + * @param behaviorFlags flags specifying various behaviors of the class + * (see enumerations in the class) + */ + void init(in nsIFile file, in long ioFlags, in long perm, + in long behaviorFlags); + + /** + * If this is set, the file will close automatically when the end of the + * file is reached. + */ + const long CLOSE_ON_EOF = 1<<2; + + /** + * If this is set, the file will be reopened whenever we reach the start of + * the file, either by doing a Seek(0, NS_SEEK_CUR), or by doing a relative + * seek that happen to reach the beginning of the file. If the file is + * already open and the seek occurs, it will happen naturally. (The file + * will only be reopened if it is closed for some reason.) + */ + const long REOPEN_ON_REWIND = 1<<3; + + /** + * If this is set, the file will be opened (i.e., a call to + * PR_Open done) only when we do an actual operation on the stream, + * or more specifically, when one of the following is called: + * - Seek + * - Tell + * - SetEOF + * - Available + * - Read + * - ReadLine + * + * DEFER_OPEN is useful if we use the stream on a background + * thread, so that the opening and possible |stat|ing of the file + * happens there as well. + * + * @note Using this flag results in the file not being opened + * during the call to Init. This means that any errors that might + * happen when this flag is not set would happen during the + * first read. Also, the file is not locked when Init is called, + * so it might be deleted before we try to read from it. + */ + const long DEFER_OPEN = 1<<4; + + /** + * This flag has no effect and is totally ignored on any platform except + * Windows since this is the default behavior on POSIX systems. On Windows + * if this flag is set then the stream is opened in a special mode that + * allows the OS to delete the file from disk just like POSIX. + */ + const long SHARE_DELETE = 1<<5; +}; + +/** + * An output stream that lets you stream to a file. + */ +[scriptable, builtinclass, uuid(e734cac9-1295-4e6f-9684-3ac4e1f91063)] +interface nsIFileOutputStream : nsIOutputStream +{ + /** + * @param file file to write to + * @param ioFlags file open flags listed in prio.h (see + * PR_Open documentation) or -1 to open the + * file in default mode (PR_WRONLY | + * PR_CREATE_FILE | PR_TRUNCATE) + * @param perm file mode bits listed in prio.h or -1 to + * use the default permissions (0664) + * @param behaviorFlags flags specifying various behaviors of the class + * (currently none supported) + */ + void init(in nsIFile file, in long ioFlags, in long perm, + in long behaviorFlags); + + /** + * @param length asks the operating system to allocate storage for + * this file of at least |length| bytes long, and + * set the file length to the corresponding size. + * @throws NS_ERROR_FAILURE if the preallocation fails. + * @throws NS_ERROR_NOT_INITIALIZED if the file is not opened. + */ + [noscript] void preallocate(in long long length); + + /** + * See the same constant in nsIFileInputStream. The deferred open will + * be performed when one of the following is called: + * - Seek + * - Tell + * - SetEOF + * - Write + * - Flush + * + * @note Using this flag results in the file not being opened + * during the call to Init. This means that any errors that might + * happen when this flag is not set would happen during the + * first write, and if the file is to be created, then it will not + * appear on the disk until the first write. + */ + const long DEFER_OPEN = 1<<0; +}; + +/** + * A stream that allows you to read from a file or stream to a file. + */ +[scriptable, builtinclass, uuid(82cf605a-8393-4550-83ab-43cd5578e006)] +interface nsIFileRandomAccessStream : nsIRandomAccessStream +{ + /** + * @param file file to read from or stream to + * @param ioFlags file open flags listed in prio.h (see + * PR_Open documentation) or -1 to open the + * file in default mode (PR_RDWR). + * @param perm file mode bits listed in prio.h or -1 to + * use the default value (0) + * @param behaviorFlags flags specifying various behaviors of the class + * (see enumerations in the class) + */ + void init(in nsIFile file, in long ioFlags, in long perm, + in long behaviorFlags); + + /** + * See the same constant in nsIFileInputStream. The deferred open will + * be performed when one of the following is called: + * - Seek + * - Tell + * - SetEOF + * - Available + * - Read + * - Flush + * - Write + * - GetSize + * - GetLastModified + * + * @note Using this flag results in the file not being opened + * during the call to Init. This means that any errors that might + * happen when this flag is not set would happen during the + * first read or write. The file is not locked when Init is called, + * so it might be deleted before we try to read from it and if the + * file is to be created, then it will not appear on the disk until + * the first write. + */ + const long DEFER_OPEN = 1<<0; +}; + +/** + * An interface that allows you to get some metadata like file size and + * file last modified time. These methods and attributes can throw + * NS_BASE_STREAM_WOULD_BLOCK in case the informations are not available yet. + * If this happens, consider the use of nsIAsyncFileMetadata. + * + * If using nsIAsyncFileMetadata, you should retrieve any data via this + * interface before taking any action that might consume the underlying stream. + * For example, once Available(), Read(), or nsIAsyncInputStream::AsyncWait() + * are invoked, these methods may return NS_BASE_STREAM_CLOSED. This will + * happen when using RemoteLazyInputStream with an underlying file stream, for + * example. + */ +[scriptable, builtinclass, uuid(07f679e4-9601-4bd1-b510-cd3852edb881)] +interface nsIFileMetadata : nsISupports +{ + /** + * File size in bytes. + */ + readonly attribute long long size; + + /** + * File last modified time in milliseconds from midnight (00:00:00), + * January 1, 1970 Greenwich Mean Time (GMT). + */ + readonly attribute long long lastModified; + + /** + * The internal file descriptor. It can be used for memory mapping of the + * underlying file. Please use carefully! If this returns + * NS_BASE_STREAM_WOULD_BLOCK, consider the use of nsIAsyncFileMetadata. + */ + [noscript] PRFileDescPtr getFileDescriptor(); +}; + +[scriptable, builtinclass, uuid(de15b80b-29ba-4b7f-9220-a3d75b17ae8c)] +interface nsIAsyncFileMetadata : nsIFileMetadata +{ + /** + * Asynchronously wait for the object to be ready. + * + * @param aCallback The callback will be used when the stream is ready to + * return File metadata. Use a nullptr to cancel a + * previous operation. + * + * @param aEventTarget The event target where aCallback will be executed. + * If aCallback is passed, aEventTarget cannot be null. + */ + void asyncFileMetadataWait(in nsIFileMetadataCallback aCallback, + in nsIEventTarget aEventTarget); +}; + +/** + * This is a companion interface for + * nsIAsyncFileMetadata::asyncFileMetadataWait. + */ +[function, scriptable, uuid(d01c7ead-7ba3-4726-b399-618ec8ec7057)] +interface nsIFileMetadataCallback : nsISupports +{ + /** + * Called to indicate that the nsIFileMetadata object is ready. + */ + void onFileMetadataReady(in nsIAsyncFileMetadata aObject); +}; diff --git a/netwerk/base/nsIFileURL.idl b/netwerk/base/nsIFileURL.idl new file mode 100644 index 0000000000..3c97a2e3af --- /dev/null +++ b/netwerk/base/nsIFileURL.idl @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIURL.idl" + +interface nsIFile; +interface nsIURIMutator; + +/** + * nsIFileURL provides access to the underlying nsIFile object corresponding to + * an URL. The URL scheme need not be file:, since other local protocols may + * map URLs to files (e.g., resource:). + */ +[scriptable, builtinclass, uuid(e91ac988-27c2-448b-b1a1-3822e1ef1987)] +interface nsIFileURL : nsIURL +{ + /** + * Get the nsIFile corresponding to this URL. + * + * - Returns a reference to an immutable object. Callers must clone + * before attempting to modify the returned nsIFile object. NOTE: this + * constraint might not be enforced at runtime, so beware!! + */ + readonly attribute nsIFile file; +}; + +[scriptable, builtinclass, uuid(a588b6f2-d2b9-4024-84c7-be3368546b57)] +interface nsIFileURLMutator : nsISupports +{ + /* + * - Marks the inner URI implementation as one that supports nsIFileURL. + */ + [must_use, noscript] void markFileURL(); + + /* + * - Setter clones the nsIFile object (allowing the caller to safely modify + * the nsIFile object after setting it on this interface). + */ + [must_use, noscript] void setFile(in nsIFile aFile); +}; diff --git a/netwerk/base/nsIForcePendingChannel.idl b/netwerk/base/nsIForcePendingChannel.idl new file mode 100644 index 0000000000..714c83f52d --- /dev/null +++ b/netwerk/base/nsIForcePendingChannel.idl @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIForcePending interface exposes a function that enables overwriting of the normal + * behavior for the channel's IsPending(), forcing 'true' to be returned. + */ + +[uuid(2ac3e1ca-049f-44c3-a519-f0681f51e9b1)] +interface nsIForcePendingChannel : nsISupports +{ + + /** + * forcePending(true) overrides the normal behavior for the + * channel's IsPending(), forcing 'true' to be returned. A call to + * forcePending(false) reverts IsPending() back to normal behavior. + */ + void forcePending(in boolean aForcePending); +}; diff --git a/netwerk/base/nsIFormPOSTActionChannel.idl b/netwerk/base/nsIFormPOSTActionChannel.idl new file mode 100644 index 0000000000..870886390c --- /dev/null +++ b/netwerk/base/nsIFormPOSTActionChannel.idl @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIUploadChannel.idl" + +/** + * nsIFormPOSTActionChannel + * + * Channel classes that want to be allowed for HTML form POST action must + * implement this interface. + */ +[scriptable, uuid(fc826b53-0db8-42b4-aa6a-5dd2cfca52a4)] +interface nsIFormPOSTActionChannel : nsIUploadChannel +{ +}; diff --git a/netwerk/base/nsIHttpAuthenticatorCallback.idl b/netwerk/base/nsIHttpAuthenticatorCallback.idl new file mode 100644 index 0000000000..74fd3c97ab --- /dev/null +++ b/netwerk/base/nsIHttpAuthenticatorCallback.idl @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[uuid(d989cb03-e446-4086-b9e6-46842cb97bd5)] +interface nsIHttpAuthenticatorCallback : nsISupports +{ + /** + * Authentication data for a header is available. + * + * @param aCreds + * Credentials which were obtained asynchonously. + * @param aFlags + * Flags set by asynchronous call. + * @param aResult + * Result status of credentials generation + * @param aSessionState + * Modified session state to be passed to caller + * @param aContinuationState + * Modified continuation state to be passed to caller + */ + void onCredsGenerated(in ACString aCreds, + in unsigned long aFlags, + in nsresult aResult, + in nsISupports aSessionsState, + in nsISupports aContinuationState); + +}; diff --git a/netwerk/base/nsIHttpPushListener.idl b/netwerk/base/nsIHttpPushListener.idl new file mode 100644 index 0000000000..f44605c000 --- /dev/null +++ b/netwerk/base/nsIHttpPushListener.idl @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIHttpChannel; + +/** + * nsIHttpPushListener + * + * Used for triggering when a HTTP/2 push is received. + * + */ +[scriptable, uuid(0d6ce59c-ad5d-4520-b4d3-09664868f279)] +interface nsIHttpPushListener : nsISupports +{ + /** + * When provided as a notificationCallback to an httpChannel, this.onPush() + * will be invoked when there is a >= Http2 push to that + * channel. The push may be in progress. + * + * The consumer must start the new channel in the usual way by calling + * pushChannel.AsyncOpen with a nsIStreamListener object that + * will receive the normal sequence of OnStartRequest(), + * 0 to N OnDataAvailable(), and onStopRequest(). + * + * The new channel can be canceled after the AsyncOpen if it is not wanted. + * + * @param associatedChannel + * the monitor channel that was recieved on + * @param pushChannel + * a channel to the resource which is being pushed + */ + void onPush(in nsIHttpChannel associatedChannel, + in nsIHttpChannel pushChannel); +}; diff --git a/netwerk/base/nsIIOService.idl b/netwerk/base/nsIIOService.idl new file mode 100644 index 0000000000..5bc3e41181 --- /dev/null +++ b/netwerk/base/nsIIOService.idl @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIContentPolicy.idl" + +interface nsIProtocolHandler; +interface nsIChannel; +interface nsIURI; +interface nsIFile; +interface nsIPrincipal; +interface nsILoadInfo; +interface nsIWebTransport; + +webidl Node; + +%{C++ +#include "mozilla/Maybe.h" + +namespace mozilla { +namespace dom { +class ClientInfo; +class ServiceWorkerDescriptor; +} // namespace dom +} // namespace mozilla +%} + +[ref] native const_MaybeClientInfoRef(const mozilla::Maybe<mozilla::dom::ClientInfo>); +[ref] native const_MaybeServiceWorkerDescriptorRef(const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>); + +/** + * nsIIOService provides a set of network utility functions. This interface + * duplicates many of the nsIProtocolHandler methods in a protocol handler + * independent way (e.g., NewURI inspects the scheme in order to delegate + * creation of the new URI to the appropriate protocol handler). nsIIOService + * also provides a set of URL parsing utility functions. These are provided + * as a convenience to the programmer and in some cases to improve performance + * by eliminating intermediate data structures and interfaces. + */ +[scriptable, builtinclass, uuid(4286de5a-b2ea-446f-8f70-e2a461f42694)] +interface nsIIOService : nsISupports +{ + /** + * Returns a protocol handler for a given URI scheme. + * + * @param aScheme the URI scheme + * @return reference to corresponding nsIProtocolHandler + */ + nsIProtocolHandler getProtocolHandler(in string aScheme); + + /** + * Returns the protocol flags for a given scheme. + * + * @param aScheme the URI scheme + * @return protocol flags for the corresponding protocol + */ + unsigned long getProtocolFlags(in string aScheme); + + /** + * Returns the dynamic protocol flags for a given URI. + * + * @param aURI the URI to get all dynamic flags for + * @return protocol flags for that URI + */ + unsigned long getDynamicProtocolFlags(in nsIURI aURI); + + /** + * Returns the default port for a given scheme. + * + * @param aScheme the URI scheme + * @return default port for the corresponding protocol + */ + long getDefaultPort(in string aScheme); + + /** + * This method constructs a new URI based on the scheme of the URI spec. + * QueryInterface can be used on the resulting URI object to obtain a more + * specific type of URI. + */ + nsIURI newURI(in AUTF8String aSpec, + [optional] in string aOriginCharset, + [optional] in nsIURI aBaseURI); + + /** + * This method constructs a new URI from a nsIFile. + * + * @param aFile specifies the file path + * @return reference to a new nsIURI object + * + * Note: in the future, for perf reasons we should allow + * callers to specify whether this is a file or directory by + * splitting this into newDirURI() and newActualFileURI(). + */ + nsIURI newFileURI(in nsIFile aFile); + + /** + * Converts an internal URI (e.g. one that has a username and password in + * it) into one which we can expose to the user, for example on the URL bar. + * + * @param aURI The URI to be converted. + * @return nsIURI The converted, exposable URI. + */ + nsIURI createExposableURI(in nsIURI aURI); + + /** + * Creates a channel for a given URI. + * + * @param aURI + * nsIURI from which to make a channel + * @param aLoadingNode + * @param aLoadingPrincipal + * @param aTriggeringPrincipal + * @param aSecurityFlags + * @param aContentPolicyType + * These will be used as values for the nsILoadInfo object on the + * created channel. For details, see nsILoadInfo in nsILoadInfo.idl + * @return reference to the new nsIChannel object + * + * Please note, if you provide both a loadingNode and a loadingPrincipal, + * then loadingPrincipal must be equal to loadingNode->NodePrincipal(). + * But less error prone is to just supply a loadingNode. + * + * Keep in mind that URIs coming from a webpage should *never* use the + * systemPrincipal as the loadingPrincipal. + */ + nsIChannel newChannelFromURI(in nsIURI aURI, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in nsContentPolicyType aContentPolicyType); + + [noscript, nostdcall, notxpcom] + nsresult NewChannelFromURIWithClientAndController(in nsIURI aURI, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in const_MaybeClientInfoRef aLoadingClientInfo, + in const_MaybeServiceWorkerDescriptorRef aController, + in unsigned long aSecurityFlags, + in nsContentPolicyType aContentPolicyType, + in unsigned long aSandboxFlags, + in boolean aSkipCheckForBrokenURLOrZeroSized, + out nsIChannel aResult); + + /** + * Equivalent to newChannelFromURI(aURI, aLoadingNode, ...) + */ + nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI, + in nsILoadInfo aLoadInfo); + + /** + * Equivalent to newChannelFromURI(newURI(...)) + */ + nsIChannel newChannel(in AUTF8String aSpec, + in string aOriginCharset, + in nsIURI aBaseURI, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in nsContentPolicyType aContentPolicyType); + + /** + * Creates a WebTransport. + */ + nsIWebTransport newWebTransport(); + + /** + * Returns true if networking is in "offline" mode. When in offline mode, + * attempts to access the network will fail (although this does not + * necessarily correlate with whether there is actually a network + * available -- that's hard to detect without causing the dialer to + * come up). + * + * Changing this fires observer notifications ... see below. + */ + attribute boolean offline; + + /** + * Returns false if there are no interfaces for a network request + */ + readonly attribute boolean connectivity; + + /** + * Checks if a port number is banned. This involves consulting a list of + * unsafe ports, corresponding to network services that may be easily + * exploitable. If the given port is considered unsafe, then the protocol + * handler (corresponding to aScheme) will be asked whether it wishes to + * override the IO service's decision to block the port. This gives the + * protocol handler ultimate control over its own security policy while + * ensuring reasonable, default protection. + * + * @see nsIProtocolHandler::allowPort + */ + boolean allowPort(in long aPort, in string aScheme); + + /** + * Utility to extract the scheme from a URL string, consistently and + * according to spec (see RFC 2396). + * + * NOTE: Most URL parsing is done via nsIURI, and in fact the scheme + * can also be extracted from a URL string via nsIURI. This method + * is provided purely as an optimization. + * + * @param aSpec the URL string to parse + * @return URL scheme, lowercase + * + * @throws NS_ERROR_MALFORMED_URI if URL string is not of the right form. + */ + ACString extractScheme(in AUTF8String urlString); + + /** + * Checks if a URI host is a local IPv4 or IPv6 address literal. + * + * @param nsIURI the URI that contains the hostname to check + * @return true if the URI hostname is a local IP address + */ + boolean hostnameIsLocalIPAddress(in nsIURI aURI); + + /** + * Checks if a URI host is a shared IPv4 address literal. + * + * @param nsIURI the URI that contains the hostname to check + * @return true if the URI hostname is a shared IP address + */ + boolean hostnameIsSharedIPAddress(in nsIURI aURI); + + /** + * Checks if a hostname is valid. + * + * @param AUTF8String hostname is the hostname to validate + * @return true if the hostname is valid, else false + */ + boolean isValidHostname(in AUTF8String hostname); + + /** + * While this is set, IOService will monitor an nsINetworkLinkService + * (if available) and set its offline status to "true" whenever + * isLinkUp is false. + * + * Applications that want to control changes to the IOService's offline + * status should set this to false, watch for network:link-status-changed + * broadcasts, and change nsIIOService::offline as they see fit. Note + * that this means during application startup, IOService may be offline + * if there is no link, until application code runs and can turn off + * this management. + */ + attribute boolean manageOfflineStatus; + + /** + * Creates a channel for a given URI. + * + * @param aURI + * nsIURI from which to make a channel + * @param aProxyURI + * nsIURI to use for proxy resolution. Can be null in which + * case aURI is used + * @param aProxyFlags flags from nsIProtocolProxyService to use + * when resolving proxies for this new channel + * @param aLoadingNode + * @param aLoadingPrincipal + * @param aTriggeringPrincipal + * @param aSecurityFlags + * @param aContentPolicyType + * These will be used as values for the nsILoadInfo object on the + * created channel. For details, see nsILoadInfo in nsILoadInfo.idl + * @return reference to the new nsIChannel object + * + * Please note, if you provide both a loadingNode and a loadingPrincipal, + * then loadingPrincipal must be equal to loadingNode->NodePrincipal(). + * But less error prone is to just supply a loadingNode. + */ + nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI, + in nsIURI aProxyURI, + in unsigned long aProxyFlags, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in nsContentPolicyType aContentPolicyType); + + /** + * Return true if socket process is launched. + */ + readonly attribute boolean socketProcessLaunched; + + /** + * The pid for socket process. + */ + readonly attribute unsigned long long socketProcessId; + + /** + * Register a protocol handler at runtime, given protocol flags and a + * default port. + * + * Statically registered protocol handlers cannot be overridden, and an + * error will be returned if that is attempted. + * + * Runtime registered protocol handlers are never QueryInterface-ed into + * `nsIProtocolHandlerWithDynamicFlags`, so that interface will be ignored. + * + * @param aScheme the scheme handled by the protocol handler. + * @param aHandler the protocol handler instance. + * @param aProtocolFlags protocol flags for this protocol, see + * nsIProtocolHandler for values. + * @param aDefaultPort default port for this scheme, or -1. + */ + void registerProtocolHandler(in ACString aScheme, + in nsIProtocolHandler aHandler, + in unsigned long aProtocolFlags, + in long aDefaultPort); + + /** + * Unregister a protocol handler which was previously registered using + * registerProtocolHandler. + * + * @param aScheme the scheme to unregister a handler for. + */ + void unregisterProtocolHandler(in ACString aScheme); +}; + +%{C++ +/** + * We send notifications through nsIObserverService with topic + * NS_IOSERVICE_GOING_OFFLINE_TOPIC and data NS_IOSERVICE_OFFLINE + * when 'offline' has changed from false to true, and we are about + * to shut down network services such as DNS. When those + * services have been shut down, we send a notification with + * topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data + * NS_IOSERVICE_OFFLINE. + * + * When 'offline' changes from true to false, then after + * network services have been restarted, we send a notification + * with topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data + * NS_IOSERVICE_ONLINE. + */ +#define NS_IOSERVICE_GOING_OFFLINE_TOPIC "network:offline-about-to-go-offline" +#define NS_IOSERVICE_OFFLINE_STATUS_TOPIC "network:offline-status-changed" +#define NS_IOSERVICE_OFFLINE "offline" +#define NS_IOSERVICE_ONLINE "online" + +%} + +[uuid(6633c0bf-d97a-428f-8ece-cb6a655fb95a)] +interface nsIIOServiceInternal : nsISupports +{ + /** + * This is an internal method that should only be called from ContentChild + * in order to pass the connectivity state from the chrome process to the + * content process. It throws if called outside the content process. + */ + void SetConnectivity(in boolean connectivity); + + /** + * An internal method to asynchronously run our notifications that happen + * when we wake from sleep + */ + void NotifyWakeup(); +}; diff --git a/netwerk/base/nsIIncrementalDownload.idl b/netwerk/base/nsIIncrementalDownload.idl new file mode 100644 index 0000000000..3ae363c488 --- /dev/null +++ b/netwerk/base/nsIIncrementalDownload.idl @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIURI; +interface nsIFile; +interface nsIRequestObserver; + +/** + * An incremental download object attempts to fetch a file piecemeal over time + * in an effort to minimize network bandwidth usage. + * + * Canceling a background download does not cause the file on disk to be + * deleted. + */ +[scriptable, uuid(6687823f-56c4-461d-93a1-7f6cb7dfbfba)] +interface nsIIncrementalDownload : nsIRequest +{ + /** + * Initialize the incremental download object. If the destination file + * already exists, then only the remaining portion of the file will be + * fetched. + * + * NOTE: The downloader will create the destination file if it does not + * already exist. It will create the file with the permissions 0600 if + * needed. To affect the permissions of the file, consumers of this + * interface may create an empty file at the specified destination prior to + * starting the incremental download. + * + * NOTE: Since this class may create a temporary file at the specified + * destination, it is advisable for the consumer of this interface to specify + * a file name for the destination that would not tempt the user into + * double-clicking it. For example, it might be wise to append a file + * extension like ".part" to the end of the destination to protect users from + * accidentally running "blah.exe" before it is a complete file. + * + * @param uri + * The URI to fetch. + * @param destination + * The location where the file is to be stored. + * @param chunkSize + * The size of the chunks to fetch. A non-positive value results in + * the default chunk size being used. + * @param intervalInSeconds + * The amount of time to wait between fetching chunks. Pass a + * negative to use the default interval, or 0 to fetch the remaining + * part of the file in one chunk. + */ + void init(in nsIURI uri, in nsIFile destination, in long chunkSize, + in long intervalInSeconds); + + /** + * The URI being fetched. + */ + readonly attribute nsIURI URI; + + /** + * The URI being fetched after any redirects have been followed. This + * attribute is set just prior to calling OnStartRequest on the observer + * passed to the start method. + */ + readonly attribute nsIURI finalURI; + + /** + * The file where the download is being written. + */ + readonly attribute nsIFile destination; + + /** + * The total number of bytes for the requested file. This attribute is set + * just prior to calling OnStartRequest on the observer passed to the start + * method. + * + * This attribute has a value of -1 if the total size is unknown. + */ + readonly attribute long long totalSize; + + /** + * The current number of bytes downloaded so far. This attribute is set just + * prior to calling OnStartRequest on the observer passed to the start + * method. + * + * This attribute has a value of -1 if the current size is unknown. + */ + readonly attribute long long currentSize; + + /** + * Start the incremental download. + * + * @param observer + * An observer to be notified of various events. OnStartRequest is + * called when finalURI and totalSize have been determined or when an + * error occurs. OnStopRequest is called when the file is completely + * downloaded or when an error occurs. If this object implements + * nsIProgressEventSink, then its OnProgress method will be called as + * data is written to the destination file. If this object implements + * nsIInterfaceRequestor, then it will be assigned as the underlying + * channel's notification callbacks, which allows it to provide a + * nsIAuthPrompt implementation if needed by the channel, for example. + * @param ctxt + * User defined object forwarded to the observer's methods. + */ + void start(in nsIRequestObserver observer, + in nsISupports ctxt); +}; diff --git a/netwerk/base/nsIIncrementalStreamLoader.idl b/netwerk/base/nsIIncrementalStreamLoader.idl new file mode 100644 index 0000000000..aef56ed41f --- /dev/null +++ b/netwerk/base/nsIIncrementalStreamLoader.idl @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIThreadRetargetableStreamListener.idl" + +interface nsIRequest; +interface nsIIncrementalStreamLoader; + +[scriptable, uuid(07c3d2cc-5454-4618-9f4f-cd93de9504a4)] +interface nsIIncrementalStreamLoaderObserver : nsISupports +{ + /** + * Called when new data has arrived on the stream. + * + * @param loader the stream loader that loaded the stream. + * @param ctxt the context parameter of the underlying channel + * @param dataLength the length of the new data received + * @param data the contents of the new data received. + * + * This method will always be called asynchronously by the + * nsIIncrementalStreamLoader involved, on the thread that called the + * loader's init() method. + * + * If the observer wants to not accumulate all or portional of the data in + * the internal buffer, the consumedLength shall be set to the value of + * the dataLength or less. By default the consumedLength value is assumed 0. + * The data and dataLength reflect the non-consumed data and will be + * accumulated if consumedLength is not set. + * + * In comparison with onStreamComplete(), the data buffer cannot be + * adopted if this method returns NS_SUCCESS_ADOPTED_DATA. + */ + void onIncrementalData(in nsIIncrementalStreamLoader loader, + in nsISupports ctxt, + in unsigned long dataLength, + [const,array,size_is(dataLength)] in octet data, + inout unsigned long consumedLength); + + /** + * Called when the entire stream has been loaded. + * + * @param loader the stream loader that loaded the stream. + * @param ctxt the context parameter of the underlying channel + * @param status the status of the underlying channel + * @param resultLength the length of the data loaded + * @param result the data + * + * This method will always be called asynchronously by the + * nsIIncrementalStreamLoader involved, on the thread that called the + * loader's init() method. + * + * If the observer wants to take over responsibility for the + * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA + * in place of NS_OK as its success code. The loader will then + * "forget" about the data and not free() it after + * onStreamComplete() returns; observer must call free() + * when the data is no longer required. + */ + void onStreamComplete(in nsIIncrementalStreamLoader loader, + in nsISupports ctxt, + in nsresult status, + in unsigned long resultLength, + [const,array,size_is(resultLength)] in octet result); +}; + +/** + * Asynchronously loads a channel into a memory buffer. + * + * To use this interface, first call init() with a nsIIncrementalStreamLoaderObserver + * that will be notified when the data has been loaded. Then call asyncOpen() + * on the channel with the nsIIncrementalStreamLoader as the listener. The context + * argument in the asyncOpen() call will be passed to the onStreamComplete() + * callback. + * + * XXX define behaviour for sizes >4 GB + */ +[scriptable, uuid(a023b060-ba23-431a-b449-2dd63e220554)] +interface nsIIncrementalStreamLoader : nsIThreadRetargetableStreamListener +{ + /** + * Initialize this stream loader, and start loading the data. + * + * @param aObserver + * An observer that will be notified when the data is complete. + */ + void init(in nsIIncrementalStreamLoaderObserver aObserver); + + /** + * Gets the number of bytes read so far. + */ + readonly attribute unsigned long numBytesRead; + + /** + * Gets the request that loaded this file. + * null after the request has finished loading. + */ + readonly attribute nsIRequest request; +}; diff --git a/netwerk/base/nsIInputStreamChannel.idl b/netwerk/base/nsIInputStreamChannel.idl new file mode 100644 index 0000000000..9172a2b283 --- /dev/null +++ b/netwerk/base/nsIInputStreamChannel.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; +interface nsIURI; + +/** + * nsIInputStreamChannel + * + * This interface provides methods to initialize an input stream channel. + * The input stream channel serves as a data pump for an input stream. + */ +[scriptable, uuid(ea730238-4bfd-4015-8489-8f264d05b343)] +interface nsIInputStreamChannel : nsISupports +{ + /** + * Sets the URI for this channel. This must be called before the + * channel is opened, and it may only be called once. + */ + void setURI(in nsIURI aURI); + + /** + * Get/set the content stream + * + * This stream contains the data that will be pushed to the channel's + * stream listener. If the stream is non-blocking and supports the + * nsIAsyncInputStream interface, then the stream will be read directly. + * Otherwise, the stream will be read on a background thread. + * + * This attribute must be set before the channel is opened, and it may + * only be set once. + * + * @throws NS_ERROR_IN_PROGRESS if the setter is called after the channel + * has been opened. + */ + attribute nsIInputStream contentStream; + + /** + * Get/set the srcdoc data string. When the input stream channel is + * created to load a srcdoc iframe, this is set to hold the value of the + * srcdoc attribute. + * + * This should be the same value used to create contentStream, but this is + * not checked. + * + * Changing the value of this attribute will not otherwise affect the + * functionality of the channel or input stream. + */ + attribute AString srcdocData; + + /** + * Returns true if srcdocData has been set within the channel. + */ + readonly attribute boolean isSrcdocChannel; + + /** + * The base URI to be used for the channel. Used when the base URI cannot + * be inferred by other means, for example when this is a srcdoc channel. + */ + attribute nsIURI baseURI; +}; diff --git a/netwerk/base/nsIInputStreamPump.idl b/netwerk/base/nsIInputStreamPump.idl new file mode 100644 index 0000000000..2bd1ee1683 --- /dev/null +++ b/netwerk/base/nsIInputStreamPump.idl @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsIInputStream; +interface nsISerialEventTarget; +interface nsIStreamListener; + +/** + * nsIInputStreamPump + * + * This interface provides a means to configure and use a input stream pump + * instance. The input stream pump will asynchronously read from an input + * stream, and push data to an nsIStreamListener instance. It utilizes the + * current thread's nsIEventTarget in order to make reading from the stream + * asynchronous. A different thread can be used if the pump also implements + * nsIThreadRetargetableRequest. + * + * If the given stream supports nsIAsyncInputStream, then the stream pump will + * call the stream's AsyncWait method to drive the stream listener. Otherwise, + * the stream will be read on a background thread utilizing the stream + * transport service. More details are provided below. + */ +[scriptable, uuid(400F5468-97E7-4d2b-9C65-A82AECC7AE82)] +interface nsIInputStreamPump : nsIRequest +{ + /** + * Initialize the input stream pump. + * + * @param aStream + * contains the data to be read. if the input stream is non-blocking, + * then it will be QI'd to nsIAsyncInputStream. if the QI succeeds + * then the stream will be read directly. otherwise, it will be read + * on a background thread using the stream transport service. + * @param aSegmentSize + * if the stream transport service is used, then this parameter + * specifies the segment size for the stream transport's buffer. + * pass 0 to specify the default value. + * @param aSegmentCount + * if the stream transport service is used, then this parameter + * specifies the segment count for the stream transport's buffer. + * pass 0 to specify the default value. + * @param aCloseWhenDone + * if true, the input stream will be closed after it has been read. + * @param aMainThreadTarget + * a labeled main therad event target. + */ + void init(in nsIInputStream aStream, + in unsigned long aSegmentSize, + in unsigned long aSegmentCount, + in boolean aCloseWhenDone, + [optional] in nsISerialEventTarget aMainThreadTarget); + + /** + * asyncRead causes the input stream to be read in chunks and delivered + * asynchronously to the listener via OnDataAvailable. + * + * @param aListener + * receives notifications. + * @param aListenerContext + * passed to listener methods. + */ + void asyncRead(in nsIStreamListener aListener); +}; diff --git a/netwerk/base/nsIInterceptionInfo.idl b/netwerk/base/nsIInterceptionInfo.idl new file mode 100644 index 0000000000..d3c1b030ac --- /dev/null +++ b/netwerk/base/nsIInterceptionInfo.idl @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIContentPolicy.idl" + +interface nsIPrincipal; +interface nsIRedirectHistoryEntry; + +%{C++ +#include "nsTArray.h" +%} + +[ref] native nsIRedirectHistoryEntryArray(const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>); +/** + * nsIInterceptionInfo is used to record the needed information of the + * InterceptedHttpChannel. + * This infomration need to be propagated to the new channel which created by + * FetchEvent.request or ServiceWorker NavigationPreload. + */ +[scriptable, builtinclass, uuid(8b9cd81f-3cd1-4f6a-9086-92a9bbf055f4)] +interface nsIInterceptionInfo : nsISupports +{ + /** + * InterceptedHttpChannel's triggering principal + */ + [noscript, notxpcom, nostdcall, binaryname(TriggeringPrincipal)] + nsIPrincipal binaryTriggeringPrincipal(); + + [noscript, notxpcom, nostdcall, binaryname(SetTriggeringPrincipal)] + void binarySetTriggeringPrincipal(in nsIPrincipal aPrincipal); + + /** + * InterceptedHttpChannel's content policy type + */ + [noscript, notxpcom, nostdcall, binaryname(ContentPolicyType)] + nsContentPolicyType binaryContentPolicyType(); + + [noscript, notxpcom, nostdcall, binaryname(ExternalContentPolicyType)] + nsContentPolicyType binaryExternalContentPolicyType(); + + [noscript, notxpcom, nostdcall, binaryname(SetContentPolicyType)] + void binarySetContentPolicyType(in nsContentPolicyType aContentPolicyType); + +%{ C++ + inline ExtContentPolicyType GetExtContentPolicyType() + { + return static_cast<ExtContentPolicyType>(ExternalContentPolicyType()); + } +%} + + /** + * The InterceptedHttpChannel's redirect chain + */ + [noscript, notxpcom, nostdcall, binaryname(RedirectChain)] + nsIRedirectHistoryEntryArray binaryRedirectChain(); + + [noscript, notxpcom, nostdcall, binaryname(SetRedirectChain)] + void binarySetRedirectChain( + in nsIRedirectHistoryEntryArray aRedirectChain); + + /** + * The InterceptedHttpChannel is a third party channel or not. + */ + [noscript, notxpcom, nostdcall, binaryname(FromThirdParty)] + bool binaryFromThirdParty(); + + [noscript, notxpcom, nostdcall, binaryname(SetFromThirdParty)] + void binarySetFromThirdParty(in bool aFromThirdParty); +}; diff --git a/netwerk/base/nsILoadContextInfo.idl b/netwerk/base/nsILoadContextInfo.idl new file mode 100644 index 0000000000..bbf2b5d48a --- /dev/null +++ b/netwerk/base/nsILoadContextInfo.idl @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +#include "mozilla/BasePrincipal.h" +%} +native OriginAttributesNativePtr(const mozilla::OriginAttributes*); + +interface nsILoadContext; +interface nsIDOMWindow; + +/** + * Helper interface to carry informatin about the load context + * encapsulating origin attributes and IsAnonymous, IsPrivite properties. + * It shall be used where nsILoadContext cannot be used or is not + * available. + */ + +[scriptable, builtinclass, uuid(555e2f8a-a1f6-41dd-88ca-ed4ed6b98a22)] +interface nsILoadContextInfo : nsISupports +{ + /** + * Whether the context is in a Private Browsing mode + */ + readonly attribute boolean isPrivate; + + /** + * Whether the load is initiated as anonymous + */ + readonly attribute boolean isAnonymous; + + /** + * OriginAttributes hiding all the security context attributes + */ + [implicit_jscontext] + readonly attribute jsval originAttributes; + [noscript, notxpcom, nostdcall, binaryname(OriginAttributesPtr)] + OriginAttributesNativePtr binaryOriginAttributesPtr(); + +%{C++ + /** + * De-XPCOMed getters + */ + bool IsPrivate() + { + bool pb; + GetIsPrivate(&pb); + return pb; + } + + bool IsAnonymous() + { + bool anon; + GetIsAnonymous(&anon); + return anon; + } + + bool Equals(nsILoadContextInfo *aOther) + { + return IsAnonymous() == aOther->IsAnonymous() && + *OriginAttributesPtr() == *aOther->OriginAttributesPtr(); + } +%} +}; + +/** + * Since OriginAttributes struct limits the implementation of + * nsILoadContextInfo (that needs to be thread safe) to C++, + * we need a scriptable factory to create instances of that + * interface from JS. + */ +[scriptable, uuid(c1c7023d-4318-4f99-8307-b5ccf0558793)] +interface nsILoadContextInfoFactory : nsISupports +{ + readonly attribute nsILoadContextInfo default; + readonly attribute nsILoadContextInfo private; + readonly attribute nsILoadContextInfo anonymous; + [implicit_jscontext] + nsILoadContextInfo custom(in boolean aAnonymous, in jsval aOriginAttributes); + nsILoadContextInfo fromLoadContext(in nsILoadContext aLoadContext, in boolean aAnonymous); + nsILoadContextInfo fromWindow(in nsIDOMWindow aWindow, in boolean aAnonymous); +}; diff --git a/netwerk/base/nsILoadGroup.idl b/netwerk/base/nsILoadGroup.idl new file mode 100644 index 0000000000..06e51e442b --- /dev/null +++ b/netwerk/base/nsILoadGroup.idl @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequest.idl" + +interface nsISimpleEnumerator; +interface nsIRequestObserver; +interface nsIInterfaceRequestor; +interface nsIRequestContext; + +/** + * A load group maintains a collection of nsIRequest objects. + * This is used in lots of places where groups of requests need to be tracked. + * For example, Document::mDocumentLoadGroup is used to track all requests + * made for subdocuments in order to track page load progress and allow all + * requests made on behalf of the document to be stopped, etc. + */ +[builtinclass, scriptable, uuid(f0c87725-7a35-463c-9ceb-2c07f23406cc)] +interface nsILoadGroup : nsIRequest +{ + /** + * The group observer is notified when requests are added to and removed + * from this load group. The groupObserver is weak referenced. + */ + attribute nsIRequestObserver groupObserver; + + /** + * Accesses the default load request for the group. Each time a number + * of requests are added to a group, the defaultLoadRequest may be set + * to indicate that all of the requests are related to a base request. + * + * The load group inherits its load flags from the default load request. + * If the default load request is NULL, then the group's load flags are + * not changed. + */ + attribute nsIRequest defaultLoadRequest; + + /** + * Adds a new request to the group. This will cause the default load + * flags to be applied to the request. If this is a foreground + * request then the groupObserver's onStartRequest will be called. + * + * If the request is the default load request or if the default load + * request is null, then the load group will inherit its load flags from + * the request. + */ + void addRequest(in nsIRequest aRequest, + in nsISupports aContext); + + /** + * Removes a request from the group. If this is a foreground request + * then the groupObserver's onStopRequest will be called. + * + * By the time this call ends, aRequest will have been removed from the + * loadgroup, even if this function throws an exception. + */ + void removeRequest(in nsIRequest aRequest, + in nsISupports aContext, + in nsresult aStatus); + + /** + * Returns the requests contained directly in this group. + * Enumerator element type: nsIRequest. + */ + readonly attribute nsISimpleEnumerator requests; + + /** + * Returns the count of "active" requests (ie. requests without the + * LOAD_BACKGROUND bit set). + */ + readonly attribute unsigned long activeCount; + + /** + * Notification callbacks for the load group. + */ + attribute nsIInterfaceRequestor notificationCallbacks; + + /** + * Context for managing things like js/css connection blocking, + * and per-tab connection grouping. + */ + readonly attribute unsigned long long requestContextID; + + /** + * The set of load flags that will be added to all new requests added to + * this group. Any existing requests in the load group are not modified, + * so it is expected these flags will be added before requests are added + * to the group - typically via nsIDocShell::defaultLoadFlags on a new + * docShell. + * Note that these flags are *not* added to the default request for the + * load group; it is expected the default request will already have these + * flags (again, courtesy of setting nsIDocShell::defaultLoadFlags before + * the docShell has created the default request.) + */ + attribute nsLoadFlags defaultLoadFlags; + + /** + * Returns true if the loadGroup belongs to a discarded context, such as, a + * terminated private browsing session. + */ + [infallible] + readonly attribute boolean isBrowsingContextDiscarded; +}; diff --git a/netwerk/base/nsILoadGroupChild.idl b/netwerk/base/nsILoadGroupChild.idl new file mode 100644 index 0000000000..959913a474 --- /dev/null +++ b/netwerk/base/nsILoadGroupChild.idl @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsILoadGroup; + +/** + * nsILoadGroupChild provides a hierarchy of load groups so that the + * root load group can be used to conceptually tie a series of loading + * operations into a logical whole while still leaving them separate + * for the purposes of cancellation and status events. + */ + +[builtinclass, scriptable, uuid(02efe8e2-fbbc-4718-a299-b8a09c60bf6b)] +interface nsILoadGroupChild : nsISupports +{ + /** + * The parent of this load group. It is stored with + * a nsIWeakReference/nsWeakPtr so there is no requirement for the + * parentLoadGroup to out live the child, nor will the child keep a + * reference count on the parent. + */ + attribute nsILoadGroup parentLoadGroup; + + /** + * The nsILoadGroup associated with this nsILoadGroupChild + */ + readonly attribute nsILoadGroup childLoadGroup; + + /** + * The rootLoadGroup is the recursive parent of this + * load group where parent is defined as parentlLoadGroup if set + * or childLoadGroup.loadGroup as a backup. (i.e. parentLoadGroup takes + * precedence.) The nsILoadGroup child is the root if neither parent + * nor loadgroup attribute is specified. + */ + readonly attribute nsILoadGroup rootLoadGroup; +}; diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl new file mode 100644 index 0000000000..ddfcb223e6 --- /dev/null +++ b/netwerk/base/nsILoadInfo.idl @@ -0,0 +1,1535 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIContentPolicy.idl" +#include "nsIScriptSecurityManager.idl" +#include "nsIInterceptionInfo.idl" + +interface nsIChannel; +interface nsIContentSecurityPolicy; +interface nsICookieJarSettings; +interface nsICSPEventListener; +interface nsINode; +interface nsIPrincipal; +interface nsIRedirectHistoryEntry; +interface nsIURI; +webidl Document; +webidl BrowsingContext; +native LoadContextRef(already_AddRefed<nsISupports>); +%{C++ +#include "nsTArray.h" +#include "mozilla/LoadTainting.h" +#include "mozilla/OriginAttributes.h" +#include "mozilla/UniquePtr.h" +#include "nsRFPService.h" +#include "nsStringFwd.h" + +namespace mozilla { +namespace dom { +class ClientInfo; +class ClientSource; +class PerformanceStorage; +class ServiceWorkerDescriptor; +} // namespace dom +} // namespace mozilla +%} + +[ref] native nsIRedirectHistoryEntryArray(const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>); +native OriginAttributes(mozilla::OriginAttributes); +[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes); +[ref] native CStringArrayRef(const nsTArray<nsCString>); +[ref] native StringArrayRef(const nsTArray<nsString>); +[ref] native Uint64ArrayRef(const nsTArray<uint64_t>); +[ref] native PrincipalArrayRef(const nsTArray<nsCOMPtr<nsIPrincipal>>); +[ref] native const_ClientInfoRef(const mozilla::dom::ClientInfo); + native UniqueClientSource(mozilla::UniquePtr<mozilla::dom::ClientSource>); + native UniqueClientSourceMove(mozilla::UniquePtr<mozilla::dom::ClientSource>&&); +[ref] native const_MaybeClientInfoRef(const mozilla::Maybe<mozilla::dom::ClientInfo>); +[ref] native const_ServiceWorkerDescriptorRef(const mozilla::dom::ServiceWorkerDescriptor); +[ref] native const_MaybeServiceWorkerDescriptorRef(const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>); +[ref] native const_MaybeRFPTarget(const mozilla::Maybe<mozilla::RFPTarget>); + native RFPTarget(mozilla::RFPTarget); +[ptr] native PerformanceStoragePtr(mozilla::dom::PerformanceStorage); + native LoadTainting(mozilla::LoadTainting); + native CSPRef(already_AddRefed<nsIContentSecurityPolicy>); + +typedef unsigned long nsSecurityFlags; + +/** + * The LoadInfo object contains information about a network load, why it + * was started, and how we plan on using the resulting response. + * If a network request is redirected, the new channel will receive a new + * LoadInfo object. The new object will contain mostly the same + * information as the pre-redirect one, but updated as appropriate. + * For detailed information about what parts of LoadInfo are updated on + * redirect, see documentation on individual properties. + */ +[scriptable, builtinclass, uuid(ddc65bf9-2f60-41ab-b22a-4f1ae9efcd36)] +interface nsILoadInfo : nsISupports +{ + /** + * The following five flags determine the security mode and hence what kind of + * security checks should be performed throughout the lifetime of the channel. + * + * * SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT + * * SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED + * * SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT + * * SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL + * * SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT + * + * Exactly one of these flags are required to be set in order to allow + * the channel to perform the correct security checks (SOP, CORS, ...) and + * return the correct result principal. If none or more than one of these + * flags are set AsyncOpen will fail. + */ + + /** + * Warning: Never use this flag when creating a new channel! + * Only use this flag if you have to create a temporary LoadInfo + * for performing an explicit nsIContentPolicy check, like e.g. + * when loading something from the cache that needs an explicit + * nsIContentPolicy check. In all other cases pick one of the + * security flags underneath. + */ + const unsigned long SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK = 0; + + /* + * Enforce the same origin policy where loads inherit the principal. + * See the documentation for principalToInherit, which describes exactly what + * principal is inherited. + */ + const unsigned long SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT = (1<<0); + + /* + * Enforce the same origin policy and data: loads are blocked. + */ + const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED = (1<<1); + + /** + * Allow loads from other origins. Loads which inherit the principal should + * see the documentation for principalToInherit, which describes exactly what + * principal is inherited. + * + * Commonly used by plain <img>, <video>, <link rel=stylesheet> etc. + */ + const unsigned long SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT = (1 << 2); + + /** + * Allow loads from other origins. Loads from data: will be allowed, + * but the resulting resource will get a null principal. + * Used in blink/webkit for <iframe>s. Likely also the mode + * that should be used by most Chrome code. + */ + const unsigned long SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL = (1<<3); + + /** + * Allow loads from any origin, but require CORS for cross-origin loads. + * See the documentation for principalToInherit, which describes exactly what + * principal is inherited. + * + * Commonly used by <img crossorigin>, <video crossorigin>, + * XHR, fetch(), etc. + */ + const unsigned long SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT = (1<<4); + + /** + * Choose cookie policy. The default policy is equivalent to "INCLUDE" for + * SEC_REQUIRE_SAME_ORIGIN_* and SEC_ALLOW_CROSS_ORIGIN_* modes, and + * equivalent to "SAME_ORIGIN" for SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT mode. + * + * This means that if you want to perform a CORS load with credentials, pass + * SEC_COOKIES_INCLUDE. + * + * Note that these flags are still subject to the user's cookie policies. + * For example, if the user is blocking 3rd party cookies, those cookies + * will be blocked no matter which of these flags are set. + */ + const unsigned long SEC_COOKIES_DEFAULT = (0 << 5); + const unsigned long SEC_COOKIES_INCLUDE = (1 << 5); + const unsigned long SEC_COOKIES_SAME_ORIGIN = (2 << 5); + const unsigned long SEC_COOKIES_OMIT = (3 << 5); + + /** + * Force inheriting of the principal. See the documentation for + * principalToInherit, which describes exactly what principal is inherited. + * + * Setting this flag will cause GetChannelResultPrincipal to return the + * principal to be inherited as the channel principal. + * + * This will happen independently of the scheme of the URI that the + * channel is loading. + * + * So if the principal that gets inherited is "http://a.com/", and the channel + * is loading the URI "http://b.com/whatever", GetChannelResultPrincipal + * will return a principal from "http://a.com/". + * + * This flag can not be used together with SANDBOXED_ORIGIN sandbox flag. If + * both are passed to the LoadInfo constructor then this flag will be dropped. + * If you need to know whether this flag would have been present but was dropped + * due to sandboxing, check for the forceInheritPrincipalDropped flag. + */ + const unsigned long SEC_FORCE_INHERIT_PRINCIPAL = (1<<7); + + /** + * Inherit the Principal for about:blank. + */ + const unsigned long SEC_ABOUT_BLANK_INHERITS = (1<<9); + + /** + * Allow access to chrome: packages that are content accessible. + */ + const unsigned long SEC_ALLOW_CHROME = (1<<10); + + /** + * Disallow access to javascript: uris. + */ + const unsigned long SEC_DISALLOW_SCRIPT = (1<<11); + + /** + * Don't follow redirects. Instead the redirect response is returned + * as a successful response for the channel. + * + * Redirects not initiated by a server response, i.e. REDIRECT_INTERNAL and + * REDIRECT_STS_UPGRADE, are still followed. + * + * Note: If this flag is set and the channel response is a redirect, then + * the response body might not be available. + * This can happen if the redirect was cached. + */ + const unsigned long SEC_DONT_FOLLOW_REDIRECTS = (1<<12); + + /** + * Load an error page, it should be one of following : about:neterror, + * about:certerror, about:blocked, about:tabcrashed or about:restartrequired. + */ + const unsigned long SEC_LOAD_ERROR_PAGE = (1<<13); + + /** + * Force inheriting of the principal, overruling any owner that might be set + * on the channel. (Please note that channel.owner is deprecated and will be + * removed within Bug 1286838). See the documentation for principalToInherit, + * which describes exactly what principal is inherited. + * + * Setting this flag will cause GetChannelResultPrincipal to return the + * principal to be inherited as the channel principal. + * + * This will happen independently of the scheme of the URI that the + * channel is loading. + */ + const unsigned long SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER = (1<<14); + + /** + * This is the principal of the network request's caller/requester where + * the resulting resource will be used. I.e. it is the principal which + * will get access to the result of the request. (Where "get access to" + * might simply mean "embed" depending on the type of resource that is + * loaded). + * + * For example for an image, it is the principal of the document where + * the image is rendered. For a stylesheet it is the principal of the + * document where the stylesheet will be applied. + * + * So if document at http://a.com/page.html loads an image from + * http://b.com/pic.jpg, then loadingPrincipal will be + * http://a.com/page.html. + * + * For <iframe> and <frame> loads, the LoadingPrincipal is the + * principal of the parent document. For top-level loads, the + * LoadingPrincipal is null. For all loads except top-level loads + * the LoadingPrincipal is never null. + * + * If the loadingPrincipal is the system principal, no security checks + * will be done at all. There will be no security checks on the initial + * load or any subsequent redirects. This means there will be no + * nsIContentPolicy checks or any CheckLoadURI checks. Because of + * this, never set the loadingPrincipal to the system principal when + * the URI to be loaded is controlled by a webpage. + * If the loadingPrincipal and triggeringPrincipal are both + * content principals, then we will always call into + * nsIContentPolicies and CheckLoadURI. The call to nsIContentPolicies + * and CheckLoadURI happen even if the URI to be loaded is same-origin + * with the loadingPrincipal or triggeringPrincipal. + */ + readonly attribute nsIPrincipal loadingPrincipal; + + /** + * A C++-friendly version of triggeringPrincipal. + * + * This is a bit awkward because we can't use + * binaryname(GetLoadingPrincipal). + */ + [noscript, notxpcom, nostdcall] + nsIPrincipal virtualGetLoadingPrincipal(); + +%{C++ + nsIPrincipal* GetLoadingPrincipal() { + return VirtualGetLoadingPrincipal(); + } +%} + + /** + * This is the principal which caused the network load to start. I.e. + * this is the principal which provided the URL to be loaded. This is + * often the same as the LoadingPrincipal, but there are a few cases + * where that's not true. + * + * For example for loads into an <iframe>, the LoadingPrincipal is always + * the principal of the parent document. However the triggeringPrincipal + * is the principal of the document which provided the URL that the + * <iframe> is navigating to. This could be the previous document inside + * the <iframe> which set document.location. Or a document elsewhere in + * the frame tree which contained a <a target="..."> which targetted the + * <iframe>. + * + * If a stylesheet links to a sub-resource, like an @imported stylesheet, + * or a background image, then the triggeringPrincipal is the principal + * of the stylesheet, while the LoadingPrincipal is the principal of the + * document being styled. + * + * The triggeringPrincipal is never null. + * + * If the triggeringPrincipal is the system principal, no security checks + * will be done at all. There will be no security checks on the initial + * load or any subsequent redirects. This means there will be no + * nsIContentPolicy checks or any CheckLoadURI checks. Because of + * this, never set the triggeringPrincipal to the system principal when + * the URI to be loaded is controlled by a webpage. + * If the loadingPrincipal and triggeringPrincipal are both + * content principals, then we will always call into + * nsIContentPolicies and CheckLoadURI. The call to nsIContentPolicies + * and CheckLoadURI happen even if the URI to be loaded is same-origin + * with the loadingPrincipal or triggeringPrincipal. + */ + readonly attribute nsIPrincipal triggeringPrincipal; + + /** + * A C++-friendly version of triggeringPrincipal. + */ + [noscript, notxpcom, nostdcall, binaryname(TriggeringPrincipal)] + nsIPrincipal binaryTriggeringPrincipal(); + + /** + * The remote type of the process which caused the network load to start. I.e. + * this is the remote type of the process which provided the URL to be loaded. + * + * For subresource loads, this should be the same as the process which will + * handle the response, however for document loads this may both be different + * than the final process, as well as different from the process which starts + * the navigation. + * + * This field is intentionally not perfectly preserved over IPC, and will be + * reset to the remote type of the sending process when sent from a content + * process to the parent process. + */ + attribute AUTF8String triggeringRemoteType; + + /** + * For non-document loads the principalToInherit is always null. For + * loads of type TYPE_DOCUMENT or TYPE_SUBDOCUMENT the principalToInherit + * might be null. If it's non null, then this is the principal that is + * inherited if a principal needs to be inherited. If the principalToInherit + * is null but the inherit flag is set, then the triggeringPrincipal is + * the principal that is inherited. + */ + attribute nsIPrincipal principalToInherit; + + /** + * A C++-friendly version of principalToInherit. + */ + [noscript, notxpcom, nostdcall, binaryname(PrincipalToInherit)] + nsIPrincipal binaryPrincipalToInherit(); + + /** + * Finds the correct principal to inherit for the given channel, based on + * the values of PrincipalToInherit and TriggeringPrincipal. + */ + [noscript, notxpcom, nostdcall] + nsIPrincipal FindPrincipalToInherit(in nsIChannel aChannel); + + /** + * This is the ownerDocument of the LoadingNode. Unless the LoadingNode + * is a Document, in which case the LoadingDocument is the same as the + * LoadingNode. + * + * For top-level loads, and for loads originating from workers, the + * LoadingDocument is null. When the LoadingDocument is not null, the + * LoadingPrincipal is set to the principal of the LoadingDocument. + */ + readonly attribute Document loadingDocument; + + /** + * A C++-friendly version of loadingDocument (loadingNode). + * This is the Node where the resulting resource will be used. I.e. it is + * the Node which will get access to the result of the request. (Where + * "get access to" might simply mean "embed" depending on the type of + * resource that is loaded). + * + * For example for an <img>/<video> it is the image/video element. For + * document loads inside <iframe> and <frame>s, the LoadingNode is the + * <iframe>/<frame> element. For an XMLHttpRequest, it is the Document + * which contained the JS which initiated the XHR. For a stylesheet, it + * is the Document that contains <link rel=stylesheet>. + * + * For loads triggered by the HTML pre-parser, the LoadingNode is the + * Document which is currently being parsed. + * + * For top-level loads, and for loads originating from workers, the + * LoadingNode is null. If the LoadingNode is non-null, then the + * LoadingPrincipal is the principal of the LoadingNode. + */ + [noscript, notxpcom, nostdcall, binaryname(LoadingNode)] + nsINode binaryLoadingNode(); + + /** + * A C++ friendly version of the loadingContext for toplevel loads. + * Most likely you want to query the ownerDocument or LoadingNode + * and not this context only available for TYPE_DOCUMENT loads. + * Please note that except for loads of TYPE_DOCUMENT, this + * ContextForTopLevelLoad will always return null. + */ + [noscript, notxpcom, nostdcall, binaryname(ContextForTopLevelLoad)] + LoadContextRef binaryContextForTopLevelLoad(); + + /** + * For all loads except loads of TYPE_DOCUMENT, the loadingContext + * simply returns the loadingNode. For loads of TYPE_DOCUMENT this + * will return the context available for top-level loads which + * do not have a loadingNode. + */ + [binaryname(LoadingContextXPCOM)] + readonly attribute nsISupports loadingContext; + + /** + * A C++ friendly version of the loadingContext. + */ + [noscript, notxpcom, nostdcall, binaryname(GetLoadingContext)] + LoadContextRef binaryGetLoadingContext(); + + /** + * The securityFlags of that channel. + */ + readonly attribute nsSecurityFlags securityFlags; + +%{C++ + inline nsSecurityFlags GetSecurityFlags() + { + nsSecurityFlags result; + mozilla::DebugOnly<nsresult> rv = GetSecurityFlags(&result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return result; + } +%} + + /** + * The sandboxFlags of that channel. + */ + [infallible] readonly attribute unsigned long sandboxFlags; + + /** + * The TriggingSandboxFlags are the SandboxFlags of the entity + * responsible for causing the load to occur. + */ + [infallible] attribute unsigned long triggeringSandboxFlags; + + + /** + * The window id and storage access status of the window of the + * context that triggered the load. This is used to allow self-initiated + * same-origin navigations to propogate their "has storage access" bit + * to the next Document. + */ + [infallible] attribute unsigned long long triggeringWindowId; + [infallible] attribute boolean triggeringStorageAccess; + + /** + * Allows to query only the security mode bits from above. + */ + [infallible] readonly attribute unsigned long securityMode; + + /** + * This flag is used for any browsing context where we should not sniff + * the content type. E.g if an iframe has the XCTO nosniff header, then + * that flag is set to true so we skip content sniffing for that browsing + * context. + */ + [infallible] attribute boolean skipContentSniffing; + + /** + * (default) If this flag is set, it has not yet been determined if the + * HTTPS-Only mode will upgrade the request. + */ + const unsigned long HTTPS_ONLY_UNINITIALIZED = (1 << 0); + + /** + * Indicates that this is the first time the request gets upgraded, and thus + * the HTTPS-Only StreamListener hasn't been registered yet. Even though there + * might be multiple channels per request that have to be upgraded (e.g., + * because of redirects), the StreamListener only has to be attached to one + * channel. + */ + const unsigned long HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED = (1 << 1); + + /** + * Indicates that the request will get upgraded, and the HTTPS-Only + * StreamListener got registered. + */ + const unsigned long HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED = (1 << 2); + + /** + * This flag can be manually set if the HTTPS-Only mode should exempt the + * request and not upgrade it. (e.g in the case of OCSP. + */ + const unsigned long HTTPS_ONLY_EXEMPT = (1 << 3); + + /** + * This flag can only ever be set on top-level loads. It indicates + * that the top-level https connection succeeded. This flag is mostly + * used to counter time-outs which allows to cancel the channel + * if the https load has not started. + */ + const unsigned long HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS = (1 << 4); + + /** + * This flag can only ever be set on downloads. It indicates + * that the download https connection succeeded. This flag is mostly + * used to counter time-outs which allows to cancel the channel + * if the https load has not started. + */ + const unsigned long HTTPS_ONLY_DOWNLOAD_IN_PROGRESS = (1 << 5); + + /** + * This flag indicates that the request should not be logged to the + * console. + */ + const unsigned long HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE = (1 << 6); + + /** + * This flag indicates that the request was upgraded by https-first mode. + */ + const unsigned long HTTPS_ONLY_UPGRADED_HTTPS_FIRST = (1 << 7); + + /** + * This flag indicates that the request should not be blocked by ORB. + */ + const unsigned long HTTPS_ONLY_BYPASS_ORB = (1 << 8); + + /** + * This flag indicates that HTTPS_ONLY_EXEMPT should be + * set the next time HTTPS-Only exemptions are checked + * and HTTPS-First is enabled. + */ + const unsigned long HTTPS_FIRST_EXEMPT_NEXT_LOAD = (1 << 9); + + /** + * Upgrade state of HTTPS-Only Mode. The flag HTTPS_ONLY_EXEMPT can get + * set on requests that should be excempt from an upgrade. + */ + [infallible] attribute unsigned long httpsOnlyStatus; + + /** + * Reflects whetehr this is an HTTP Strict Transport Security host + */ +[infallible] attribute boolean hstsStatus; + + /** + * Returns true if at the time of the loadinfo construction the document + * that triggered this load has the bit hasValidTransientUserGestureActivation + * set or the load was triggered from External. (Mostly this bool is used + * in the context of Sec-Fetch-User.) + */ + [infallible] attribute boolean hasValidUserGestureActivation; + + /** + * We disallow the SystemPrincipal to initiate requests to + * the public web. This flag is to allow exceptions. + */ + [infallible] attribute boolean allowDeprecatedSystemRequests; + + /** + * Only ever returns true if the loadinfo is of TYPE_SCRIPT and + * the script was created by the HTML parser. + */ + [infallible] attribute boolean parserCreatedScript; + + /** + * True if this request is known to have been triggered by a user + * manually requesting the URI to be saved. + */ + [infallible] attribute boolean isUserTriggeredSave; + + /** + * True if this request is from DevTools. + */ + [infallible] attribute boolean isInDevToolsContext; + + /** + * True if this request is embedded in a context that can't be third-party + * (i.e. an iframe embedded in a cross-origin parent window). If this is + * false, then this request may be third-party if it's a third-party to + * loadingPrincipal. + */ + [infallible] attribute boolean isInThirdPartyContext; + + /** + * True if this request is a third party in respect to the top-level window. + * + * Note that this doesn't consider the parent window. I.e. It will still + * return false even in the case that the parent is cross-origin but the + * top-level is same-origin. + * + * This value would be set during opening the channel in parent and propagate + * to the channel in the content. + */ + [infallible] attribute boolean isThirdPartyContextToTopWindow; + + /** + * See the SEC_COOKIES_* flags above. This attribute will never return + * SEC_COOKIES_DEFAULT, but will instead return what the policy resolves to. + * I.e. SEC_COOKIES_SAME_ORIGIN for CORS mode, and SEC_COOKIES_INCLUDE + * otherwise. + */ + [infallible] readonly attribute unsigned long cookiePolicy; + + /** + * The cookie jar settings inherited from the top-level document's loadInfo. + * It cannot be null. + */ + attribute nsICookieJarSettings cookieJarSettings; + + cenum StoragePermissionState : 8 { + NoStoragePermission = 0, + HasStoragePermission = 1, + StoragePermissionAllowListed = 2, + }; + + /** + * The result of the storage permission check of the loading document. This + * value would be set during opening the channel. + */ + [infallible] attribute nsILoadInfo_StoragePermissionState + storagePermission; + + /** + * Get the granular overrides of fingerprinting protections associated to the + * channel, the value will override the default fingerprinting protection + * settings. This field will only get populated if these is one that comes + * from the local granular overrides pref or WebCompat. Otherwise, a value of + * Nothing() indicates no granular overrides are present for this channel. + * + * The RFPTarget defined in the RFPTargets.inc. + */ + [noscript, nostdcall, notxpcom] + const_MaybeRFPTarget GetOverriddenFingerprintingSettings(); + + /** + * Set the granular overrides of fingerprinting protections for the channel. + */ + [noscript, nostdcall, notxpcom] + void SetOverriddenFingerprintingSettings(in RFPTarget aTargets); + + /** + * True if the load was triggered by a meta refresh. + */ + [infallible] attribute boolean isMetaRefresh; + + /** + * If forceInheritPrincipal is true, the data coming from the channel should + * inherit its principal, even when the data is loaded over http:// or another + * protocol that would normally use a URI-based principal. + * + * See the documentation for principalToInherit, which describes exactly what + * principal is inherited. + * + * This attribute will never be true when loadingSandboxed is true. + */ + [infallible] readonly attribute boolean forceInheritPrincipal; + + /** + * If forceInheritPrincipalOverruleOwner is true, the data coming from the + * channel should inherit the principal, even when the data is loaded over + * http:// or another protocol that would normally use a URI-based principal + * and even if the channel's .owner is not null. This last is the difference + * between forceInheritPrincipalOverruleOwner and forceInheritPrincipal: the + * latter does _not_ overrule the .owner setting. + * + * See the documentation for principalToInherit, which describes exactly what + * principal is inherited. + */ + [infallible] readonly attribute boolean forceInheritPrincipalOverruleOwner; + + /** + * If loadingSandboxed is true, the data coming from the channel is + * being loaded sandboxed, so it should have a nonce origin and + * hence should use a NullPrincipal. + */ + [infallible] readonly attribute boolean loadingSandboxed; + + /** + * If aboutBlankInherits is true, then about:blank should inherit + * the principal. + */ + [infallible] readonly attribute boolean aboutBlankInherits; + + /** + * If allowChrome is true, then use nsIScriptSecurityManager::ALLOW_CHROME + * when calling CheckLoadURIWithPrincipal(). + */ + [infallible] readonly attribute boolean allowChrome; + + /** + * If disallowScript is true, then use nsIScriptSecurityManager::DISALLOW_SCRIPT + * when calling CheckLoadURIWithPrincipal(). + */ + [infallible] readonly attribute boolean disallowScript; + +%{C++ + uint32_t CheckLoadURIFlags() { + uint32_t flags = nsIScriptSecurityManager::STANDARD; + if (GetAllowChrome()) { + flags |= nsIScriptSecurityManager::ALLOW_CHROME; + } + if (GetDisallowScript()) { + flags |= nsIScriptSecurityManager::DISALLOW_SCRIPT; + } + return flags; + } +%} + + /** + * Returns true if SEC_DONT_FOLLOW_REDIRECTS is set. + */ + [infallible] readonly attribute boolean dontFollowRedirects; + + /** + * Returns true if SEC_LOAD_ERROR_PAGE is set. + */ + [infallible] readonly attribute boolean loadErrorPage; + + /** + * True if the load was initiated by a form request. + */ + [infallible] attribute boolean isFormSubmission; + + /** + * The external contentPolicyType of the channel, used for security checks + * like Mixed Content Blocking and Content Security Policy. + * + * Specifically, content policy types with _INTERNAL_ in their name will + * never get returned from this attribute. + */ + readonly attribute nsContentPolicyType externalContentPolicyType; + + /** + * CSP uses this parameter to send or not CSP violation events. + * Default value: true. + */ + [infallible] attribute boolean sendCSPViolationEvents; + +%{ C++ + inline ExtContentPolicyType GetExternalContentPolicyType() + { + nsContentPolicyType result; + mozilla::DebugOnly<nsresult> rv = GetExternalContentPolicyType(&result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return static_cast<ExtContentPolicyType>(result); + } + +%} + + + /** + * The internal contentPolicyType of the channel, used for constructing + * RequestContext values when creating a fetch event for an intercepted + * channel. + * + * This should not be used for the purposes of security checks, since + * the content policy implementations cannot be expected to deal with + * _INTERNAL_ values. Please use the contentPolicyType attribute above + * for that purpose. + */ + [noscript, notxpcom, nostdcall, binaryname(InternalContentPolicyType)] + nsContentPolicyType binaryInternalContentPolicyType(); + + readonly attribute nsContentPolicyType internalContentPolicyType; + + /** + * Returns true if document or any of the documents ancestors + * up to the toplevel document make use of the CSP directive + * 'block-all-mixed-content'. + * + * Warning: If the loadingDocument is null, then the + * blockAllMixedContent is false. + */ + [infallible] readonly attribute boolean blockAllMixedContent; + + /** + * Returns true if document or any of the documents ancestors + * up to the toplevel document make use of the CSP directive + * 'upgrade-insecure-requests'. + * + * Warning: If the loadingDocument is null, then the + * upgradeInsecureRequests is false. + */ + [infallible] readonly attribute boolean upgradeInsecureRequests; + + /** + * Returns true if the the page is https and the content is upgradable from http + * requires 'security.mixed_content.upgrade_display_content' pref to be true. + * Currently this only upgrades display content but might be expanded to other loads. + * This is very similar in implementation to upgradeInsecureRequests but browser set. + */ + [infallible] readonly attribute boolean browserUpgradeInsecureRequests; + + /** + * Returns true if the display content was or will get upgraded from http to https. + * Requires 'security.mixed_content.upgrade_display_content' pref to be true. + * Flag is set purely to collect telemetry. + */ + [infallible] attribute boolean browserDidUpgradeInsecureRequests; + + /** + * Returns true if the the page is https and the content is upgradable from http + * requires 'security.mixed_content.upgrade_display_content' pref to be false. + * See browserUpgradeInsecureRequests for more details, this only happens + * when *not* upgrading purely for telemetry. + */ + [infallible] readonly attribute boolean browserWouldUpgradeInsecureRequests; + + /** + * If true, toplevel data: URI navigation is allowed + */ + [infallible] attribute boolean forceAllowDataURI; + + /** + * If true, insecure redirects to a data: URI are allowed. + */ + [infallible] attribute boolean allowInsecureRedirectToDataURI; + + /** + * If true, the content policy security check is excluded from web requests. + */ + [infallible] attribute boolean skipContentPolicyCheckForWebRequest; + + /** + * If true, this is the load of a frame's original src attribute + */ + [infallible] attribute boolean originalFrameSrcLoad; + + /** + * The SEC_FORCE_INHERIT_PRINCIPAL flag may be dropped when a load info + * object is created. Specifically, it will be dropped if the SANDBOXED_ORIGIN + * sandbox flag is also present. This flag is set if SEC_FORCE_INHERIT_PRINCIPAL + * was dropped. + */ + [infallible] readonly attribute boolean forceInheritPrincipalDropped; + + /** + * This is the inner window ID of the window in which the element being + * loaded lives. + * + * Note that this window ID can be 0 if the window is not + * available. + */ + [infallible] readonly attribute unsigned long long innerWindowID; + + /** + * The BrowsingContext performing the load for this nsILoadInfo object. + */ + [infallible] readonly attribute unsigned long long browsingContextID; + [infallible] readonly attribute BrowsingContext browsingContext; + + /** + * The BrowsingContext which the worker is associated. + * + * Note that this could be 0 if the load is not triggered in a WorkerScope. + * This value is only set and used in the parent process for some sitautions + * the channel is created in the parent process for Workers. Such as fetch(). + * In content process, it is always 0. + * This value would not be propagated through IPC. + */ + [infallible] attribute unsigned long long workerAssociatedBrowsingContextID; + [infallible] readonly attribute BrowsingContext workerAssociatedBrowsingContext; + + /** + * Only when the element being loaded is <frame src="foo.html"> + * (or, more generally, if the element QIs to nsFrameLoaderOwner), + * the frameBrowsingContext is the browsing context containing the + * foo.html document. + * + * Note: For other cases, frameBrowsingContextID is 0. + */ + [infallible] readonly attribute unsigned long long frameBrowsingContextID; + [infallible] readonly attribute BrowsingContext frameBrowsingContext; + + /** + * If the element being loaded is a nsFrameLoaderOwner, + * `targetBrowsingContext` is the Browsing Context which will contain the + * loading document (see `frameBrowsingContext`). Otherwise, it is the + * Browsing Context performing the load (see `browsingContext`). + */ + [infallible] readonly attribute unsigned long long targetBrowsingContextID; + [infallible] readonly attribute BrowsingContext targetBrowsingContext; + + /** + * Resets the PrincipalToInherit to a freshly created NullPrincipal + * which inherits the origin attributes from the loadInfo. + * + * WARNING: Please only use that function if you know exactly what + * you are doing!!! + */ + void resetPrincipalToInheritToNullPrincipal(); + + /** + * Customized OriginAttributes within LoadInfo to allow overwriting of the + * default originAttributes from the loadingPrincipal. + * + * In chrome side, originAttributes.privateBrowsingId will always be 0 even if + * the usePrivateBrowsing is true, because chrome docshell won't set + * privateBrowsingId on origin attributes (See bug 1278664). This is to make + * sure nsILoadInfo and nsILoadContext have the same origin attributes. + */ + [implicit_jscontext, binaryname(ScriptableOriginAttributes)] + attribute jsval originAttributes; + + [noscript, nostdcall, binaryname(GetOriginAttributes)] + OriginAttributes binaryGetOriginAttributes(); + + [noscript, nostdcall, binaryname(SetOriginAttributes)] + void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs); + +%{ C++ + inline mozilla::OriginAttributes GetOriginAttributes() + { + mozilla::OriginAttributes result; + mozilla::DebugOnly<nsresult> rv = GetOriginAttributes(&result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return result; + } +%} + + /** + * Whenever a channel is evaluated by the ContentSecurityManager + * the first time, we set this flag to true to indicate that + * subsequent calls of AsyncOpen() do not have to enforce all + * security checks again. E.g., after a redirect there is no + * need to set up CORS again. We need this separate flag + * because the redirectChain might also contain internal + * redirects which might pollute the redirectChain so we can't + * rely on the size of the redirectChain-array to query whether + * a channel got redirected or not. + * + * Please note, once the flag is set to true it must remain true + * throughout the lifetime of the channel. Trying to set it + * to anything else than true will be discarded. + * + */ + [infallible] attribute boolean initialSecurityCheckDone; + + /** + * Returns true if the load was triggered from an external application + * (e.g. Thunderbird). Please note that this flag will only ever be true + * if the load is of TYPE_DOCUMENT. + */ + [infallible] attribute boolean loadTriggeredFromExternal; + + /** + * True if the tainting has been set by the service worker. + */ + [noscript, infallible] readonly attribute boolean serviceWorkerTaintingSynthesized; + + /** + * Whenever a channel gets redirected, append the redirect history entry of + * the channel which contains principal referrer and remote address [before + * the channels got redirected] to the loadinfo, so that at every point this + * array provides us information about all the redirects this channel went + * through. + * @param channelToDeriveFrom the channel being redirected + * @param aIsInternalRedirect should be true if the channel is going + * through an internal redirect, otherwise false. + */ + void appendRedirectHistoryEntry(in nsIChannel channelToDeriveFrom, + in boolean isInternalRedirect); + + /** + * An array of nsIRedirectHistoryEntry which stores redirects associated + * with this channel. This array is filled whether or not the channel has + * ever been opened. The last element of the array is associated with the + * most recent redirect. Please note, that this array *includes* internal + * redirects. + */ + [implicit_jscontext] + readonly attribute jsval redirectChainIncludingInternalRedirects; + + /** + * A C++-friendly version of redirectChain. + * Please note that this array has the same lifetime as the + * loadInfo object - use with caution! + */ + [noscript, notxpcom, nostdcall, binaryname(RedirectChainIncludingInternalRedirects)] + nsIRedirectHistoryEntryArray binaryRedirectChainIncludingInternalRedirects(); + + /** + * Same as RedirectChain but does *not* include internal redirects. + */ + [implicit_jscontext] + readonly attribute jsval redirectChain; + + /** + * A C++-friendly version of redirectChain. + * Please note that this array has the same lifetime as the + * loadInfo object - use with caution! + */ + [noscript, notxpcom, nostdcall, binaryname(RedirectChain)] + nsIRedirectHistoryEntryArray binaryRedirectChain(); + + /** + * This array is only filled out when we are in the parent process and we are + * creating a loadInfo object or deserializing LoadInfoArgs into LoadInfo, + * as we ever only need in the parent process. + * + * The array is meant to be a list of principals of the documents that the + * browsing context, corresponding to this loadInfo object, is "nested through" in + * the sense of + * <https://html.spec.whatwg.org/multipage/browsers.html#browsing-context-nested-through>. + * Note that the array does not include the principal corresponding to the frame + * loading this request. The closest ancestor is at index zero and the top level + * ancestor is at the last index. + * + * If this is a toplevel content browsing context (i.e. toplevel document in spec + * terms), the list is empty. + * + * Otherwise the array is a list for the document we're nested through (again in + * the spec sense), with the principal of that document prepended. The + * ancestorPrincipals[0] entry for an iframe load will be the principal of the + * iframe element's owner document. The ancestorPrincipals[0] entry for an image + * loaded in an iframe will be the principal of the iframe element's owner + * document. This matches the ordering specified for Location.ancestorOrigins. + * + * Please note that this array has the same lifetime as the loadInfo object - use + * with caution! + */ + [noscript, notxpcom, nostdcall] + PrincipalArrayRef AncestorPrincipals(); + + /** + * An array of BrowsingContext IDs which correspond to nsILoadInfo::AncestorPrincipals + * above. AncestorBrowsingContextIDs[0] is the BrowsingContext ID of the frame + * associated with the principal at ancestorPrincipals[0], and so forth. + * + * Please note that this array has the same lifetime as the + * loadInfo object - use with caution! + */ + [noscript, notxpcom, nostdcall] + Uint64ArrayRef AncestorBrowsingContextIDs(); + + /** + * Sets the list of unsafe headers according to CORS spec, as well as + * potentially forces a preflight. + * Note that you do not need to set the Content-Type header. That will be + * automatically detected as needed. + * + * Only call this function when using the SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT mode. + */ + [noscript, notxpcom, nostdcall] + void setCorsPreflightInfo(in CStringArrayRef unsafeHeaders, + in boolean forcePreflight); + + /** + * A C++-friendly getter for the list of cors-unsafe headers. + * Please note that this array has the same lifetime as the + * loadInfo object - use with caution! + */ + [noscript, notxpcom, nostdcall, binaryname(CorsUnsafeHeaders)] + CStringArrayRef corsUnsafeHeaders(); + + /** + * Returns value set through setCorsPreflightInfo. + */ + [infallible] readonly attribute boolean forcePreflight; + + /** + * A C++ friendly getter for the forcePreflight flag. + */ + [infallible] readonly attribute boolean isPreflight; + + /** + * Constants reflecting the channel tainting. These are mainly defined here + * for script. Internal C++ code should use the enum defined in LoadTainting.h. + * See LoadTainting.h for documentation. + */ + const unsigned long TAINTING_BASIC = 0; + const unsigned long TAINTING_CORS = 1; + const unsigned long TAINTING_OPAQUE = 2; + + /** + * Determine the associated channel's current tainting. Note, this can + * change due to a service worker intercept, so it should be checked after + * OnStartRequest() fires. + */ + readonly attribute unsigned long tainting; + + /** + * Note a new tainting level and possibly increase the current tainting + * to match. If the tainting level is already greater than the given + * value, then there is no effect. It is not possible to reduce the tainting + * level on an existing channel/loadinfo. + */ + void maybeIncreaseTainting(in unsigned long aTainting); + + /** + * Various helper code to provide more convenient C++ access to the tainting + * attribute and maybeIncreaseTainting(). + */ +%{C++ + static_assert(TAINTING_BASIC == static_cast<uint32_t>(mozilla::LoadTainting::Basic), + "basic tainting enums should match"); + static_assert(TAINTING_CORS == static_cast<uint32_t>(mozilla::LoadTainting::CORS), + "cors tainting enums should match"); + static_assert(TAINTING_OPAQUE == static_cast<uint32_t>(mozilla::LoadTainting::Opaque), + "opaque tainting enums should match"); + + mozilla::LoadTainting GetTainting() + { + uint32_t tainting = TAINTING_BASIC; + MOZ_ALWAYS_SUCCEEDS(GetTainting(&tainting)); + return static_cast<mozilla::LoadTainting>(tainting); + } + + void MaybeIncreaseTainting(mozilla::LoadTainting aTainting) + { + uint32_t tainting = static_cast<uint32_t>(aTainting); + MOZ_ALWAYS_SUCCEEDS(MaybeIncreaseTainting(tainting)); + } +%} + + /** + * Returns true if this load is for top level document. + * Note that the load for a sub-frame's document will return false here. + */ + [infallible] readonly attribute boolean isTopLevelLoad; + + /** + * If this is non-null, this property represents two things: (1) the + * URI to be used for the principal if the channel with this loadinfo + * gets a principal based on URI and (2) the URI to use for a document + * created from the channel with this loadinfo. + */ + attribute nsIURI resultPrincipalURI; + + /** + * This is the URI used to create the most recent channel in the load's + * redirect chain, if it's different from channel's `originalURI`. + * This is always null for loads not handled by DocumentLoadListener. If + * non-null, channelCreationOriginalURI will be used instead of channel's + * originalURI to re-create the channel in the final content process selected + * to perform the load. + */ + attribute nsIURI channelCreationOriginalURI; + + /** + * Returns a unique nsID used to construct the null principal for the + * resulting resource if the SANDBOXED_ORIGIN flag is set. This is used by + * GetChannelResultPrincipal() to ensure that the same null principal is + * returned every time. + */ + [noscript, nostdcall, notxpcom] + nsIDRef GetSandboxedNullPrincipalID(); + + /** + * Generates a new nsID to be returned by a future call to + * `GetSandboxedNullPrincipalID()`. + */ + [noscript, nostdcall, notxpcom] + void ResetSandboxedNullPrincipalID(); + + /** + * Return the top-level principal, which is the principal of the top-level + * window. + */ + [notxpcom, nostdcall] readonly attribute nsIPrincipal topLevelPrincipal; + + /** + * Note which client (i.e. global) initiated this network request. All + * nsGlobalWindow and WorkerPrivate can be converted to a ClientInfo to + * be set here. While this is being added to support service worker + * FetchEvent, it can also be used to communicate other information about + * the source global context in the future. + */ + [noscript, nostdcall, notxpcom] + void SetClientInfo(in const_ClientInfoRef aClientInfo); + + /** + * Get the ClientInfo for the global that initiated the network request, + * if it has been set. + */ + [noscript, nostdcall, notxpcom] + const_MaybeClientInfoRef GetClientInfo(); + + /** + * Give a pre-allocated ClientSource to the channel LoadInfo. This is + * intended to be used by docshell when loading windows without an + * initial about:blank document. The docshell will allocate the ClientSource + * to represent the client that will be created as a result of the navigation + * network request. If the channel succeeds and remains same-origin, then + * the result nsGlobalWindow will take ownership of the reserved ClientSource. + * + * This method is also called when a cross-origin redirect occurs. A new + * ClientSource with a different UUID must be created in this case. + * + * This method automatically calls SetReservedClientInfo() with the + * ClientSource::Info(). + */ + [noscript, nostdcall, notxpcom] + void GiveReservedClientSource(in UniqueClientSourceMove aClientSource); + + /** + * This method takes ownership of the reserved ClientSource previously + * provided in GiveReservedClientSource(). It may return nullptr if the + * nsILoadInfo does not own a ClientSource object. + */ + [noscript, nostdcall, notxpcom] + UniqueClientSource TakeReservedClientSource(); + + /** + * Note the reserved client that be created if this non-subresource + * network request succeeds. Depending on the type of client this + * may be called directly or indirectly via GiveReservedClientSource(). + * For example, web workers do not call give their ClientSource to + * the nsILoadInfo, but must still call this method to indicate the + * reserved client for their main script load. + */ + [noscript, nostdcall, notxpcom] + void SetReservedClientInfo(in const_ClientInfoRef aClientInfo); + + /** + * This will clear any existing reserved or initial client and override + * it with the given reserved client. This is similar to calling + * TakeReservedClientSource() and then GiveReservedClientSource() with + * a new client as ClientChannelHelper does. This method is needed, + * though, to perform this operation in the parent process where + * the LoadInfo does not have direct access to a ClientSource. + * + * If in doubt, do not call this method. Its really only needed for + * a specific redirect case where the child has created a new client on + * redirect and we need to override the parent side's reserved client + * to match. + */ + [noscript, nostdcall, notxpcom] + void OverrideReservedClientInfoInParent(in const_ClientInfoRef aClientInfo); + + /** + * Return the reserved ClientInfo for this load, if one has been set. + */ + [noscript, nostdcall, notxpcom] + const_MaybeClientInfoRef GetReservedClientInfo(); + + /** + * Note that this non-subresource network request will result in + * re-using an existing "initial" active client. This mainly only + * happens when an initial about:blank document is replaced with + * a real load in a window. In these cases we need to track this + * initial client so that we may report its existence in a FetchEvent. + * + * Note, an nsILoadInfo may only have a reserved client or an + * initial client. It should never have both. + */ + [noscript, nostdcall, notxpcom] + void SetInitialClientInfo(in const_ClientInfoRef aClientInfo); + + /** + * Return the initial ClientInfo for this load, if one has been set. + */ + [noscript, nostdcall, notxpcom] + const_MaybeClientInfoRef GetInitialClientInfo(); + + /** + * Note that this network request should be controlled by a service worker. + * For non-subresource requests this may be set during the load when + * the first service worker interception occurs. For subresource requests + * it may be set by the source client if its already controlled by a + * service worker. + */ + [noscript, nostdcall, notxpcom] + void SetController(in const_ServiceWorkerDescriptorRef aServiceWorker); + + /** + * Clear the service worker controller for this channel. This should only + * be used for window navigation redirects. By default we want to keep + * the controller in all other cases. + */ + [noscript, nostdcall, notxpcom] + void ClearController(); + + /** + * Get the service worker controlling this network request, if one has + * been set. + */ + [noscript, nostdcall, notxpcom] + const_MaybeServiceWorkerDescriptorRef GetController(); + + /** + * Set a custom performance storage. This is meant to be executed only for + * workers. If a PerformanceStorage is not set, the loadingDocument->Window + * Performance object will be returned instead. + */ + [noscript, nostdcall, notxpcom] + void SetPerformanceStorage(in PerformanceStoragePtr aPerformanceStorage); + + /** + * Get the custom PerformanceStorage if set by SetPerformanceStorage. + * Otherwise the loadingDocument->Window Performance object will be returned + * instead if all the following conditions are met: + * - the triggeringPrincipal is the same as the loadingDocument's principal. + * - if the external content policy type is TYPE_SUBDOCUMENT then loading + * wasn't caused by processing the attributes of the browsing context + * container. + */ + [noscript, nostdcall, notxpcom] + PerformanceStoragePtr GetPerformanceStorage(); + + /** + * Returns the CSP (or Preload CSP for preloads) which should be enforced + * when fetching the resource this loadinfo belongs to. + * + * a) Non-navigations: + * For non-navigation loads, GetCsp() returns what the spec refers to as the + * "request's client's global object's CSP list". In practice, if this is the + * loadinfo of a subresource load (e.g an image load), then GetCsp() or + * GetPreloadCSP() returns the CSP of the document which embeds the image. + * The returned CSP includes any policy delivered through the HTTP header or + * also through the meta tag (modulo the difference for preloads, e.g. image + * preloads have to query GetPreloadCsp() because at the time of preloading + * we are not entirely sure if the Meta CSP will be applied to the document + * in the end or not). Please note that GetCSPToInherit() called on a + * loadinfo for any non-navigation always returns null. + * + * b) Navigations: + * * Top-level loads: + * For top-level loads (navigations) GetCsp() will return null, unless + * the navigation is started by a WebExtension, in which case it will + * return the CSP of the webextension, if any. + * If you need to query the CSP that potentially should apply to the + * new top-level load, you have to query GetCspToInherit(), which is + * the CSP of the request's client's global object, just like GetCsp() + * is for non-navigation requests. + * + * * Iframe-loads: + * For iframe-loads (navigations) GetCsp() will return the CSP of the + * parent document, unless the navigation is started by a WebExtension, + * in which case it will return the CSP of the webextension, if any. + * + * If you need to query the CSP that should potentially be inherited + * into the new document, you have to query GetCSPToInherit(). + * + * TODO Bug 1557114: + * After evaluating what CSP to use for frame navigations we should + * update the above documentation to match the outcome of Bug 1557114. + */ + [notxpcom,nostdcall] CSPRef GetCsp(); + [notxpcom,nostdcall] CSPRef GetPreloadCsp(); + [notxpcom,nostdcall] CSPRef GetCspToInherit(); + + /** + * The service worker and fetch specifications require returning the + * exact tainting level of the Response passed to FetchEvent.respondWith(). + * This method allows us to override the tainting level in that case. + * + * NOTE: This should not be used outside of service worker code! Use + * nsILoadInfo::MaybeIncreaseTainting() instead. + */ + [noscript, nostdcall, notxpcom] + void SynthesizeServiceWorkerTainting(in LoadTainting aTainting); + + /** + * The top-level document has been user-interacted. + */ + [infallible] attribute boolean documentHasUserInteracted; + + /** + * During a top-level document channel redirect from tracking to + * non-tracking resources, our anti-tracking heuristic, grants the storage + * access permission for a short amount of seconds (See + * privacy.restrict3rdpartystorage.expiration_redirect pref). + * We use this flag to remember this decision even if this channel is part + * of a chain of redirects. + */ + [infallible] attribute boolean allowListFutureDocumentsCreatedFromThisRedirectChain; + + /** + * Indicates that we need to check if we should apply the anti-tracking + * heuristic after the channel has been classified. + */ + [infallible] attribute boolean needForCheckingAntiTrackingHeuristic; + + /** + * A snapshot of the nonce at load start time which is used for CSP + * checks and only set for: + * * TYPE_SCRIPT and + * * TYPE_STYLESHEET + */ + attribute AString cspNonce; + + // Subresource Integrity (SRI) metadata. + attribute AString integrityMetadata; + + /** + * List of possible reasons the request associated with this load info + * may have been blocked, set by various content blocking checkers. + */ + const uint32_t BLOCKING_REASON_NONE = 0; + const uint32_t BLOCKING_REASON_CORSDISABLED = 1001; + const uint32_t BLOCKING_REASON_CORSDIDNOTSUCCEED = 1002; + const uint32_t BLOCKING_REASON_CORSREQUESTNOTHTTP = 1003; + const uint32_t BLOCKING_REASON_CORSMULTIPLEALLOWORIGINNOTALLOWED = 1004; + const uint32_t BLOCKING_REASON_CORSMISSINGALLOWORIGIN = 1005; + const uint32_t BLOCKING_REASON_CORSNOTSUPPORTINGCREDENTIALS = 1006; + const uint32_t BLOCKING_REASON_CORSALLOWORIGINNOTMATCHINGORIGIN = 1007; + const uint32_t BLOCKING_REASON_CORSMISSINGALLOWCREDENTIALS = 1008; + const uint32_t BLOCKING_REASON_CORSORIGINHEADERNOTADDED = 1009; + const uint32_t BLOCKING_REASON_CORSEXTERNALREDIRECTNOTALLOWED = 1010; + const uint32_t BLOCKING_REASON_CORSPREFLIGHTDIDNOTSUCCEED = 1011; + const uint32_t BLOCKING_REASON_CORSINVALIDALLOWMETHOD = 1012; + const uint32_t BLOCKING_REASON_CORSMETHODNOTFOUND = 1013; + const uint32_t BLOCKING_REASON_CORSINVALIDALLOWHEADER = 1014; + const uint32_t BLOCKING_REASON_CORSMISSINGALLOWHEADERFROMPREFLIGHT = 1015; + const uint32_t BLOCKING_REASON_CLASSIFY_MALWARE_URI = 2001; + const uint32_t BLOCKING_REASON_CLASSIFY_PHISHING_URI = 2002; + const uint32_t BLOCKING_REASON_CLASSIFY_UNWANTED_URI = 2003; + const uint32_t BLOCKING_REASON_CLASSIFY_TRACKING_URI = 2004; + const uint32_t BLOCKING_REASON_CLASSIFY_BLOCKED_URI = 2005; + const uint32_t BLOCKING_REASON_CLASSIFY_HARMFUL_URI = 2006; + const uint32_t BLOCKING_REASON_CLASSIFY_CRYPTOMINING_URI = 2007; + const uint32_t BLOCKING_REASON_CLASSIFY_FINGERPRINTING_URI = 2008; + const uint32_t BLOCKING_REASON_CLASSIFY_SOCIALTRACKING_URI = 2009; + const uint32_t BLOCKING_REASON_CLASSIFY_EMAILTRACKING_URI = 2010; + const uint32_t BLOCKING_REASON_MIXED_BLOCKED = 3001; + // The general reason comes from nsCSPContext::permitsInternal(), + // which is way too generic to distinguish an exact reason. + const uint32_t BLOCKING_REASON_CONTENT_POLICY_GENERAL = 4000; + const uint32_t BLOCKING_REASON_CONTENT_POLICY_NO_DATA_PROTOCOL = 4001; + // removed 4002 + const uint32_t BLOCKING_REASON_CONTENT_POLICY_CONTENT_BLOCKED = 4003; + const uint32_t BLOCKING_REASON_CONTENT_POLICY_DATA_DOCUMENT = 4004; + const uint32_t BLOCKING_REASON_CONTENT_POLICY_WEB_BROWSER = 4005; + const uint32_t BLOCKING_REASON_CONTENT_POLICY_PRELOAD = 4006; + // The reason used when SEC_REQUIRE_SAME_ORIGIN_* is set and not satisifed. + const uint32_t BLOCKING_REASON_NOT_SAME_ORIGIN = 5000; + // The reason used when an extension cancels the request via the WebRequest api. + const uint32_t BLOCKING_REASON_EXTENSION_WEBREQUEST = 6000; + // The reason used when a request is cancelled via WebDriver BiDi network interception. + const uint32_t BLOCKING_REASON_WEBDRIVER_BIDI = 7000; + + /** + * If the request associated with this load info was blocked by some of + * our content or load blockers, the reason can be found here. + * Note that setting this attribute has NO EFFECT on blocking the request. + * This attribute is only informative! + * + * By default the value is '0' - NONE. + * Each write rewrites the last value. + * Can be accessed only on a single thread. + */ + [infallible] attribute unsigned long requestBlockingReason; + + /** + * The object in charged to receive CSP violation events. It can be null. + * This attribute will be merged into the CSP object eventually. + * See bug 1500908. + */ + attribute nsICSPEventListener cspEventListener; + + /** + * This attribute will be true if this is a load triggered by + * https://html.spec.whatwg.org/multipage/iframe-embed-object.html#process-the-iframe-attributes + * or https://html.spec.whatwg.org/multipage/obsolete.html#process-the-frame-attributes + */ + [infallible] readonly attribute boolean isFromProcessingFrameAttributes; + + cenum CrossOriginOpenerPolicy : 8 { + OPENER_POLICY_UNSAFE_NONE = 0, + OPENER_POLICY_SAME_ORIGIN = 1, + OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS = 2, + OPENER_POLICY_EMBEDDER_POLICY_REQUIRE_CORP_FLAG = 0x10, + OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP = + OPENER_POLICY_SAME_ORIGIN | + OPENER_POLICY_EMBEDDER_POLICY_REQUIRE_CORP_FLAG + }; + + cenum CrossOriginEmbedderPolicy : 8 { + EMBEDDER_POLICY_NULL = 0, + EMBEDDER_POLICY_REQUIRE_CORP = 1, + EMBEDDER_POLICY_CREDENTIALLESS = 2, + }; + + /** + * This attribute is the loading context's cross origin embedder policy. + * The value is initialized with corresponding WindowContext which get by + * innerWindowIID in the nsILoadInfo. + * It also could be set by workers when fetch is called under + * the workers' scope. + */ + [infallible] attribute nsILoadInfo_CrossOriginEmbedderPolicy + loadingEmbedderPolicy; + + /** + * This attribute will be true if the top level document has COEP: + * credentialless enabled in Origin Trial. + */ + [infallible] attribute boolean isOriginTrialCoepCredentiallessEnabledForTopLevel; + /** + * This attribute will be true if this is a load triggered by a media + * element. + */ + [infallible] attribute boolean isMediaRequest; + + /** + * This attribute will be true if this is a load triggered by a media + * element and it's an initial request. + */ + [infallible] attribute boolean isMediaInitialRequest; + + /** + * This attribute will be true if the fetch request is from object or embed + * elements + */ + [infallible] attribute boolean isFromObjectOrEmbed; + + /** + * This attribute will be true if the URL is known to be possibly broken and + * CheckForBrokenChromeURL and RecordZeroLengthEvent should be skipped. + */ + [infallible] readonly attribute boolean shouldSkipCheckForBrokenURLOrZeroSized; + + /** + * If this is non-null, this property holds the URI as it was before query + * stripping was performed. + */ + attribute nsIURI unstrippedURI; + + /** + * Propagated information from InterceptedHttpChannel + * It should be null when the channel is not created from FetchEvent.request + * or ServiceWorker NavigationPreload. + * nsIFetchEventInfo is C++ only, so it is not an attribute. + */ + [noscript, notxpcom, nostdcall, binaryname(InterceptionInfo)] + nsIInterceptionInfo binaryInterceptionInfo(); + + [noscript, notxpcom, nostdcall, binaryname(SetInterceptionInfo)] + void binarySetInterceptionInfo(in nsIInterceptionInfo info); + + /** + * Whether nsICookieInjector has injected a cookie for this request to + * handle a cookie banner. This is only done for top-level requests. + */ + [infallible] attribute boolean hasInjectedCookieForCookieBannerHandling; + + /** + * Whether the load has gone through the URL bar, where the fixup had to add * the protocol scheme. + */ + [infallible] attribute boolean wasSchemelessInput; +}; diff --git a/netwerk/base/nsIMIMEInputStream.idl b/netwerk/base/nsIMIMEInputStream.idl new file mode 100644 index 0000000000..1842cdb40c --- /dev/null +++ b/netwerk/base/nsIMIMEInputStream.idl @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIHttpHeaderVisitor.idl" +#include "nsIInputStream.idl" + +/** + * The MIME stream separates headers and a datastream. It also allows + * automatic creation of the content-length header. + */ + +[scriptable, builtinclass, uuid(dcbce63c-1dd1-11b2-b94d-91f6d49a3161)] +interface nsIMIMEInputStream : nsIInputStream +{ + /** + * Adds an additional header to the stream on the form "name: value". May + * not be called once the stream has been started to be read. + * @param name name of the header + * @param value value of the header + */ + void addHeader(in string name, in string value); + + /** + * Visits all headers which have been added via addHeader. Calling + * addHeader while visiting request headers has undefined behavior. + * + * @param aVisitor + * The header visitor instance. + */ + void visitHeaders(in nsIHttpHeaderVisitor visitor); + + /** + * Sets data-stream. May not be called once the stream has been started + * to be read. + * The cursor of the new stream should be located at the beginning of the + * stream if the implementation of the nsIMIMEInputStream also is used as + * an nsISeekableStream. + * @param stream stream containing the data for the stream + */ + void setData(in nsIInputStream stream); + + /** + * Get the wrapped data stream + */ + readonly attribute nsIInputStream data; +}; diff --git a/netwerk/base/nsIMultiPartChannel.idl b/netwerk/base/nsIMultiPartChannel.idl new file mode 100644 index 0000000000..3e4bf58812 --- /dev/null +++ b/netwerk/base/nsIMultiPartChannel.idl @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIChannel; + +/** + * An interface to access the the base channel + * associated with a MultiPartChannel. + */ + +[scriptable, builtinclass, uuid(4fefb490-5567-11e5-a837-0800200c9a66)] +interface nsIMultiPartChannel : nsISupports +{ + /** + * readonly attribute to access the underlying channel + */ + readonly attribute nsIChannel baseChannel; + + /** + * Attribute guaranteed to be different for different parts of + * the same multipart document. + */ + readonly attribute uint32_t partID; + + [noscript] readonly attribute boolean isFirstPart; + + /** + * Set to true when onStopRequest is received from the base channel. + * The listener can check this from its onStopRequest to determine + * whether more data can be expected. + */ + readonly attribute boolean isLastPart; +}; + +/** + * An interface that listeners can implement to receive a notification + * when the last part of the multi-part channel has finished, and the + * final OnStopRequest has been sent. + */ +[scriptable, uuid(b084959a-4fb9-41a5-88a0-d0f045ce75cf)] +interface nsIMultiPartChannelListener : nsISupports +{ + /** + * Sent when all parts have finished and sent OnStopRequest. + */ + void onAfterLastPart(in nsresult status); +}; diff --git a/netwerk/base/nsINestedURI.idl b/netwerk/base/nsINestedURI.idl new file mode 100644 index 0000000000..df124348af --- /dev/null +++ b/netwerk/base/nsINestedURI.idl @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +/** + * nsINestedURI is an interface that must be implemented by any nsIURI + * implementation which has an "inner" URI that it actually gets data + * from. + * + * For example, if URIs for the scheme "sanitize" have the structure: + * + * sanitize:http://example.com + * + * and opening a channel on such a sanitize: URI gets the data from + * http://example.com, sanitizes it, and returns it, then the sanitize: URI + * should implement nsINestedURI and return the http://example.com URI as its + * inner URI. + */ +[scriptable, builtinclass, uuid(6de2c874-796c-46bf-b57f-0d7bd7d6cab0)] +interface nsINestedURI : nsISupports +{ + /** + * The inner URI for this nested URI. This must not return null if the + * getter succeeds; URIs that have no inner must not QI to this interface. + * Dynamically changing whether there is an inner URI is not allowed. + * + * Modifying the returned URI must not in any way modify the nested URI; this + * means the returned URI must be either immutable or a clone. + */ + readonly attribute nsIURI innerURI; + + /** + * The innermost URI for this nested URI. This must not return null if the + * getter succeeds. This is equivalent to repeatedly calling innerURI while + * the returned URI QIs to nsINestedURI. + * + * Modifying the returned URI must not in any way modify the nested URI; this + * means the returned URI must be either immutable or a clone. + */ + readonly attribute nsIURI innermostURI; +}; + +[scriptable, builtinclass, uuid(ca3d6c03-4eee-4271-a97a-d16c0a0b2c5c)] +interface nsINestedURIMutator : nsISupports +{ + /* + * - Creates a new URI with the given innerURI. + */ + [must_use, noscript] void init(in nsIURI innerURI); +}; + +[scriptable, builtinclass, uuid(c6357a3b-c2bb-4b4b-9278-513377398a38)] +interface nsINestedAboutURIMutator : nsISupports +{ + /* + * - Creates a new URI with the given innerURI and base. + */ + [must_use, noscript] void initWithBase(in nsIURI innerURI, in nsIURI baseURI); +}; + +[scriptable, builtinclass, uuid(3bd44535-08ea-478f-99b9-85fa1084e820)] +interface nsIJSURIMutator : nsISupports +{ + /* + * - Inits the URI by setting the base URI + * - It is the caller's responsibility to also call SetSpec afterwards, + * otherwise we will return an incomplete URI (with only a base) + */ + [must_use, noscript] void setBase(in nsIURI aBaseURI); +}; diff --git a/netwerk/base/nsINetAddr.idl b/netwerk/base/nsINetAddr.idl new file mode 100644 index 0000000000..bbbcd28c0e --- /dev/null +++ b/netwerk/base/nsINetAddr.idl @@ -0,0 +1,88 @@ +/* vim: et ts=4 sw=4 tw=80 + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +namespace mozilla { +namespace net { +union NetAddr; +} +} +%} +native NetAddr(mozilla::net::NetAddr); + +/** + * nsINetAddr + * + * This interface represents a native NetAddr struct in a readonly + * interface. + */ +[scriptable, uuid(652B9EC5-D159-45D7-9127-50BB559486CD)] +interface nsINetAddr : nsISupports +{ + /** + * @return the address family of the network address, which corresponds to + * one of the FAMILY_ constants. + */ + readonly attribute unsigned short family; + + /** + * @return Either the IP address (FAMILY_INET, FAMILY_INET6) or the path + * (FAMILY_LOCAL) in string form. IP addresses are in the format produced by + * mozilla::net::NetAddr::ToStringBuffer. + * + * Note: Paths for FAMILY_LOCAL may have length limitations which are + * implementation dependent and not documented as part of this interface. + */ + readonly attribute AUTF8String address; + + /** + * @return the port number for a FAMILY_INET or FAMILY_INET6 address. + * + * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET or + * FAMILY_INET6. + */ + readonly attribute unsigned short port; + + /** + * @return the flow label for a FAMILY_INET6 address. + * + * @see http://www.ietf.org/rfc/rfc3697.txt + * + * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6 + */ + readonly attribute unsigned long flow; + + /** + * @return the address scope of a FAMILY_INET6 address. + * + * @see http://tools.ietf.org/html/rfc4007 + * + * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6 + */ + readonly attribute unsigned long scope; + + /** + * @return whether a FAMILY_INET6 address is mapped from FAMILY_INET. + * + * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6 + */ + readonly attribute boolean isV4Mapped; + + /** + * Network address families. These correspond to all the network address + * families supported by the NetAddr struct. + */ + const unsigned long FAMILY_INET = 1; + const unsigned long FAMILY_INET6 = 2; + const unsigned long FAMILY_LOCAL = 3; + + /** + * @return the underlying NetAddr struct. + */ + [noscript] NetAddr getNetAddr(); +}; diff --git a/netwerk/base/nsINetUtil.idl b/netwerk/base/nsINetUtil.idl new file mode 100644 index 0000000000..67e6c54f2b --- /dev/null +++ b/netwerk/base/nsINetUtil.idl @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; +interface nsIPrefBranch; + +/** + * nsINetUtil provides various network-related utility methods. + */ +[scriptable, uuid(fe2625ec-b884-4df1-b39c-9e830e47aa94)] +interface nsINetUtil : nsISupports +{ + /** + * Parse a Content-Type header value in strict mode. This is a more + * conservative parser that reject things that violate RFC 7231 section + * 3.1.1.1. This is typically useful for parsing Content-Type header values + * that are used for HTTP requests, and those that are used to make security + * decisions. + * + * @param aTypeHeader the header string to parse + * @param [out] aCharset the charset parameter specified in the + * header, if any. + * @param [out] aHadCharset whether a charset was explicitly specified. + * @return the MIME type specified in the header, in lower-case. + */ + AUTF8String parseRequestContentType(in AUTF8String aTypeHeader, + out AUTF8String aCharset, + out boolean aHadCharset); + + /** + * Parse a Content-Type header value in relaxed mode. This is a more + * permissive parser that ignores things that go against RFC 7231 section + * 3.1.1.1. This is typically useful for parsing Content-Type header values + * received from web servers where we want to make a best effort attempt + * at extracting a useful MIME type and charset. + * + * NOTE: DO NOT USE THIS if you're going to make security decisions + * based on the result. + * + * @param aTypeHeader the header string to parse + * @param [out] aCharset the charset parameter specified in the + * header, if any. + * @param [out] aHadCharset whether a charset was explicitly specified. + * @return the MIME type specified in the header, in lower-case. + */ + AUTF8String parseResponseContentType(in AUTF8String aTypeHeader, + out AUTF8String aCharset, + out boolean aHadCharset); + + /** + * Test whether the given URI's handler has the given protocol flags. + * + * @param aURI the URI in question + * @param aFlags the flags we're testing for. + * + * @return whether the protocol handler for aURI has all the flags + * in aFlags. + */ + boolean protocolHasFlags(in nsIURI aURI, in unsigned long aFlag); + + /** + * Test whether the protocol handler for this URI or that for any of + * its inner URIs has the given protocol flags. This will QI aURI to + * nsINestedURI and walk the nested URI chain. + * + * @param aURI the URI in question + * @param aFlags the flags we're testing for. + * + * @return whether any of the protocol handlers involved have all the flags + * in aFlags. + */ + boolean URIChainHasFlags(in nsIURI aURI, in unsigned long aFlags); + + /** Escape every character with its %XX-escaped equivalent */ + const unsigned long ESCAPE_ALL = 0; + + /** Leave alphanumeric characters intact and %XX-escape all others */ + const unsigned long ESCAPE_XALPHAS = 1; + + /** Leave alphanumeric characters intact, convert spaces to '+', + %XX-escape all others */ + const unsigned long ESCAPE_XPALPHAS = 2; + + /** Leave alphanumeric characters and forward slashes intact, + %XX-escape all others */ + const unsigned long ESCAPE_URL_PATH = 4; + + /** Additional encoding for Apple's NSURL compatibility. + Like XALPHAS, but leave '%' to avoid double encoding, '+', and '/'. + %XX-escape all others */ + const unsigned long ESCAPE_URL_APPLE_EXTRA = 8; + + /** + * escape a string with %00-style escaping + */ + ACString escapeString(in ACString aString, in unsigned long aEscapeType); + + /** %XX-escape URL scheme */ + const unsigned long ESCAPE_URL_SCHEME = 1; + + /** %XX-escape username in the URL */ + const unsigned long ESCAPE_URL_USERNAME = 1 << 1; + + /** %XX-escape password in the URL */ + const unsigned long ESCAPE_URL_PASSWORD = 1 << 2; + + /** %XX-escape URL host */ + const unsigned long ESCAPE_URL_HOST = 1 << 3; + + /** %XX-escape URL directory */ + const unsigned long ESCAPE_URL_DIRECTORY = 1 << 4; + + /** %XX-escape file basename in the URL */ + const unsigned long ESCAPE_URL_FILE_BASENAME = 1 << 5; + + /** %XX-escape file extension in the URL */ + const unsigned long ESCAPE_URL_FILE_EXTENSION = 1 << 6; + + /** %XX-escape URL parameters */ + const unsigned long ESCAPE_URL_PARAM = 1 << 7; + + /** %XX-escape URL query */ + const unsigned long ESCAPE_URL_QUERY = 1 << 8; + + /** %XX-escape URL ref */ + const unsigned long ESCAPE_URL_REF = 1 << 9; + + /** %XX-escape URL path - same as escaping directory, basename and extension */ + const unsigned long ESCAPE_URL_FILEPATH = + ESCAPE_URL_DIRECTORY | ESCAPE_URL_FILE_BASENAME | ESCAPE_URL_FILE_EXTENSION; + + /** %XX-escape scheme, username, password, host, path, params, query and ref */ + const unsigned long ESCAPE_URL_MINIMAL = + ESCAPE_URL_SCHEME | ESCAPE_URL_USERNAME | ESCAPE_URL_PASSWORD | + ESCAPE_URL_HOST | ESCAPE_URL_FILEPATH | ESCAPE_URL_PARAM | + ESCAPE_URL_QUERY | ESCAPE_URL_REF; + + /** Force %XX-escaping of already escaped sequences */ + const unsigned long ESCAPE_URL_FORCED = 1 << 10; + + /** Skip non-ascii octets, %XX-escape all others */ + const unsigned long ESCAPE_URL_ONLY_ASCII = 1 << 11; + + /** + * Skip graphic octets (0x20-0x7E) when escaping + * Skips all ASCII octets (0x00-0x7F) when unescaping + */ + const unsigned long ESCAPE_URL_ONLY_NONASCII = 1 << 12; + + /** Force %XX-escape of colon */ + const unsigned long ESCAPE_URL_COLON = 1 << 14; + + /** Skip C0 and DEL from unescaping */ + const unsigned long ESCAPE_URL_SKIP_CONTROL = 1 << 15; + + /** %XX-escape external protocol handler URL */ + const unsigned long ESCAPE_URL_EXT_HANDLER = 1 << 17; + + /** + * %XX-Escape invalid chars in a URL segment. + * + * @param aStr the URL to be escaped + * @param aFlags the URL segment type flags + * + * @return the escaped string (the string itself if escaping did not happen) + * + */ + ACString escapeURL(in ACString aStr, in unsigned long aFlags); + + /** + * Expands URL escape sequences + * + * @param aStr the URL to be unescaped + * @param aFlags only ESCAPE_URL_ONLY_NONASCII and ESCAPE_URL_SKIP_CONTROL + * are recognized. If |aFlags| is 0 all escape sequences are + * unescaped + * @return unescaped string + */ + ACString unescapeString(in AUTF8String aStr, in unsigned long aFlags); + + /** + * Extract the charset parameter location and value from a content-type + * header. + * + * @param aTypeHeader the header string to parse + * @param [out] aCharset the charset parameter specified in the + * header, if any. + * @param [out] aCharsetStart index of the start of the charset parameter + * (the ';' separating it from what came before) in aTypeHeader. + * If this function returns false, this argument will still be + * set, to the index of the location where a new charset should + * be inserted. + * @param [out] aCharsetEnd index of the end of the charset parameter (the + * ';' separating it from what comes after, or the end + * of the string) in aTypeHeader. If this function returns + * false, this argument will still be set, to the index of the + * location where a new charset should be inserted. + * + * @return whether a charset parameter was found. This can be false even in + * cases when parseContentType would claim to have a charset, if the type + * that won out does not have a charset parameter specified. + */ + boolean extractCharsetFromContentType(in AUTF8String aTypeHeader, + out AUTF8String aCharset, + out long aCharsetStart, + out long aCharsetEnd); + + /** + * This is test-only. Send an IPC message to let socket process send a + * telemetry. + */ + void socketProcessTelemetryPing(); + + /** + * This is a void method that is C++ implemented and always + * returns NS_ERROR_NOT_IMPLEMENTED. To be used for testing. + */ + void notImplemented(); +}; diff --git a/netwerk/base/nsINetworkConnectivityService.idl b/netwerk/base/nsINetworkConnectivityService.idl new file mode 100644 index 0000000000..482eaf45ee --- /dev/null +++ b/netwerk/base/nsINetworkConnectivityService.idl @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, builtinclass, uuid(2693457e-3ba5-4455-991f-5350946adb12)] +interface nsINetworkConnectivityService : nsISupports +{ + /** + * Each tested feature may be in one of 3 states: + * UNKNOWN, if a check hasn't been performed. + * OK, if the feature was successfully tested + * NOT_AVAILABLE, if the feature is blocked by the network. + * Note that the endpoints are guaranteed to support the features. + */ + cenum ConnectivityState: 32 { + UNKNOWN = 0, + OK = 1, + NOT_AVAILABLE = 2 + }; + + /* If DNS v4/v6 queries actually work on the current network */ + [infallible] + readonly attribute nsINetworkConnectivityService_ConnectivityState DNSv4; + [infallible] + readonly attribute nsINetworkConnectivityService_ConnectivityState DNSv6; + + /* If connecting to IPv4/v6 works on the current network */ + [infallible] + readonly attribute nsINetworkConnectivityService_ConnectivityState IPv4; + [infallible] + readonly attribute nsINetworkConnectivityService_ConnectivityState IPv6; + + /* If a NAT64 gateway was detected on the current network */ + [infallible] + readonly attribute nsINetworkConnectivityService_ConnectivityState NAT64; + + /* Starts the DNS request to check for DNS v4/v6 availability */ + void recheckDNS(); + + /* Starts HTTP requests over IPv4 and IPv6, and checks if they work */ + void recheckIPConnectivity(); +}; diff --git a/netwerk/base/nsINetworkInfoService.idl b/netwerk/base/nsINetworkInfoService.idl new file mode 100644 index 0000000000..9349abe13f --- /dev/null +++ b/netwerk/base/nsINetworkInfoService.idl @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Listener for getting list of addresses. + */ +[scriptable, uuid(c4bdaac1-3ab1-4fdb-9a16-17cbed794603)] +interface nsIListNetworkAddressesListener : nsISupports +{ + /** + * Callback function that gets called by nsINetworkInfoService.listNetworkAddresses. + * Each address in the array is a string IP address in canonical form, + * e.g. 192.168.1.10, or an IPV6 address in string form. + */ + void onListedNetworkAddresses(in Array<ACString> aAddressArray); + void onListNetworkAddressesFailed(); +}; + +/** + * Listener for getting hostname. + */ +[scriptable, uuid(3ebdcb62-2df4-4042-8864-3fa81abd4693)] +interface nsIGetHostnameListener : nsISupports +{ + void onGotHostname(in AUTF8String aHostname); + void onGetHostnameFailed(); +}; + +/** + * Service information + */ +[scriptable, uuid(55fc8dae-4a58-4e0f-a49b-901cbabae809)] +interface nsINetworkInfoService : nsISupports +{ + /** + * Obtain a list of local machine network addresses. The listener object's + * onListedNetworkAddresses will be called with the obtained addresses. + * On failure, the listener object's onListNetworkAddressesFailed() will be called. + */ + void listNetworkAddresses(in nsIListNetworkAddressesListener aListener); + + /** + * Obtain the hostname of the local machine. The listener object's + * onGotHostname will be called with the obtained hostname. + * On failure, the listener object's onGetHostnameFailed() will be called. + */ + void getHostname(in nsIGetHostnameListener aListener); +}; + +%{ C++ +#define NETWORKINFOSERVICE_CONTRACT_ID \ + "@mozilla.org/network-info-service;1" +%} diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl new file mode 100644 index 0000000000..155daa5cd5 --- /dev/null +++ b/netwerk/base/nsINetworkInterceptController.idl @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIContentPolicy.idl" + +interface nsICacheInfoChannel; +interface nsIChannel; +interface nsIConsoleReportCollector; +interface nsIInputStream; +interface nsIOutputStream; +interface nsIURI; + +%{C++ +#include "nsContentUtils.h" +#include "nsIChannel.h" +#include "nsIConsoleReportCollector.h" +#include "nsILoadInfo.h" +namespace mozilla { +class TimeStamp; + +namespace dom { +class ChannelInfo; +} +} +%} + +native TimeStamp(mozilla::TimeStamp); + +[ptr] native ChannelInfo(mozilla::dom::ChannelInfo); + +/** + * Interface allowing the nsIInterceptedChannel to callback when it is + * done reading from the body stream. + */ +[scriptable, uuid(51039eb6-bea0-40c7-b523-ccab56cc4fde)] +interface nsIInterceptedBodyCallback : nsISupports +{ + void bodyComplete(in nsresult aRv); +}; + +/** + * Interface to allow implementors of nsINetworkInterceptController to control the behaviour + * of intercepted channels without tying implementation details of the interception to + * the actual channel. nsIInterceptedChannel is expected to be implemented by objects + * which do not implement nsIChannel. + */ + +[scriptable, uuid(f4b82975-6a86-4cc4-87fe-9a1fd430c86d)] +interface nsIInterceptedChannel : nsISupports +{ + /** + * Instruct a channel that has been intercepted to continue with the original + * network request. + * + * For our mitigations, we support this reset triggering "bypass" mode which + * results in the resulting client not being controlled. + */ + void resetInterception(in boolean bypass); + + /** + * Set the status and reason for the forthcoming synthesized response. + * Multiple calls overwrite existing values. + */ + void synthesizeStatus(in uint16_t status, in ACString reason); + + /** + * Attach a header name/value pair to the forthcoming synthesized response. + * Overwrites any existing header value. + */ + void synthesizeHeader(in ACString name, in ACString value); + + /** + * Instruct a channel that has been intercepted that a response is + * starting to be synthesized. No further header modification is allowed + * after this point. There are a few parameters: + * - A body stream may be optionally passed. If nullptr, then an + * empty body is assumed. + * - A callback may be optionally passed. It will be invoked + * when the body is complete. For a nullptr body this may be + * synchronously on the current thread. Otherwise it will be invoked + * asynchronously on the current thread. + * - A cacheInfoChannel may be optionally passed. If the body stream is + * from alternative data cache, this cacheInfoChannel provides needed + * cache information. + * - The caller may optionally pass a spec for a URL that this response + * originates from; an empty string will cause the original + * intercepted request's URL to be used instead. + * - The responseRedirected flag is false will cause the channel do an + * internal redirect when the original intercepted reauest's URL is + * different from the response's URL. The flag is true will cause the + * chaanel do a non-internal redirect when the URLs are different. + */ + void startSynthesizedResponse(in nsIInputStream body, + in nsIInterceptedBodyCallback callback, + in nsICacheInfoChannel channel, + in ACString finalURLSpec, + in bool responseRedirected); + + /** + * Instruct a channel that has been intercepted that response synthesis + * has completed and all outstanding resources can be closed. + */ + void finishSynthesizedResponse(); + + /** + * Cancel the pending intercepted request. + * @return NS_ERROR_FAILURE if the response has already been synthesized or + * the original request has been instructed to continue. + */ + void cancelInterception(in nsresult status); + + /** + * The underlying channel object that was intercepted. + */ + readonly attribute nsIChannel channel; + + /** + * The URL of the underlying channel object, corrected for a potential + * secure upgrade. + */ + readonly attribute nsIURI secureUpgradedChannelURI; + + /** + * This method allows to override the channel info for the channel. + */ + [noscript] + void setChannelInfo(in ChannelInfo channelInfo); + + /** + * Get the internal load type from the underlying channel. + */ + [noscript] + readonly attribute nsContentPolicyType internalContentPolicyType; + + [noscript] + readonly attribute nsIConsoleReportCollector consoleReportCollector; + + [noscript] + void SetFetchHandlerStart(in TimeStamp aTimeStamp); + + [noscript] + void SetFetchHandlerFinish(in TimeStamp aTimeStamp); + + [noscript] + void SetRemoteWorkerLaunchStart(in TimeStamp aTimeStamp); + + [noscript] + void SetRemoteWorkerLaunchEnd(in TimeStamp aTimeStamp); + + /** + * This method indicates if the ServiceWorker Interception is reset to + * network or not. + */ + [noscript] + bool GetIsReset(); + +%{C++ + already_AddRefed<nsIConsoleReportCollector> + GetConsoleReportCollector() + { + nsCOMPtr<nsIConsoleReportCollector> reporter; + GetConsoleReportCollector(getter_AddRefs(reporter)); + return reporter.forget(); + } + + void + GetSubresourceTimeStampKey(nsIChannel* aChannel, nsACString& aKey) + { + if (!nsContentUtils::IsNonSubresourceRequest(aChannel)) { + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + switch(loadInfo->InternalContentPolicyType()) { + case nsIContentPolicy::TYPE_SCRIPT: + case nsIContentPolicy::TYPE_INTERNAL_SCRIPT: + case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD: + case nsIContentPolicy::TYPE_INTERNAL_MODULE: + case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD: + case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS: + case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE: { + aKey = "subresource-script"_ns; + break; + } + case nsIContentPolicy::TYPE_IMAGE: + case nsIContentPolicy::TYPE_INTERNAL_IMAGE: + case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD: + case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON: { + aKey = "subresource-image"_ns; + break; + } + case nsIContentPolicy::TYPE_STYLESHEET: + case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET: + case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD: { + aKey = "subresource-stylesheet"_ns; + break; + } + default: { + aKey = "subresource-other"_ns; + break; + } + } + } + } + + bool + IsReset() + { + bool result; + GetIsReset(&result); + return result; + } +%} + + /** + * Allow the ServiceWorkerManager to set an RAII-style object on the + * intercepted channel that should be released once the channel is + * torn down. + */ + [noscript] + void setReleaseHandle(in nsISupports aHandle); +}; + +/** + * Interface to allow consumers to attach themselves to a channel's + * notification callbacks/loadgroup and determine if a given channel + * request should be intercepted before any network request is initiated. + */ + +[scriptable, uuid(70d2b4fe-a552-48cd-8d93-1d8437a56b53)] +interface nsINetworkInterceptController : nsISupports +{ + /** + * Returns true if a channel should avoid initiating any network + * requests until specifically instructed to do so. + * + * @param aURI The URI to be loaded. Note, this may differ from + * the channel's current URL in some cases. + * @param aChannel The channel that may be intercepted. It will + * be in the state prior to calling OnStartRequest(). + */ + bool shouldPrepareForIntercept(in nsIURI aURI, in nsIChannel aChannel); + + /** + * Notification when a given intercepted channel is prepared to accept a synthesized + * response via the provided stream. + * + * @param aChannel the controlling interface for a channel that has been intercepted + */ + void channelIntercepted(in nsIInterceptedChannel aChannel); +}; diff --git a/netwerk/base/nsINetworkLinkService.idl b/netwerk/base/nsINetworkLinkService.idl new file mode 100644 index 0000000000..29c7fab836 --- /dev/null +++ b/netwerk/base/nsINetworkLinkService.idl @@ -0,0 +1,154 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsINetAddr; + +%{ C++ +#include "nsTArrayForwardDeclare.h" +namespace mozilla { +namespace net { +union NetAddr; +} +} +%} +native NetAddr(mozilla::net::NetAddr); + +/** + * Network link status monitoring service. + */ +[scriptable, uuid(103e5293-77b3-4b70-af59-6e9e4a1f994a)] +interface nsINetworkLinkService : nsISupports +{ + /* Link type constants */ + const unsigned long LINK_TYPE_UNKNOWN = 0; + const unsigned long LINK_TYPE_ETHERNET = 1; + const unsigned long LINK_TYPE_USB = 2; + const unsigned long LINK_TYPE_WIFI = 3; + const unsigned long LINK_TYPE_WIMAX = 4; + const unsigned long LINK_TYPE_MOBILE = 9; + + /** + * This is set to true when the system is believed to have a usable + * network connection. + * + * The link is only up when network connections can be established. For + * example, the link is down during DHCP configuration (unless there + * is another usable interface already configured). + * + * If the link status is not currently known, we generally assume that + * it is up. + */ + readonly attribute boolean isLinkUp; + + /** + * This is set to true when we believe that isLinkUp is accurate. + */ + readonly attribute boolean linkStatusKnown; + + /** + * The type of network connection. + */ + readonly attribute unsigned long linkType; + + /** + * A string uniquely identifying the current active network interfaces. + * Empty when there are no active network interfaces. + */ + readonly attribute ACString networkID; + + /** + * The list of DNS suffixes for the currently active network interfaces. + */ + readonly attribute Array<ACString> dnsSuffixList; + + /** + * The IPs of the DNS resolvers currently used by the platform. + */ + [noscript] readonly attribute Array<NetAddr> nativeResolvers; + + /** + * Same as previous - returns the IPs of DNS resolvers but this time as + * XPCOM objects usable by extensions. + */ + readonly attribute Array<nsINetAddr> resolvers; + + const unsigned long NONE_DETECTED = 0; + const unsigned long VPN_DETECTED = 1 << 0; + const unsigned long PROXY_DETECTED = 1 << 1; + const unsigned long NRPT_DETECTED = 1 << 2; + + /** + * A bitfield that encodes the platform attributes we detected which + * indicate that we should only use DNS, not TRR. + */ + readonly attribute unsigned long platformDNSIndications; +}; + +%{C++ +/** + * We send notifications through nsIObserverService with topic + * NS_NETWORK_LINK_TOPIC whenever one of isLinkUp or linkStatusKnown + * changes. We pass one of the NS_NETWORK_LINK_DATA_ constants below + * as the aData parameter of the notification. + */ +#define NS_NETWORK_LINK_TOPIC "network:link-status-changed" + +/** + * isLinkUp is now true, linkStatusKnown is true. + */ +#define NS_NETWORK_LINK_DATA_UP "up" +/** + * isLinkUp is now false, linkStatusKnown is true. + */ +#define NS_NETWORK_LINK_DATA_DOWN "down" +/** + * isLinkUp is still true, but the network setup is modified. + * linkStatusKnown is true. + */ +#define NS_NETWORK_LINK_DATA_CHANGED "changed" +/** + * linkStatusKnown is now false. + */ +#define NS_NETWORK_LINK_DATA_UNKNOWN "unknown" + +/** + * network ID has changed. + */ +#define NS_NETWORK_ID_CHANGED_TOPIC "network:networkid-changed" + +/** + * DNS suffix list has updated. + */ +#define NS_DNS_SUFFIX_LIST_UPDATED_TOPIC "network:dns-suffix-list-updated" + +/** + * We send notifications through nsIObserverService with topic + * NS_NETWORK_LINK_TYPE_TOPIC whenever the network connection type + * changes. We pass one of the valid connection type constants + * below as the aData parameter of the notification. + */ +#define NS_NETWORK_LINK_TYPE_TOPIC "network:link-type-changed" + +/** We were unable to determine the network connection type */ +#define NS_NETWORK_LINK_TYPE_UNKNOWN "unknown" + +/** A standard wired ethernet connection */ +#define NS_NETWORK_LINK_TYPE_ETHERNET "ethernet" + +/** A connection via a USB port */ +#define NS_NETWORK_LINK_TYPE_USB "usb" + +/** A connection via a WiFi access point (IEEE802.11) */ +#define NS_NETWORK_LINK_TYPE_WIFI "wifi" + +/** A connection via WiMax (IEEE802.16) */ +#define NS_NETWORK_LINK_TYPE_WIMAX "wimax" + +/** A mobile connection (e.g. 2G, 3G, etc) */ +#define NS_NETWORK_LINK_TYPE_MOBILE "mobile" +%} diff --git a/netwerk/base/nsINetworkPredictor.idl b/netwerk/base/nsINetworkPredictor.idl new file mode 100644 index 0000000000..07f88cba8d --- /dev/null +++ b/netwerk/base/nsINetworkPredictor.idl @@ -0,0 +1,194 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; +interface nsINetworkPredictorVerifier; + +webidl Document; + +typedef unsigned long PredictorPredictReason; +typedef unsigned long PredictorLearnReason; + +%{C++ +namespace mozilla { + +class OriginAttributes; + +} +%} + +[ref] native OriginAttributes(const mozilla::OriginAttributes); + +/** + * nsINetworkPredictor - learn about pages users visit, and allow us to take + * predictive actions upon future visits. + * NOTE: nsINetworkPredictor should only + * be used on the main thread. + */ +[scriptable, builtinclass, uuid(acc88e7c-3f39-42c7-ac31-6377c2c3d73e)] +interface nsINetworkPredictor : nsISupports +{ + /** + * Prediction reasons + * + * PREDICT_LINK - we are being asked to take predictive action because + * the user is hovering over a link. + * + * PREDICT_LOAD - we are being asked to take predictive action because + * the user has initiated a pageload. + * + * PREDICT_STARTUP - we are being asked to take predictive action + * because the browser is starting up. + */ + const PredictorPredictReason PREDICT_LINK = 0; + const PredictorPredictReason PREDICT_LOAD = 1; + const PredictorPredictReason PREDICT_STARTUP = 2; + + /** + * Start taking predictive actions + * + * Calling this will cause the predictor to (possibly) start + * taking actions such as DNS prefetch and/or TCP preconnect based on + * (1) the host name that we are given, and (2) the reason we are being + * asked to take actions. + * + * @param targetURI - The URI we are being asked to take actions based on. + * @param sourceURI - The URI that is currently loaded. This is so we can + * avoid doing predictive actions for link hover on an HTTPS page (for + * example). + * @param reason - The reason we are being asked to take actions. Can be + * any of the PREDICT_* values above. + * In the case of PREDICT_LINK, targetURI should be the URI of the link + * that is being hovered over, and sourceURI should be the URI of the page + * on which the link appears. + * In the case of PREDICT_LOAD, targetURI should be the URI of the page that + * is being loaded and sourceURI should be null. + * In the case of PREDICT_STARTUP, both targetURI and sourceURI should be + * null. + * @param originAttributes - The originAttributes of the page load we are + * predicting about. + * @param verifier - An nsINetworkPredictorVerifier used in testing to ensure + * we're predicting the way we expect to. Not necessary (or desired) for + * normal operation. + */ + [implicit_jscontext] + void predict(in nsIURI targetURI, + in nsIURI sourceURI, + in PredictorPredictReason reason, + in jsval originAttributes, + in nsINetworkPredictorVerifier verifier); + + [notxpcom] + nsresult predictNative(in nsIURI targetURI, + in nsIURI sourceURI, + in PredictorPredictReason reason, + in OriginAttributes originAttributes, + in nsINetworkPredictorVerifier verifier); + + + /* + * Reasons we are learning something + * + * LEARN_LOAD_TOPLEVEL - we are learning about the toplevel resource of a + * pageload (NOTE: this should ONLY be used by tests) + * + * LEARN_LOAD_SUBRESOURCE - we are learning a subresource from a pageload + * + * LEARN_LOAD_REDIRECT - we are learning about the re-direct of a URI + * + * LEARN_STARTUP - we are learning about a page loaded during startup + */ + const PredictorLearnReason LEARN_LOAD_TOPLEVEL = 0; + const PredictorLearnReason LEARN_LOAD_SUBRESOURCE = 1; + const PredictorLearnReason LEARN_LOAD_REDIRECT = 2; + const PredictorLearnReason LEARN_STARTUP = 3; + + /** + * Add to our compendium of knowledge + * + * This adds to our prediction database to make things (hopefully) + * smarter next time we predict something. + * + * @param targetURI - The URI that was loaded that we are keeping track of. + * @param sourceURI - The URI that caused targetURI to be loaded (for page + * loads). This means the DOCUMENT URI. + * @param reason - The reason we are learning this bit of knowledge. + * Reason can be any of the LEARN_* values. + * In the case of LEARN_LOAD_SUBRESOURCE, targetURI should be the URI of a + * subresource of a page, and sourceURI should be the top-level URI. + * In the case of LEARN_LOAD_REDIRECT, targetURI is the NEW URI of a + * top-level resource that was redirected to, and sourceURI is the + * ORIGINAL URI of said top-level resource. + * In the case of LEARN_STARTUP, targetURI should be the URI of a page + * that was loaded immediately after browser startup, and sourceURI should + * be null. + * @param originAttributes - The originAttributes for the page load that we + * are learning about. + */ + [implicit_jscontext] + void learn(in nsIURI targetURI, + in nsIURI sourceURI, + in PredictorLearnReason reason, + in jsval originAttributes); + + [notxpcom] + nsresult learnNative(in nsIURI targetURI, + in nsIURI sourceURI, + in PredictorLearnReason reason, + in OriginAttributes originAttributes); + + /** + * Clear out all our learned knowledge + * + * This removes everything from our database so that any predictions begun + * after this completes will start from a blank slate. + */ + void reset(); +}; + +%{C++ +// Wrapper functions to make use of the predictor easier and less invasive +class nsIChannel; + +class nsILoadContext; +class nsILoadGroup; +class nsINetworkPredictorVerifier; + +namespace mozilla { + +class OriginAttributes; + +namespace net { + +nsresult PredictorPredict(nsIURI *targetURI, + nsIURI *sourceURI, + PredictorPredictReason reason, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier *verifier); + +nsresult PredictorLearn(nsIURI *targetURI, + nsIURI *sourceURI, + PredictorLearnReason reason, + const OriginAttributes& originAttributes); + +nsresult PredictorLearn(nsIURI *targetURI, + nsIURI *sourceURI, + PredictorLearnReason reason, + nsILoadGroup *loadGroup); + +nsresult PredictorLearn(nsIURI *targetURI, + nsIURI *sourceURI, + PredictorLearnReason reason, + dom::Document *document); + +nsresult PredictorLearnRedirect(nsIURI *targetURI, + nsIChannel *channel, + const OriginAttributes& originAttributes); + +} // mozilla::net +} // mozilla +%} diff --git a/netwerk/base/nsINetworkPredictorVerifier.idl b/netwerk/base/nsINetworkPredictorVerifier.idl new file mode 100644 index 0000000000..b00aecc076 --- /dev/null +++ b/netwerk/base/nsINetworkPredictorVerifier.idl @@ -0,0 +1,40 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * nsINetworkPredictorVerifier - used for testing the network predictor to + * ensure it does what we expect it to do. + */ + +#include "nsISupports.idl" + +interface nsIURI; + +[scriptable, uuid(2e43bb32-dabf-4494-9f90-2b3195b1c73d)] +interface nsINetworkPredictorVerifier : nsISupports +{ + /** + * Callback for when we do a predictive prefetch + * + * @param uri - The URI that was prefetched + * @param status - The request status code returned by the + * prefetch attempt e.g. 200 (OK):w + */ + void onPredictPrefetch(in nsIURI uri, in uint32_t status); + + /** + * Callback for when we do a predictive preconnect + * + * @param uri - The URI that was preconnected to + */ + void onPredictPreconnect(in nsIURI uri); + + /** + * Callback for when we do a predictive DNS lookup + * + * @param uri - The URI that was looked up + */ + void onPredictDNS(in nsIURI uri); +}; diff --git a/netwerk/base/nsINullChannel.idl b/netwerk/base/nsINullChannel.idl new file mode 100644 index 0000000000..6c03a2743b --- /dev/null +++ b/netwerk/base/nsINullChannel.idl @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface is only used in order to mark the fact that + * an object isn't a complete implementation of its interfaces. + * For example, a consumer can QI NullHttpChannel to nsINullChannel, + * to determine if the object is just a dummy implementation of nsIHttpChannel. + */ +[scriptable, builtinclass, uuid(4610b901-df41-4bb4-bd3f-fd4d6b6d8d68)] +interface nsINullChannel : nsISupports +{ +}; diff --git a/netwerk/base/nsIOService.cpp b/netwerk/base/nsIOService.cpp new file mode 100644 index 0000000000..8450a59b4a --- /dev/null +++ b/netwerk/base/nsIOService.cpp @@ -0,0 +1,2194 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 cindent et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" + +#include "nsIOService.h" +#include "nsIProtocolHandler.h" +#include "nsIFileProtocolHandler.h" +#include "nscore.h" +#include "nsIURI.h" +#include "prprf.h" +#include "netCore.h" +#include "nsIObserverService.h" +#include "nsXPCOM.h" +#include "nsIProxiedProtocolHandler.h" +#include "nsIProxyInfo.h" +#include "nsDNSService2.h" +#include "nsEscape.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "nsCRT.h" +#include "nsSimpleNestedURI.h" +#include "nsSocketTransport2.h" +#include "nsTArray.h" +#include "nsIConsoleService.h" +#include "nsIUploadChannel2.h" +#include "nsXULAppAPI.h" +#include "nsIProtocolProxyCallback.h" +#include "nsICancelable.h" +#include "nsINetworkLinkService.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsURLHelper.h" +#include "nsIProtocolProxyService2.h" +#include "MainThreadUtils.h" +#include "nsINode.h" +#include "nsIWebTransport.h" +#include "nsIWidget.h" +#include "nsThreadUtils.h" +#include "WebTransportSessionProxy.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/net/DNS.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/NeckoParent.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/nsHTTPSOnlyUtils.h" +#include "mozilla/dom/ServiceWorkerDescriptor.h" +#include "mozilla/net/CaptivePortalService.h" +#include "mozilla/net/NetworkConnectivityService.h" +#include "mozilla/net/SocketProcessHost.h" +#include "mozilla/net/SocketProcessParent.h" +#include "mozilla/net/SSLTokensCache.h" +#include "mozilla/Unused.h" +#include "nsContentSecurityManager.h" +#include "nsContentUtils.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPrefs_security.h" +#include "nsNSSComponent.h" +#include "ssl.h" +#include "StaticComponents.h" + +namespace mozilla { +namespace net { + +using mozilla::Maybe; +using mozilla::dom::ClientInfo; +using mozilla::dom::ServiceWorkerDescriptor; + +#define PORT_PREF_PREFIX "network.security.ports." +#define PORT_PREF(x) PORT_PREF_PREFIX x +#define MANAGE_OFFLINE_STATUS_PREF "network.manage-offline-status" + +// Nb: these have been misnomers since bug 715770 removed the buffer cache. +// "network.segment.count" and "network.segment.size" would be better names, +// but the old names are still used to preserve backward compatibility. +#define NECKO_BUFFER_CACHE_COUNT_PREF "network.buffer.cache.count" +#define NECKO_BUFFER_CACHE_SIZE_PREF "network.buffer.cache.size" +#define NETWORK_CAPTIVE_PORTAL_PREF "network.captive-portal-service.enabled" +#define WEBRTC_PREF_PREFIX "media.peerconnection." +#define NETWORK_DNS_PREF "network.dns." +#define FORCE_EXTERNAL_PREF_PREFIX "network.protocol-handler.external." + +nsIOService* gIOService; +static bool gHasWarnedUploadChannel2; +static bool gCaptivePortalEnabled = false; +static LazyLogModule gIOServiceLog("nsIOService"); +#undef LOG +#define LOG(args) MOZ_LOG(gIOServiceLog, LogLevel::Debug, args) + +// A general port blacklist. Connections to these ports will not be allowed +// unless the protocol overrides. +// +// This list is to be kept in sync with "bad ports" as defined in the +// WHATWG Fetch standard at <https://fetch.spec.whatwg.org/#port-blocking> + +int16_t gBadPortList[] = { + 1, // tcpmux + 7, // echo + 9, // discard + 11, // systat + 13, // daytime + 15, // netstat + 17, // qotd + 19, // chargen + 20, // ftp-data + 21, // ftp + 22, // ssh + 23, // telnet + 25, // smtp + 37, // time + 42, // name + 43, // nicname + 53, // domain + 69, // tftp + 77, // priv-rjs + 79, // finger + 87, // ttylink + 95, // supdup + 101, // hostriame + 102, // iso-tsap + 103, // gppitnp + 104, // acr-nema + 109, // pop2 + 110, // pop3 + 111, // sunrpc + 113, // auth + 115, // sftp + 117, // uucp-path + 119, // nntp + 123, // ntp + 135, // loc-srv / epmap + 137, // netbios + 139, // netbios + 143, // imap2 + 161, // snmp + 179, // bgp + 389, // ldap + 427, // afp (alternate) + 465, // smtp (alternate) + 512, // print / exec + 513, // login + 514, // shell + 515, // printer + 526, // tempo + 530, // courier + 531, // chat + 532, // netnews + 540, // uucp + 548, // afp + 554, // rtsp + 556, // remotefs + 563, // nntp+ssl + 587, // smtp (outgoing) + 601, // syslog-conn + 636, // ldap+ssl + 989, // ftps-data + 990, // ftps + 993, // imap+ssl + 995, // pop3+ssl + 1719, // h323gatestat + 1720, // h323hostcall + 1723, // pptp + 2049, // nfs + 3659, // apple-sasl + 4045, // lockd + 5060, // sip + 5061, // sips + 6000, // x11 + 6566, // sane-port + 6665, // irc (alternate) + 6666, // irc (alternate) + 6667, // irc (default) + 6668, // irc (alternate) + 6669, // irc (alternate) + 6697, // irc+tls + 10080, // amanda + 0, // Sentinel value: This MUST be zero +}; + +static const char kProfileChangeNetTeardownTopic[] = + "profile-change-net-teardown"; +static const char kProfileChangeNetRestoreTopic[] = + "profile-change-net-restore"; +static const char kProfileDoChange[] = "profile-do-change"; + +// Necko buffer defaults +uint32_t nsIOService::gDefaultSegmentSize = 4096; +uint32_t nsIOService::gDefaultSegmentCount = 24; + +uint32_t nsIOService::sSocketProcessCrashedCount = 0; + +//////////////////////////////////////////////////////////////////////////////// + +nsIOService::nsIOService() + : mLastOfflineStateChange(PR_IntervalNow()), + mLastConnectivityChange(PR_IntervalNow()), + mLastNetworkLinkChange(PR_IntervalNow()) {} + +static const char* gCallbackPrefs[] = { + PORT_PREF_PREFIX, + MANAGE_OFFLINE_STATUS_PREF, + NECKO_BUFFER_CACHE_COUNT_PREF, + NECKO_BUFFER_CACHE_SIZE_PREF, + NETWORK_CAPTIVE_PORTAL_PREF, + FORCE_EXTERNAL_PREF_PREFIX, + nullptr, +}; + +static const char* gCallbackPrefsForSocketProcess[] = { + WEBRTC_PREF_PREFIX, + NETWORK_DNS_PREF, + "network.send_ODA_to_content_directly", + "network.trr.", + "doh-rollout.", + "network.dns.disableIPv6", + "network.offline-mirrors-connectivity", + "network.disable-localhost-when-offline", + "network.proxy.parse_pac_on_socket_process", + "network.proxy.allow_hijacking_localhost", + "network.connectivity-service.", + "network.captive-portal-service.testMode", + nullptr, +}; + +static const char* gCallbackSecurityPrefs[] = { + // Note the prefs listed below should be in sync with the code in + // HandleTLSPrefChange(). + "security.tls.version.min", + "security.tls.version.max", + "security.tls.version.enable-deprecated", + "security.tls.hello_downgrade_check", + "security.ssl.require_safe_negotiation", + "security.ssl.enable_false_start", + "security.ssl.enable_alpn", + "security.tls.enable_0rtt_data", + "security.ssl.disable_session_identifiers", + "security.tls.enable_post_handshake_auth", + "security.tls.enable_delegated_credentials", + // Note the prefs listed below should be in sync with the code in + // SetValidationOptionsCommon(). + "security.ssl.enable_ocsp_stapling", + "security.ssl.enable_ocsp_must_staple", + "security.pki.certificate_transparency.mode", + nullptr, +}; + +nsresult nsIOService::Init() { + SSLTokensCache::Init(); + + InitializeCaptivePortalService(); + + // setup our bad port list stuff + for (int i = 0; gBadPortList[i]; i++) { + // We can't be accessed by another thread yet + MOZ_PUSH_IGNORE_THREAD_SAFETY + mRestrictedPortList.AppendElement(gBadPortList[i]); + MOZ_POP_THREAD_SAFETY + } + + // Further modifications to the port list come from prefs + Preferences::RegisterPrefixCallbacks(nsIOService::PrefsChanged, + gCallbackPrefs, this); + PrefsChanged(); + + mSocketProcessTopicBlockedList.Insert( + nsLiteralCString(NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)); + mSocketProcessTopicBlockedList.Insert( + nsLiteralCString(NS_XPCOM_SHUTDOWN_OBSERVER_ID)); + mSocketProcessTopicBlockedList.Insert("xpcom-shutdown-threads"_ns); + mSocketProcessTopicBlockedList.Insert("profile-do-change"_ns); + mSocketProcessTopicBlockedList.Insert("network:socket-process-crashed"_ns); + + // Register for profile change notifications + mObserverService = services::GetObserverService(); + AddObserver(this, kProfileChangeNetTeardownTopic, true); + AddObserver(this, kProfileChangeNetRestoreTopic, true); + AddObserver(this, kProfileDoChange, true); + AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + AddObserver(this, NS_NETWORK_LINK_TOPIC, true); + AddObserver(this, NS_NETWORK_ID_CHANGED_TOPIC, true); + AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true); + + // Register observers for sending notifications to nsSocketTransportService + if (XRE_IsParentProcess()) { + AddObserver(this, "profile-initial-state", true); + AddObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC, true); + } + + if (IsSocketProcessChild()) { + Preferences::RegisterCallbacks(nsIOService::OnTLSPrefChange, + gCallbackSecurityPrefs, this); + } + + gIOService = this; + + InitializeNetworkLinkService(); + InitializeProtocolProxyService(); + + SetOffline(false); + + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::AddObserver(nsIObserver* aObserver, const char* aTopic, + bool aOwnsWeak) { + if (!mObserverService) { + return NS_ERROR_FAILURE; + } + + // Register for the origional observer. + nsresult rv = mObserverService->AddObserver(aObserver, aTopic, aOwnsWeak); + if (NS_FAILED(rv)) { + return rv; + } + + if (!XRE_IsParentProcess()) { + return NS_OK; + } + + nsAutoCString topic(aTopic); + // This happens when AddObserver() is called by nsIOService::Init(). We don't + // want to add nsIOService again. + if (SameCOMIdentity(aObserver, static_cast<nsIObserver*>(this))) { + mIOServiceTopicList.Insert(topic); + return NS_OK; + } + + if (!UseSocketProcess()) { + return NS_OK; + } + + if (mSocketProcessTopicBlockedList.Contains(topic)) { + return NS_ERROR_FAILURE; + } + + // Avoid registering duplicate topics. + if (mObserverTopicForSocketProcess.Contains(topic)) { + return NS_ERROR_FAILURE; + } + + mObserverTopicForSocketProcess.Insert(topic); + + // Avoid registering duplicate topics. + if (mIOServiceTopicList.Contains(topic)) { + return NS_ERROR_FAILURE; + } + + return mObserverService->AddObserver(this, aTopic, true); +} + +NS_IMETHODIMP +nsIOService::RemoveObserver(nsIObserver* aObserver, const char* aTopic) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsIOService::EnumerateObservers(const char* aTopic, + nsISimpleEnumerator** anEnumerator) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsIOService::NotifyObservers(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsIOService::~nsIOService() { + if (gIOService) { + MOZ_ASSERT(gIOService == this); + gIOService = nullptr; + } +} + +// static +void nsIOService::OnTLSPrefChange(const char* aPref, void* aSelf) { + MOZ_ASSERT(IsSocketProcessChild()); + + if (!EnsureNSSInitializedChromeOrContent()) { + LOG(("NSS not initialized.")); + return; + } + + nsAutoCString pref(aPref); + // The preferences listed in gCallbackSecurityPrefs need to be in sync with + // the code in HandleTLSPrefChange() and SetValidationOptionsCommon(). + if (HandleTLSPrefChange(pref)) { + LOG(("HandleTLSPrefChange done")); + } else if (pref.EqualsLiteral("security.ssl.enable_ocsp_stapling") || + pref.EqualsLiteral("security.ssl.enable_ocsp_must_staple") || + pref.EqualsLiteral("security.pki.certificate_transparency.mode")) { + SetValidationOptionsCommon(); + } +} + +nsresult nsIOService::InitializeCaptivePortalService() { + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // We only initalize a captive portal service in the main process + return NS_OK; + } + + mCaptivePortalService = do_GetService(NS_CAPTIVEPORTAL_CID); + if (mCaptivePortalService) { + return static_cast<CaptivePortalService*>(mCaptivePortalService.get()) + ->Initialize(); + } + + // Instantiate and initialize the service + RefPtr<NetworkConnectivityService> ncs = + NetworkConnectivityService::GetSingleton(); + + return NS_OK; +} + +nsresult nsIOService::InitializeSocketTransportService() { + nsresult rv = NS_OK; + + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + LOG( + ("nsIOService aborting InitializeSocketTransportService because of app " + "shutdown")); + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + if (!mSocketTransportService) { + mSocketTransportService = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("failed to get socket transport service"); + } + } + + if (mSocketTransportService) { + rv = mSocketTransportService->Init(); + NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service init failed"); + mSocketTransportService->SetOffline(false); + } + + return rv; +} + +nsresult nsIOService::InitializeNetworkLinkService() { + nsresult rv = NS_OK; + + if (mNetworkLinkServiceInitialized) return rv; + + if (!NS_IsMainThread()) { + NS_WARNING("Network link service should be created on main thread"); + return NS_ERROR_FAILURE; + } + + // go into managed mode if we can, and chrome process + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + mNetworkLinkService = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv); + + if (mNetworkLinkService) { + mNetworkLinkServiceInitialized = true; + } + + // After initializing the networkLinkService, query the connectivity state + OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN); + + return rv; +} + +nsresult nsIOService::InitializeProtocolProxyService() { + nsresult rv = NS_OK; + + if (XRE_IsParentProcess()) { + // for early-initialization + Unused << do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); + } + + return rv; +} + +already_AddRefed<nsIOService> nsIOService::GetInstance() { + if (!gIOService) { + RefPtr<nsIOService> ios = new nsIOService(); + if (NS_SUCCEEDED(ios->Init())) { + MOZ_ASSERT(gIOService == ios.get()); + return ios.forget(); + } + } + return do_AddRef(gIOService); +} + +class SocketProcessListenerProxy : public SocketProcessHost::Listener { + public: + SocketProcessListenerProxy() = default; + void OnProcessLaunchComplete(SocketProcessHost* aHost, bool aSucceeded) { + if (!gIOService) { + return; + } + + gIOService->OnProcessLaunchComplete(aHost, aSucceeded); + } + + void OnProcessUnexpectedShutdown(SocketProcessHost* aHost) { + if (!gIOService) { + return; + } + + gIOService->OnProcessUnexpectedShutdown(aHost); + } +}; + +// static +bool nsIOService::TooManySocketProcessCrash() { + return sSocketProcessCrashedCount >= + StaticPrefs::network_max_socket_process_failed_count(); +} + +// static +void nsIOService::IncreaseSocketProcessCrashCount() { + MOZ_ASSERT(IsNeckoChild()); + sSocketProcessCrashedCount++; +} + +nsresult nsIOService::LaunchSocketProcess() { + MOZ_ASSERT(NS_IsMainThread()); + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + return NS_OK; + } + + // We shouldn't launch socket prcess when shutdown begins. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return NS_OK; + } + + if (mSocketProcess) { + return NS_OK; + } + + if (PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS")) { + LOG(("nsIOService skipping LaunchSocketProcess because of the env")); + return NS_OK; + } + + if (!StaticPrefs::network_process_enabled()) { + LOG(("nsIOService skipping LaunchSocketProcess because of the pref")); + return NS_OK; + } + + Preferences::RegisterPrefixCallbacks( + nsIOService::NotifySocketProcessPrefsChanged, + gCallbackPrefsForSocketProcess, this); + + // The subprocess is launched asynchronously, so we wait for a callback to + // acquire the IPDL actor. + mSocketProcess = new SocketProcessHost(new SocketProcessListenerProxy()); + LOG(("nsIOService::LaunchSocketProcess")); + if (!mSocketProcess->Launch()) { + NS_WARNING("Failed to launch socket process!!"); + DestroySocketProcess(); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void nsIOService::DestroySocketProcess() { + LOG(("nsIOService::DestroySocketProcess")); + MOZ_ASSERT(NS_IsMainThread()); + + if (XRE_GetProcessType() != GeckoProcessType_Default || !mSocketProcess) { + return; + } + + Preferences::UnregisterPrefixCallbacks( + nsIOService::NotifySocketProcessPrefsChanged, + gCallbackPrefsForSocketProcess, this); + + mSocketProcess->Shutdown(); + mSocketProcess = nullptr; +} + +bool nsIOService::SocketProcessReady() { + return mSocketProcess && mSocketProcess->IsConnected(); +} + +static bool sUseSocketProcess = false; +static bool sUseSocketProcessChecked = false; + +// static +bool nsIOService::UseSocketProcess(bool aCheckAgain) { + if (sUseSocketProcessChecked && !aCheckAgain) { + return sUseSocketProcess; + } + + sUseSocketProcessChecked = true; + sUseSocketProcess = false; + + if (PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS")) { + return sUseSocketProcess; + } + + if (TooManySocketProcessCrash()) { + LOG(("TooManySocketProcessCrash")); + return sUseSocketProcess; + } + + if (PR_GetEnv("MOZ_FORCE_USE_SOCKET_PROCESS")) { + sUseSocketProcess = true; + return sUseSocketProcess; + } + + if (StaticPrefs::network_process_enabled()) { + sUseSocketProcess = + StaticPrefs::network_http_network_access_on_socket_process_enabled(); + } + return sUseSocketProcess; +} + +// static +void nsIOService::NotifySocketProcessPrefsChanged(const char* aName, + void* aSelf) { + static_cast<nsIOService*>(aSelf)->NotifySocketProcessPrefsChanged(aName); +} + +void nsIOService::NotifySocketProcessPrefsChanged(const char* aName) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!XRE_IsParentProcess()) { + return; + } + + if (!StaticPrefs::network_process_enabled()) { + return; + } + + dom::Pref pref(nsCString(aName), /* isLocked */ false, + /* isSanitized */ false, Nothing(), Nothing()); + + Preferences::GetPreference(&pref, GeckoProcessType_Socket, + /* remoteType */ ""_ns); + auto sendPrefUpdate = [pref]() { + Unused << gIOService->mSocketProcess->GetActor()->SendPreferenceUpdate( + pref); + }; + CallOrWaitForSocketProcess(sendPrefUpdate); +} + +void nsIOService::OnProcessLaunchComplete(SocketProcessHost* aHost, + bool aSucceeded) { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("nsIOService::OnProcessLaunchComplete aSucceeded=%d\n", aSucceeded)); + + mSocketProcessLaunchComplete = aSucceeded; + + if (mShutdown || !SocketProcessReady() || !aSucceeded) { + mPendingEvents.Clear(); + return; + } + + if (!mPendingEvents.IsEmpty()) { + nsTArray<std::function<void()>> pendingEvents = std::move(mPendingEvents); + for (auto& func : pendingEvents) { + func(); + } + } +} + +void nsIOService::CallOrWaitForSocketProcess( + const std::function<void()>& aFunc) { + MOZ_ASSERT(NS_IsMainThread()); + if (IsSocketProcessLaunchComplete() && SocketProcessReady()) { + aFunc(); + } else { + mPendingEvents.AppendElement(aFunc); // infallible + LaunchSocketProcess(); + } +} + +int32_t nsIOService::SocketProcessPid() { + if (!mSocketProcess) { + return 0; + } + if (SocketProcessParent* actor = mSocketProcess->GetActor()) { + return (int32_t)actor->OtherPid(); + } + return 0; +} + +bool nsIOService::IsSocketProcessLaunchComplete() { + MOZ_ASSERT(NS_IsMainThread()); + return mSocketProcessLaunchComplete; +} + +void nsIOService::OnProcessUnexpectedShutdown(SocketProcessHost* aHost) { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("nsIOService::OnProcessUnexpectedShutdown\n")); + DestroySocketProcess(); + mPendingEvents.Clear(); + + // Nothing to do if socket process was not used before. + if (!UseSocketProcess()) { + return; + } + + sSocketProcessCrashedCount++; + if (TooManySocketProcessCrash()) { + sUseSocketProcessChecked = false; + DNSServiceWrapper::SwitchToBackupDNSService(); + } + + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + if (observerService) { + (void)observerService->NotifyObservers( + nullptr, "network:socket-process-crashed", nullptr); + } + + // UseSocketProcess() could return false if we have too many crashes, so we + // should call it again. + if (UseSocketProcess()) { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( + NewRunnableMethod("nsIOService::LaunchSocketProcess", this, + &nsIOService::LaunchSocketProcess))); + } +} + +RefPtr<MemoryReportingProcess> nsIOService::GetSocketProcessMemoryReporter() { + // Check the prefs here again, since we don't want to create + // SocketProcessMemoryReporter for some tests. + if (!StaticPrefs::network_process_enabled() || !SocketProcessReady()) { + return nullptr; + } + + return new SocketProcessMemoryReporter(); +} + +NS_IMETHODIMP +nsIOService::SocketProcessTelemetryPing() { + CallOrWaitForSocketProcess([]() { + Unused << gIOService->mSocketProcess->GetActor() + ->SendSocketProcessTelemetryPing(); + }); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsIOService, nsIIOService, nsINetUtil, nsISpeculativeConnect, + nsIObserver, nsIIOServiceInternal, nsISupportsWeakReference, + nsIObserverService) + +//////////////////////////////////////////////////////////////////////////////// + +nsresult nsIOService::RecheckCaptivePortal() { + MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread"); + if (!mCaptivePortalService) { + return NS_OK; + } + nsCOMPtr<nsIRunnable> task = NewRunnableMethod( + "nsIOService::RecheckCaptivePortal", mCaptivePortalService, + &nsICaptivePortalService::RecheckCaptivePortal); + return NS_DispatchToMainThread(task); +} + +nsresult nsIOService::RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan) { + nsresult rv; + + if (!mCaptivePortalService) { + return NS_OK; + } + + nsCOMPtr<nsIURI> uri; + rv = newChan->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCString host; + rv = uri->GetHost(host); + if (NS_FAILED(rv)) { + return rv; + } + + NetAddr addr; + // If the redirect wasn't to an IP literal, so there's probably no need + // to trigger the captive portal detection right now. It can wait. + if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrLocal()) { + RecheckCaptivePortal(); + } + + return NS_OK; +} + +nsresult nsIOService::AsyncOnChannelRedirect( + nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags, + nsAsyncRedirectVerifyHelper* helper) { + // If a redirect to a local network address occurs, then chances are we + // are in a captive portal, so we trigger a recheck. + RecheckCaptivePortalIfLocalRedirect(newChan); + + // This is silly. I wish there was a simpler way to get at the global + // reference of the contentSecurityManager. But it lives in the XPCOM + // service registry. + nsCOMPtr<nsIChannelEventSink> sink = + do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID); + if (sink) { + nsresult rv = + helper->DelegateOnChannelRedirect(sink, oldChan, newChan, flags); + if (NS_FAILED(rv)) return rv; + } + + // Finally, our category + nsCOMArray<nsIChannelEventSink> entries; + mChannelEventSinks.GetEntries(entries); + int32_t len = entries.Count(); + for (int32_t i = 0; i < len; ++i) { + nsresult rv = + helper->DelegateOnChannelRedirect(entries[i], oldChan, newChan, flags); + if (NS_FAILED(rv)) return rv; + } + + nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(oldChan)); + + // Collect the redirection from HTTP(S) only. + if (httpChan) { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIURI> newURI; + newChan->GetURI(getter_AddRefs(newURI)); + MOZ_ASSERT(newURI); + + nsAutoCString scheme; + newURI->GetScheme(scheme); + MOZ_ASSERT(!scheme.IsEmpty()); + + Telemetry::AccumulateCategoricalKeyed( + scheme, + oldChan->IsDocument() + ? Telemetry::LABELS_NETWORK_HTTP_REDIRECT_TO_SCHEME::topLevel + : Telemetry::LABELS_NETWORK_HTTP_REDIRECT_TO_SCHEME::subresource); + } + + return NS_OK; +} + +bool nsIOService::UsesExternalProtocolHandler(const nsACString& aScheme) { + if (aScheme == "file"_ns || aScheme == "chrome"_ns || + aScheme == "resource"_ns) { + // Don't allow file:, chrome: or resource: URIs to be handled with + // nsExternalProtocolHandler, since internally we rely on being able to + // use and read from these URIs. + return false; + } + + if (aScheme == "place"_ns || aScheme == "fake-favicon-uri"_ns || + aScheme == "favicon"_ns || aScheme == "moz-nullprincipal"_ns) { + // Force place: fake-favicon-uri: favicon: and moz-nullprincipal: URIs to be + // handled with nsExternalProtocolHandler, and not with a dynamically + // registered handler. + return true; + } + + // If prefs configure the URI to be handled externally, do so. + for (const auto& scheme : mForceExternalSchemes) { + if (aScheme == scheme) { + return true; + } + } + return false; +} + +ProtocolHandlerInfo nsIOService::LookupProtocolHandler( + const nsACString& aScheme) { + // Look-ups are ASCII-case-insensitive, so lower-case the string before + // continuing. + nsAutoCString scheme(aScheme); + ToLowerCase(scheme); + + // NOTE: If we could get rid of mForceExternalSchemes (or prevent them from + // disabling static protocols), we could avoid locking mLock until we need to + // check `mRuntimeProtocolHandlers. + AutoReadLock lock(mLock); + if (!UsesExternalProtocolHandler(scheme)) { + // Try the static protocol handler first - they cannot be overridden by + // dynamic protocols. + if (const xpcom::StaticProtocolHandler* handler = + xpcom::StaticProtocolHandler::Lookup(scheme)) { + return ProtocolHandlerInfo(*handler); + } + if (auto handler = mRuntimeProtocolHandlers.Lookup(scheme)) { + return ProtocolHandlerInfo(handler.Data()); + } + } + return ProtocolHandlerInfo(xpcom::StaticProtocolHandler::Default()); +} + +NS_IMETHODIMP +nsIOService::GetProtocolHandler(const char* scheme, + nsIProtocolHandler** result) { + AssertIsOnMainThread(); + NS_ENSURE_ARG_POINTER(scheme); + + *result = LookupProtocolHandler(nsDependentCString(scheme)).Handler().take(); + return *result ? NS_OK : NS_ERROR_UNKNOWN_PROTOCOL; +} + +NS_IMETHODIMP +nsIOService::ExtractScheme(const nsACString& inURI, nsACString& scheme) { + return net_ExtractURLScheme(inURI, scheme); +} + +NS_IMETHODIMP +nsIOService::HostnameIsLocalIPAddress(nsIURI* aURI, bool* aResult) { + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI); + NS_ENSURE_ARG_POINTER(innerURI); + + nsAutoCString host; + nsresult rv = innerURI->GetAsciiHost(host); + if (NS_FAILED(rv)) { + return rv; + } + + *aResult = false; + + NetAddr addr; + if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrLocal()) { + *aResult = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::HostnameIsSharedIPAddress(nsIURI* aURI, bool* aResult) { + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI); + NS_ENSURE_ARG_POINTER(innerURI); + + nsAutoCString host; + nsresult rv = innerURI->GetAsciiHost(host); + if (NS_FAILED(rv)) { + return rv; + } + + *aResult = false; + + NetAddr addr; + if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrShared()) { + *aResult = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::IsValidHostname(const nsACString& inHostname, bool* aResult) { + *aResult = net_IsValidHostName(inHostname); + + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::GetProtocolFlags(const char* scheme, uint32_t* flags) { + NS_ENSURE_ARG_POINTER(scheme); + + *flags = + LookupProtocolHandler(nsDependentCString(scheme)).StaticProtocolFlags(); + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::GetDynamicProtocolFlags(nsIURI* uri, uint32_t* flags) { + AssertIsOnMainThread(); + NS_ENSURE_ARG(uri); + + nsAutoCString scheme; + nsresult rv = uri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + + return LookupProtocolHandler(scheme).DynamicProtocolFlags(uri, flags); +} + +NS_IMETHODIMP +nsIOService::GetDefaultPort(const char* scheme, int32_t* defaultPort) { + NS_ENSURE_ARG_POINTER(scheme); + + *defaultPort = + LookupProtocolHandler(nsDependentCString(scheme)).DefaultPort(); + return NS_OK; +} + +nsresult nsIOService::NewURI(const nsACString& aSpec, const char* aCharset, + nsIURI* aBaseURI, nsIURI** result) { + return NS_NewURI(result, aSpec, aCharset, aBaseURI); +} + +NS_IMETHODIMP +nsIOService::NewFileURI(nsIFile* file, nsIURI** result) { + nsresult rv; + NS_ENSURE_ARG_POINTER(file); + + nsCOMPtr<nsIProtocolHandler> handler; + + rv = GetProtocolHandler("file", getter_AddRefs(handler)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIFileProtocolHandler> fileHandler(do_QueryInterface(handler, &rv)); + if (NS_FAILED(rv)) return rv; + + return fileHandler->NewFileURI(file, result); +} + +// static +already_AddRefed<nsIURI> nsIOService::CreateExposableURI(nsIURI* aURI) { + MOZ_ASSERT(aURI, "Must have a URI"); + nsCOMPtr<nsIURI> uri = aURI; + bool hasUserPass; + if (NS_SUCCEEDED(aURI->GetHasUserPass(&hasUserPass)) && hasUserPass) { + DebugOnly<nsresult> rv = NS_MutateURI(uri).SetUserPass(""_ns).Finalize(uri); + MOZ_ASSERT(NS_SUCCEEDED(rv) && uri, "Mutating URI should never fail"); + } + return uri.forget(); +} + +NS_IMETHODIMP +nsIOService::CreateExposableURI(nsIURI* aURI, nsIURI** _result) { + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(_result); + nsCOMPtr<nsIURI> exposableURI = CreateExposableURI(aURI); + exposableURI.forget(_result); + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::NewChannelFromURI(nsIURI* aURI, nsINode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + uint32_t aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsIChannel** result) { + return NewChannelFromURIWithProxyFlags(aURI, + nullptr, // aProxyURI + 0, // aProxyFlags + aLoadingNode, aLoadingPrincipal, + aTriggeringPrincipal, aSecurityFlags, + aContentPolicyType, result); +} +nsresult nsIOService::NewChannelFromURIWithClientAndController( + nsIURI* aURI, nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + const Maybe<ClientInfo>& aLoadingClientInfo, + const Maybe<ServiceWorkerDescriptor>& aController, uint32_t aSecurityFlags, + nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags, + bool aSkipCheckForBrokenURLOrZeroSized, nsIChannel** aResult) { + return NewChannelFromURIWithProxyFlagsInternal( + aURI, + nullptr, // aProxyURI + 0, // aProxyFlags + aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal, aLoadingClientInfo, + aController, aSecurityFlags, aContentPolicyType, aSandboxFlags, + aSkipCheckForBrokenURLOrZeroSized, aResult); +} + +NS_IMETHODIMP +nsIOService::NewChannelFromURIWithLoadInfo(nsIURI* aURI, nsILoadInfo* aLoadInfo, + nsIChannel** result) { + return NewChannelFromURIWithProxyFlagsInternal(aURI, + nullptr, // aProxyURI + 0, // aProxyFlags + aLoadInfo, result); +} + +nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal( + nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags, + nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + const Maybe<ClientInfo>& aLoadingClientInfo, + const Maybe<ServiceWorkerDescriptor>& aController, uint32_t aSecurityFlags, + nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags, + bool aSkipCheckForBrokenURLOrZeroSized, nsIChannel** result) { + nsCOMPtr<nsILoadInfo> loadInfo = new LoadInfo( + aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags, + aContentPolicyType, aLoadingClientInfo, aController, aSandboxFlags, + aSkipCheckForBrokenURLOrZeroSized); + return NewChannelFromURIWithProxyFlagsInternal(aURI, aProxyURI, aProxyFlags, + loadInfo, result); +} + +nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal( + nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags, + nsILoadInfo* aLoadInfo, nsIChannel** result) { + nsresult rv; + NS_ENSURE_ARG_POINTER(aURI); + // all channel creations must provide a valid loadinfo + MOZ_ASSERT(aLoadInfo, "can not create channel without aLoadInfo"); + NS_ENSURE_ARG_POINTER(aLoadInfo); + + nsAutoCString scheme; + rv = aURI->GetScheme(scheme); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIProtocolHandler> handler; + rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIChannel> channel; + nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler); + if (pph) { + rv = pph->NewProxiedChannel(aURI, nullptr, aProxyFlags, aProxyURI, + aLoadInfo, getter_AddRefs(channel)); + } else { + rv = handler->NewChannel(aURI, aLoadInfo, getter_AddRefs(channel)); + } + if (NS_FAILED(rv)) return rv; + + // Make sure that all the individual protocolhandlers attach a loadInfo. + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + if (aLoadInfo != loadInfo) { + MOZ_ASSERT(false, "newly created channel must have a loadinfo attached"); + return NS_ERROR_UNEXPECTED; + } + + // If we're sandboxed, make sure to clear any owner the channel + // might already have. + if (loadInfo->GetLoadingSandboxed()) { + channel->SetOwner(nullptr); + } + + // 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 + // because it's a new interface. + // Eventually we should remove this and simply require that http channels + // implement the new interface. + // See bug 529041 + if (!gHasWarnedUploadChannel2 && scheme.EqualsLiteral("http")) { + nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(channel); + if (!uploadChannel2) { + nsCOMPtr<nsIConsoleService> consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (consoleService) { + consoleService->LogStringMessage( + u"Http channel implementation " + "doesn't support nsIUploadChannel2. An extension has " + "supplied a non-functional http protocol handler. This will " + "break behavior and in future releases not work at all."); + } + gHasWarnedUploadChannel2 = true; + } + } + + channel.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::NewChannelFromURIWithProxyFlags( + nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags, + nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, uint32_t aSecurityFlags, + nsContentPolicyType aContentPolicyType, nsIChannel** result) { + return NewChannelFromURIWithProxyFlagsInternal( + aURI, aProxyURI, aProxyFlags, aLoadingNode, aLoadingPrincipal, + aTriggeringPrincipal, Maybe<ClientInfo>(), + Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, aContentPolicyType, 0, + /* aSkipCheckForBrokenURLOrZeroSized = */ false, result); +} + +NS_IMETHODIMP +nsIOService::NewChannel(const nsACString& aSpec, const char* aCharset, + nsIURI* aBaseURI, nsINode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + uint32_t aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsIChannel** result) { + nsresult rv; + nsCOMPtr<nsIURI> uri; + rv = NewURI(aSpec, aCharset, aBaseURI, getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + return NewChannelFromURI(uri, aLoadingNode, aLoadingPrincipal, + aTriggeringPrincipal, aSecurityFlags, + aContentPolicyType, result); +} + +NS_IMETHODIMP +nsIOService::NewWebTransport(nsIWebTransport** result) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsIWebTransport> webTransport = new WebTransportSessionProxy(); + + webTransport.forget(result); + return NS_OK; +} + +bool nsIOService::IsLinkUp() { + InitializeNetworkLinkService(); + + if (!mNetworkLinkService) { + // We cannot decide, assume the link is up + return true; + } + + bool isLinkUp; + nsresult rv; + rv = mNetworkLinkService->GetIsLinkUp(&isLinkUp); + if (NS_FAILED(rv)) { + return true; + } + + return isLinkUp; +} + +NS_IMETHODIMP +nsIOService::GetOffline(bool* offline) { + if (StaticPrefs::network_offline_mirrors_connectivity()) { + *offline = mOffline || !mConnectivity; + } else { + *offline = mOffline; + } + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::SetOffline(bool offline) { return SetOfflineInternal(offline); } + +nsresult nsIOService::SetOfflineInternal(bool offline, + bool notifySocketProcess) { + LOG(("nsIOService::SetOffline offline=%d\n", offline)); + // When someone wants to go online (!offline) after we got XPCOM shutdown + // throw ERROR_NOT_AVAILABLE to prevent return to online state. + if ((mShutdown || mOfflineForProfileChange) && !offline) { + return NS_ERROR_NOT_AVAILABLE; + } + + // SetOffline() may re-enter while it's shutting down services. + // If that happens, save the most recent value and it will be + // processed when the first SetOffline() call is done bringing + // down the service. + mSetOfflineValue = offline; + if (mSettingOffline) { + return NS_OK; + } + + mSettingOffline = true; + + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + + NS_ASSERTION(observerService, "The observer service should not be null"); + + if (XRE_IsParentProcess()) { + if (observerService) { + (void)observerService->NotifyObservers(nullptr, + NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, + offline ? u"true" : u"false"); + } + if (SocketProcessReady() && notifySocketProcess) { + Unused << mSocketProcess->GetActor()->SendSetOffline(offline); + } + } + + nsIIOService* subject = static_cast<nsIIOService*>(this); + while (mSetOfflineValue != mOffline) { + offline = mSetOfflineValue; + + if (offline && !mOffline) { + mOffline = true; // indicate we're trying to shutdown + + // don't care if notifications fail + if (observerService) { + observerService->NotifyObservers(subject, + NS_IOSERVICE_GOING_OFFLINE_TOPIC, + u"" NS_IOSERVICE_OFFLINE); + } + + if (mSocketTransportService) mSocketTransportService->SetOffline(true); + + mLastOfflineStateChange = PR_IntervalNow(); + if (observerService) { + observerService->NotifyObservers(subject, + NS_IOSERVICE_OFFLINE_STATUS_TOPIC, + u"" NS_IOSERVICE_OFFLINE); + } + } else if (!offline && mOffline) { + // go online + InitializeSocketTransportService(); + mOffline = false; // indicate success only AFTER we've + // brought up the services + + mLastOfflineStateChange = PR_IntervalNow(); + // don't care if notification fails + // Only send the ONLINE notification if there is connectivity + if (observerService && mConnectivity) { + observerService->NotifyObservers(subject, + NS_IOSERVICE_OFFLINE_STATUS_TOPIC, + (u"" NS_IOSERVICE_ONLINE)); + } + } + } + + // Don't notify here, as the above notifications (if used) suffice. + if ((mShutdown || mOfflineForProfileChange) && mOffline) { + if (mSocketTransportService) { + DebugOnly<nsresult> rv = mSocketTransportService->Shutdown(mShutdown); + NS_ASSERTION(NS_SUCCEEDED(rv), + "socket transport service shutdown failed"); + } + } + + mSettingOffline = false; + + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::GetConnectivity(bool* aConnectivity) { + *aConnectivity = mConnectivity; + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::SetConnectivity(bool aConnectivity) { + LOG(("nsIOService::SetConnectivity aConnectivity=%d\n", aConnectivity)); + // This should only be called from ContentChild to pass the connectivity + // value from the chrome process to the content process. + if (XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + return SetConnectivityInternal(aConnectivity); +} + +nsresult nsIOService::SetConnectivityInternal(bool aConnectivity) { + LOG(("nsIOService::SetConnectivityInternal aConnectivity=%d\n", + aConnectivity)); + if (mConnectivity == aConnectivity) { + // Nothing to do here. + return NS_OK; + } + mConnectivity = aConnectivity; + + // This is used for PR_Connect PR_Close telemetry so it is important that + // we have statistic about network change event even if we are offline. + mLastConnectivityChange = PR_IntervalNow(); + + if (mCaptivePortalService) { + if (aConnectivity && gCaptivePortalEnabled) { + // This will also trigger a captive portal check for the new network + static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Start(); + } else { + static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop(); + } + } + + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + if (!observerService) { + return NS_OK; + } + // This notification sends the connectivity to the child processes + if (XRE_IsParentProcess()) { + observerService->NotifyObservers(nullptr, + NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC, + aConnectivity ? u"true" : u"false"); + if (SocketProcessReady()) { + Unused << mSocketProcess->GetActor()->SendSetConnectivity(aConnectivity); + } + } + + if (mOffline) { + // We don't need to send any notifications if we're offline + return NS_OK; + } + + if (aConnectivity) { + // If we were previously offline due to connectivity=false, + // send the ONLINE notification + observerService->NotifyObservers(static_cast<nsIIOService*>(this), + NS_IOSERVICE_OFFLINE_STATUS_TOPIC, + (u"" NS_IOSERVICE_ONLINE)); + } else { + // If we were previously online and lost connectivity + // send the OFFLINE notification + observerService->NotifyObservers(static_cast<nsIIOService*>(this), + NS_IOSERVICE_GOING_OFFLINE_TOPIC, + u"" NS_IOSERVICE_OFFLINE); + observerService->NotifyObservers(static_cast<nsIIOService*>(this), + NS_IOSERVICE_OFFLINE_STATUS_TOPIC, + u"" NS_IOSERVICE_OFFLINE); + } + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::AllowPort(int32_t inPort, const char* scheme, bool* _retval) { + int32_t port = inPort; + if (port == -1) { + *_retval = true; + return NS_OK; + } + + if (port <= 0 || port > std::numeric_limits<uint16_t>::max()) { + *_retval = false; + return NS_OK; + } + + nsTArray<int32_t> restrictedPortList; + { + AutoReadLock lock(mLock); + restrictedPortList.Assign(mRestrictedPortList); + } + // first check to see if the port is in our blacklist: + int32_t badPortListCnt = restrictedPortList.Length(); + for (int i = 0; i < badPortListCnt; i++) { + if (port == restrictedPortList[i]) { + *_retval = false; + + // check to see if the protocol wants to override + if (!scheme) return NS_OK; + + // We don't support get protocol handler off main thread. + if (!NS_IsMainThread()) { + return NS_OK; + } + nsCOMPtr<nsIProtocolHandler> handler; + nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler)); + if (NS_FAILED(rv)) return rv; + + // let the protocol handler decide + return handler->AllowPort(port, scheme, _retval); + } + } + + *_retval = true; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +// static +void nsIOService::PrefsChanged(const char* pref, void* self) { + static_cast<nsIOService*>(self)->PrefsChanged(pref); +} + +void nsIOService::PrefsChanged(const char* pref) { + // Look for extra ports to block + if (!pref || strcmp(pref, PORT_PREF("banned")) == 0) { + ParsePortList(PORT_PREF("banned"), false); + } + + // ...as well as previous blocks to remove. + if (!pref || strcmp(pref, PORT_PREF("banned.override")) == 0) { + ParsePortList(PORT_PREF("banned.override"), true); + } + + if (!pref || strcmp(pref, MANAGE_OFFLINE_STATUS_PREF) == 0) { + bool manage; + if (mNetworkLinkServiceInitialized && + NS_SUCCEEDED( + Preferences::GetBool(MANAGE_OFFLINE_STATUS_PREF, &manage))) { + LOG(("nsIOService::PrefsChanged ManageOfflineStatus manage=%d\n", + manage)); + SetManageOfflineStatus(manage); + } + } + + if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_COUNT_PREF) == 0) { + int32_t count; + if (NS_SUCCEEDED( + Preferences::GetInt(NECKO_BUFFER_CACHE_COUNT_PREF, &count))) { + /* check for bogus values and default if we find such a value */ + if (count > 0) gDefaultSegmentCount = count; + } + } + + if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_SIZE_PREF) == 0) { + int32_t size; + if (NS_SUCCEEDED( + Preferences::GetInt(NECKO_BUFFER_CACHE_SIZE_PREF, &size))) { + /* check for bogus values and default if we find such a value + * the upper limit here is arbitrary. having a 1mb segment size + * is pretty crazy. if you remove this, consider adding some + * integer rollover test. + */ + if (size > 0 && size < 1024 * 1024) gDefaultSegmentSize = size; + } + NS_WARNING_ASSERTION(!(size & (size - 1)), + "network segment size is not a power of 2!"); + } + + if (!pref || strcmp(pref, NETWORK_CAPTIVE_PORTAL_PREF) == 0) { + nsresult rv = Preferences::GetBool(NETWORK_CAPTIVE_PORTAL_PREF, + &gCaptivePortalEnabled); + if (NS_SUCCEEDED(rv) && mCaptivePortalService) { + if (gCaptivePortalEnabled) { + static_cast<CaptivePortalService*>(mCaptivePortalService.get()) + ->Start(); + } else { + static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop(); + } + } + } + + if (!pref || strncmp(pref, FORCE_EXTERNAL_PREF_PREFIX, + strlen(FORCE_EXTERNAL_PREF_PREFIX)) == 0) { + nsTArray<nsCString> prefs; + if (nsIPrefBranch* prefRootBranch = Preferences::GetRootBranch()) { + prefRootBranch->GetChildList(FORCE_EXTERNAL_PREF_PREFIX, prefs); + } + nsTArray<nsCString> forceExternalSchemes; + for (const auto& pref : prefs) { + if (Preferences::GetBool(pref.get(), false)) { + forceExternalSchemes.AppendElement( + Substring(pref, strlen(FORCE_EXTERNAL_PREF_PREFIX))); + } + } + AutoWriteLock lock(mLock); + mForceExternalSchemes = std::move(forceExternalSchemes); + } +} + +void nsIOService::ParsePortList(const char* pref, bool remove) { + nsAutoCString portList; + nsTArray<int32_t> restrictedPortList; + { + AutoWriteLock lock(mLock); + restrictedPortList.Assign(std::move(mRestrictedPortList)); + } + // Get a pref string and chop it up into a list of ports. + Preferences::GetCString(pref, portList); + if (!portList.IsVoid()) { + nsTArray<nsCString> portListArray; + ParseString(portList, ',', portListArray); + uint32_t index; + for (index = 0; index < portListArray.Length(); index++) { + portListArray[index].StripWhitespace(); + int32_t portBegin, portEnd; + + if (PR_sscanf(portListArray[index].get(), "%d-%d", &portBegin, + &portEnd) == 2) { + if ((portBegin < 65536) && (portEnd < 65536)) { + int32_t curPort; + if (remove) { + for (curPort = portBegin; curPort <= portEnd; curPort++) { + restrictedPortList.RemoveElement(curPort); + } + } else { + for (curPort = portBegin; curPort <= portEnd; curPort++) { + restrictedPortList.AppendElement(curPort); + } + } + } + } else { + nsresult aErrorCode; + int32_t port = portListArray[index].ToInteger(&aErrorCode); + if (NS_SUCCEEDED(aErrorCode) && port < 65536) { + if (remove) { + restrictedPortList.RemoveElement(port); + } else { + restrictedPortList.AppendElement(port); + } + } + } + } + } + + AutoWriteLock lock(mLock); + mRestrictedPortList.Assign(std::move(restrictedPortList)); +} + +class nsWakeupNotifier : public Runnable { + public: + explicit nsWakeupNotifier(nsIIOServiceInternal* ioService) + : Runnable("net::nsWakeupNotifier"), mIOService(ioService) {} + + NS_IMETHOD Run() override { return mIOService->NotifyWakeup(); } + + private: + virtual ~nsWakeupNotifier() = default; + nsCOMPtr<nsIIOServiceInternal> mIOService; +}; + +NS_IMETHODIMP +nsIOService::NotifyWakeup() { + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + + NS_ASSERTION(observerService, "The observer service should not be null"); + + if (observerService && StaticPrefs::network_notify_changed()) { + (void)observerService->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC, + (u"" NS_NETWORK_LINK_DATA_CHANGED)); + } + + RecheckCaptivePortal(); + + return NS_OK; +} + +void nsIOService::SetHttpHandlerAlreadyShutingDown() { + if (!mShutdown && !mOfflineForProfileChange) { + mNetTearingDownStarted = PR_IntervalNow(); + mHttpHandlerAlreadyShutingDown = true; + } +} + +// nsIObserver interface +NS_IMETHODIMP +nsIOService::Observe(nsISupports* subject, const char* topic, + const char16_t* data) { + if (UseSocketProcess() && SocketProcessReady() && + mObserverTopicForSocketProcess.Contains(nsDependentCString(topic))) { + nsCString topicStr(topic); + nsString dataStr(data); + Unused << mSocketProcess->GetActor()->SendNotifyObserver(topicStr, dataStr); + } + + if (!strcmp(topic, kProfileChangeNetTeardownTopic)) { + if (!mHttpHandlerAlreadyShutingDown) { + mNetTearingDownStarted = PR_IntervalNow(); + } + mHttpHandlerAlreadyShutingDown = false; + if (!mOffline) { + mOfflineForProfileChange = true; + SetOfflineInternal(true, false); + } + } else if (!strcmp(topic, kProfileChangeNetRestoreTopic)) { + if (mOfflineForProfileChange) { + mOfflineForProfileChange = false; + SetOfflineInternal(false, false); + } + } else if (!strcmp(topic, kProfileDoChange)) { + if (data && u"startup"_ns.Equals(data)) { + // Lazy initialization of network link service (see bug 620472) + InitializeNetworkLinkService(); + // Set up the initilization flag regardless the actuall result. + // If we fail here, we will fail always on. + mNetworkLinkServiceInitialized = true; + + // And now reflect the preference setting + PrefsChanged(MANAGE_OFFLINE_STATUS_PREF); + + // Bug 870460 - Read cookie database at an early-as-possible time + // off main thread. Hence, we have more chance to finish db query + // before something calls into the cookie service. + nsCOMPtr<nsISupports> cookieServ = + do_GetService(NS_COOKIESERVICE_CONTRACTID); + } + } else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + // Remember we passed XPCOM shutdown notification to prevent any + // changes of the offline status from now. We must not allow going + // online after this point. + mShutdown = true; + + if (!mHttpHandlerAlreadyShutingDown && !mOfflineForProfileChange) { + mNetTearingDownStarted = PR_IntervalNow(); + } + mHttpHandlerAlreadyShutingDown = false; + + SetOfflineInternal(true, false); + + if (mCaptivePortalService) { + static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop(); + mCaptivePortalService = nullptr; + } + + SSLTokensCache::Shutdown(); + + DestroySocketProcess(); + + if (IsSocketProcessChild()) { + Preferences::UnregisterCallbacks(nsIOService::OnTLSPrefChange, + gCallbackSecurityPrefs, this); + PrepareForShutdownInSocketProcess(); + } + + // We're in XPCOM shutdown now. Unregister any dynamic protocol handlers + // after this point to avoid leaks. + { + AutoWriteLock lock(mLock); + mRuntimeProtocolHandlers.Clear(); + } + } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) { + OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get()); + } else if (!strcmp(topic, NS_NETWORK_ID_CHANGED_TOPIC)) { + LOG(("nsIOService::OnNetworkLinkEvent Network id changed")); + } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) { + // coming back alive from sleep + // this indirection brought to you by: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1152048#c19 + nsCOMPtr<nsIRunnable> wakeupNotifier = new nsWakeupNotifier(this); + NS_DispatchToMainThread(wakeupNotifier); + } + + return NS_OK; +} + +// nsINetUtil interface +NS_IMETHODIMP +nsIOService::ParseRequestContentType(const nsACString& aTypeHeader, + nsACString& aCharset, bool* aHadCharset, + nsACString& aContentType) { + net_ParseRequestContentType(aTypeHeader, aContentType, aCharset, aHadCharset); + return NS_OK; +} + +// nsINetUtil interface +NS_IMETHODIMP +nsIOService::ParseResponseContentType(const nsACString& aTypeHeader, + nsACString& aCharset, bool* aHadCharset, + nsACString& aContentType) { + net_ParseContentType(aTypeHeader, aContentType, aCharset, aHadCharset); + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::ProtocolHasFlags(nsIURI* uri, uint32_t flags, bool* result) { + NS_ENSURE_ARG(uri); + + *result = false; + nsAutoCString scheme; + nsresult rv = uri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + + auto handler = LookupProtocolHandler(scheme); + + uint32_t protocolFlags; + if (flags & nsIProtocolHandler::DYNAMIC_URI_FLAGS) { + AssertIsOnMainThread(); + rv = handler.DynamicProtocolFlags(uri, &protocolFlags); + NS_ENSURE_SUCCESS(rv, rv); + } else { + protocolFlags = handler.StaticProtocolFlags(); + } + + *result = (protocolFlags & flags) == flags; + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result) { + nsresult rv = ProtocolHasFlags(uri, flags, result); + NS_ENSURE_SUCCESS(rv, rv); + + if (*result) { + return rv; + } + + // Dig deeper into the chain. Note that this is not a do/while loop to + // avoid the extra addref/release on |uri| in the common (non-nested) case. + nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri); + while (nestedURI) { + nsCOMPtr<nsIURI> innerURI; + rv = nestedURI->GetInnerURI(getter_AddRefs(innerURI)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ProtocolHasFlags(innerURI, flags, result); + + if (*result) { + return rv; + } + + nestedURI = do_QueryInterface(innerURI); + } + + return rv; +} + +NS_IMETHODIMP +nsIOService::SetManageOfflineStatus(bool aManage) { + LOG(("nsIOService::SetManageOfflineStatus aManage=%d\n", aManage)); + mManageLinkStatus = aManage; + + // When detection is not activated, the default connectivity state is true. + if (!mManageLinkStatus) { + SetConnectivityInternal(true); + return NS_OK; + } + + InitializeNetworkLinkService(); + // If the NetworkLinkService is already initialized, it does not call + // OnNetworkLinkEvent. This is needed, when mManageLinkStatus goes from + // false to true. + OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN); + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::GetManageOfflineStatus(bool* aManage) { + *aManage = mManageLinkStatus; + return NS_OK; +} + +// input argument 'data' is already UTF8'ed +nsresult nsIOService::OnNetworkLinkEvent(const char* data) { + if (IsNeckoChild() || IsSocketProcessChild()) { + // There is nothing IO service could do on the child process + // with this at the moment. Feel free to add functionality + // here at will, though. + return NS_OK; + } + + if (mShutdown) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCString dataAsString(data); + for (auto* cp : mozilla::dom::ContentParent::AllProcesses( + mozilla::dom::ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + Unused << neckoParent->SendNetworkChangeNotification(dataAsString); + } + + LOG(("nsIOService::OnNetworkLinkEvent data:%s\n", data)); + if (!mNetworkLinkService) { + return NS_ERROR_FAILURE; + } + + if (!mManageLinkStatus) { + LOG(("nsIOService::OnNetworkLinkEvent mManageLinkStatus=false\n")); + return NS_OK; + } + + bool isUp = true; + if (!strcmp(data, NS_NETWORK_LINK_DATA_CHANGED)) { + mLastNetworkLinkChange = PR_IntervalNow(); + // CHANGED means UP/DOWN didn't change + // but the status of the captive portal may have changed. + RecheckCaptivePortal(); + return NS_OK; + } + if (!strcmp(data, NS_NETWORK_LINK_DATA_DOWN)) { + isUp = false; + } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UP)) { + isUp = true; + } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UNKNOWN)) { + nsresult rv = mNetworkLinkService->GetIsLinkUp(&isUp); + NS_ENSURE_SUCCESS(rv, rv); + } else { + NS_WARNING("Unhandled network event!"); + return NS_OK; + } + + return SetConnectivityInternal(isUp); +} + +NS_IMETHODIMP +nsIOService::EscapeString(const nsACString& aString, uint32_t aEscapeType, + nsACString& aResult) { + NS_ENSURE_ARG_MAX(aEscapeType, 4); + + nsAutoCString stringCopy(aString); + nsCString result; + + if (!NS_Escape(stringCopy, result, (nsEscapeMask)aEscapeType)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + aResult.Assign(result); + + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::EscapeURL(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult) { + aResult.Truncate(); + NS_EscapeURL(aStr.BeginReading(), aStr.Length(), aFlags | esc_AlwaysCopy, + aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::UnescapeString(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult) { + aResult.Truncate(); + NS_UnescapeURL(aStr.BeginReading(), aStr.Length(), aFlags | esc_AlwaysCopy, + aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::ExtractCharsetFromContentType(const nsACString& aTypeHeader, + nsACString& aCharset, + int32_t* aCharsetStart, + int32_t* aCharsetEnd, + bool* aHadCharset) { + nsAutoCString ignored; + net_ParseContentType(aTypeHeader, ignored, aCharset, aHadCharset, + aCharsetStart, aCharsetEnd); + if (*aHadCharset && *aCharsetStart == *aCharsetEnd) { + *aHadCharset = false; + } + return NS_OK; +} + +// nsISpeculativeConnect +class IOServiceProxyCallback final : public nsIProtocolProxyCallback { + ~IOServiceProxyCallback() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROTOCOLPROXYCALLBACK + + IOServiceProxyCallback(nsIInterfaceRequestor* aCallbacks, + nsIOService* aIOService, + Maybe<OriginAttributes>&& aOriginAttributes) + : mCallbacks(aCallbacks), + mIOService(aIOService), + mOriginAttributes(std::move(aOriginAttributes)) {} + + private: + RefPtr<nsIInterfaceRequestor> mCallbacks; + RefPtr<nsIOService> mIOService; + Maybe<OriginAttributes> mOriginAttributes; +}; + +NS_IMPL_ISUPPORTS(IOServiceProxyCallback, nsIProtocolProxyCallback) + +NS_IMETHODIMP +IOServiceProxyCallback::OnProxyAvailable(nsICancelable* request, + nsIChannel* channel, nsIProxyInfo* pi, + nsresult status) { + // Checking proxy status for speculative connect + nsAutoCString type; + if (NS_SUCCEEDED(status) && pi && NS_SUCCEEDED(pi->GetType(type)) && + !type.EqualsLiteral("direct")) { + // proxies dont do speculative connect + return NS_OK; + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = channel->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return NS_OK; + } + + nsAutoCString scheme; + rv = uri->GetScheme(scheme); + if (NS_FAILED(rv)) return NS_OK; + + nsCOMPtr<nsIProtocolHandler> handler; + rv = mIOService->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); + if (NS_FAILED(rv)) return NS_OK; + + nsCOMPtr<nsISpeculativeConnect> speculativeHandler = + do_QueryInterface(handler); + if (!speculativeHandler) return NS_OK; + + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal(); + + nsLoadFlags loadFlags = 0; + channel->GetLoadFlags(&loadFlags); + bool anonymous = !!(loadFlags & nsIRequest::LOAD_ANONYMOUS); + if (mOriginAttributes) { + speculativeHandler->SpeculativeConnectWithOriginAttributesNative( + uri, std::move(mOriginAttributes.ref()), mCallbacks, anonymous); + } else { + speculativeHandler->SpeculativeConnect(uri, principal, mCallbacks, + anonymous); + } + + return NS_OK; +} + +nsresult nsIOService::SpeculativeConnectInternal( + nsIURI* aURI, nsIPrincipal* aPrincipal, + Maybe<OriginAttributes>&& aOriginAttributes, + nsIInterfaceRequestor* aCallbacks, bool aAnonymous) { + NS_ENSURE_ARG(aURI); + + if (!aURI->SchemeIs("http") && !aURI->SchemeIs("https")) { + // We don't speculatively connect to non-HTTP[S] URIs. + return NS_OK; + } + + if (IsNeckoChild()) { + gNeckoChild->SendSpeculativeConnect( + aURI, aPrincipal, std::move(aOriginAttributes), aAnonymous); + return NS_OK; + } + + // Check for proxy information. If there is a proxy configured then a + // speculative connect should not be performed because the potential + // reward is slim with tcp peers closely located to the browser. + nsresult rv; + nsCOMPtr<nsIProtocolProxyService> pps = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> loadingPrincipal = aPrincipal; + + MOZ_ASSERT(aPrincipal || aOriginAttributes, + "We expect passing a principal or OriginAttributes here."); + + if (!aPrincipal && !aOriginAttributes) { + return NS_ERROR_INVALID_ARG; + } + + if (aOriginAttributes) { + loadingPrincipal = + BasePrincipal::CreateContentPrincipal(aURI, aOriginAttributes.ref()); + } + + // XXX Bug 1724080: Avoid TCP connections on port 80 when https-only + // or https-first is enabled. Let's create a dummy loadinfo which we + // only use to determine whether we need ot upgrade the speculative + // connection from http to https. + nsCOMPtr<nsIURI> httpsURI; + if (aURI->SchemeIs("http")) { + nsCOMPtr<nsILoadInfo> httpsOnlyCheckLoadInfo = + new LoadInfo(loadingPrincipal, loadingPrincipal, nullptr, + nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, + nsIContentPolicy::TYPE_SPECULATIVE); + + // Check if https-only, or https-first would upgrade the request + if (nsHTTPSOnlyUtils::ShouldUpgradeRequest(aURI, httpsOnlyCheckLoadInfo) || + nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest( + aURI, httpsOnlyCheckLoadInfo)) { + rv = NS_GetSecureUpgradedURI(aURI, getter_AddRefs(httpsURI)); + NS_ENSURE_SUCCESS(rv, rv); + aURI = httpsURI.get(); + } + } + + // dummy channel used to create a TCP connection. + // we perform security checks on the *real* channel, responsible + // for any network loads. this real channel just checks the TCP + // pool if there is an available connection created by the + // channel we create underneath - hence it's safe to use + // the systemPrincipal as the loadingPrincipal for this channel. + nsCOMPtr<nsIChannel> channel; + rv = NewChannelFromURI( + aURI, + nullptr, // aLoadingNode, + loadingPrincipal, + nullptr, // aTriggeringPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_SPECULATIVE, getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aAnonymous) { + nsLoadFlags loadFlags = 0; + channel->GetLoadFlags(&loadFlags); + loadFlags |= nsIRequest::LOAD_ANONYMOUS; + channel->SetLoadFlags(loadFlags); + } + + nsCOMPtr<nsICancelable> cancelable; + RefPtr<IOServiceProxyCallback> callback = new IOServiceProxyCallback( + aCallbacks, this, std::move(aOriginAttributes)); + nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps); + if (pps2) { + return pps2->AsyncResolve2(channel, 0, callback, nullptr, + getter_AddRefs(cancelable)); + } + return pps->AsyncResolve(channel, 0, callback, nullptr, + getter_AddRefs(cancelable)); +} + +NS_IMETHODIMP +nsIOService::SpeculativeConnect(nsIURI* aURI, nsIPrincipal* aPrincipal, + nsIInterfaceRequestor* aCallbacks, + bool aAnonymous) { + return SpeculativeConnectInternal(aURI, aPrincipal, Nothing(), aCallbacks, + aAnonymous); +} + +NS_IMETHODIMP nsIOService::SpeculativeConnectWithOriginAttributes( + nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes, + nsIInterfaceRequestor* aCallbacks, bool aAnonymous, JSContext* aCx) { + OriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + SpeculativeConnectWithOriginAttributesNative(aURI, std::move(attrs), + aCallbacks, aAnonymous); + return NS_OK; +} + +NS_IMETHODIMP_(void) +nsIOService::SpeculativeConnectWithOriginAttributesNative( + nsIURI* aURI, OriginAttributes&& aOriginAttributes, + nsIInterfaceRequestor* aCallbacks, bool aAnonymous) { + Maybe<OriginAttributes> originAttributes; + originAttributes.emplace(aOriginAttributes); + Unused << SpeculativeConnectInternal( + aURI, nullptr, std::move(originAttributes), aCallbacks, aAnonymous); +} + +NS_IMETHODIMP +nsIOService::NotImplemented() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsIOService::GetSocketProcessLaunched(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = SocketProcessReady(); + return NS_OK; +} + +bool nsIOService::HasObservers(const char* aTopic) { + MOZ_ASSERT(false, "Calling this method is unexpected"); + return false; +} + +NS_IMETHODIMP +nsIOService::GetSocketProcessId(uint64_t* aPid) { + NS_ENSURE_ARG_POINTER(aPid); + + *aPid = 0; + if (!mSocketProcess) { + return NS_OK; + } + + if (SocketProcessParent* actor = mSocketProcess->GetActor()) { + *aPid = (uint64_t)actor->OtherPid(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::RegisterProtocolHandler(const nsACString& aScheme, + nsIProtocolHandler* aHandler, + uint32_t aProtocolFlags, + int32_t aDefaultPort) { + if (mShutdown) { + return NS_ERROR_NOT_AVAILABLE; + } + if (aScheme.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString scheme(aScheme); + ToLowerCase(scheme); + + AutoWriteLock lock(mLock); + return mRuntimeProtocolHandlers.WithEntryHandle(scheme, [&](auto&& entry) { + if (entry) { + NS_WARNING("Cannot override an existing dynamic protocol handler"); + return NS_ERROR_FACTORY_EXISTS; + } + if (xpcom::StaticProtocolHandler::Lookup(scheme)) { + NS_WARNING("Cannot override an existing static protocol handler"); + return NS_ERROR_FACTORY_EXISTS; + } + nsMainThreadPtrHandle<nsIProtocolHandler> handler( + new nsMainThreadPtrHolder<nsIProtocolHandler>("RuntimeProtocolHandler", + aHandler)); + entry.Insert(RuntimeProtocolHandler{ + .mHandler = std::move(handler), + .mProtocolFlags = aProtocolFlags, + .mDefaultPort = aDefaultPort, + }); + return NS_OK; + }); +} + +NS_IMETHODIMP +nsIOService::UnregisterProtocolHandler(const nsACString& aScheme) { + if (mShutdown) { + return NS_OK; + } + if (aScheme.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString scheme(aScheme); + ToLowerCase(scheme); + + AutoWriteLock lock(mLock); + return mRuntimeProtocolHandlers.Remove(scheme) + ? NS_OK + : NS_ERROR_FACTORY_NOT_REGISTERED; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsIOService.h b/netwerk/base/nsIOService.h new file mode 100644 index 0000000000..52c8b48e7f --- /dev/null +++ b/netwerk/base/nsIOService.h @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsIOService_h__ +#define nsIOService_h__ + +#include "nsStringFwd.h" +#include "nsIIOService.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsIWeakReferenceUtils.h" +#include "nsINetUtil.h" +#include "nsIChannelEventSink.h" +#include "nsCategoryCache.h" +#include "nsISpeculativeConnect.h" +#include "nsWeakReference.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/RWLock.h" +#include "mozilla/net/ProtocolHandlerInfo.h" +#include "prtime.h" +#include "nsICaptivePortalService.h" +#include "nsIObserverService.h" +#include "nsTHashSet.h" +#include "nsWeakReference.h" +#include "nsNetCID.h" + +// We don't want to expose this observer topic. +// Intended internal use only for remoting offline/inline events. +// See Bug 552829 +#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline" +#define NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC "ipc:network:set-connectivity" + +class nsINetworkLinkService; +class nsIPrefBranch; +class nsIProtocolProxyService2; +class nsIProxyInfo; +class nsPISocketTransportService; + +namespace mozilla { +class MemoryReportingProcess; +namespace net { +class NeckoChild; +class nsAsyncRedirectVerifyHelper; +class SocketProcessHost; +class SocketProcessMemoryReporter; + +class nsIOService final : public nsIIOService, + public nsIObserver, + public nsINetUtil, + public nsISpeculativeConnect, + public nsSupportsWeakReference, + public nsIIOServiceInternal, + public nsIObserverService { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIOSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSINETUTIL + NS_DECL_NSISPECULATIVECONNECT + NS_DECL_NSIIOSERVICEINTERNAL + NS_DECL_NSIOBSERVERSERVICE + + // Gets the singleton instance of the IO Service, creating it as needed + // Returns nullptr on out of memory or failure to initialize. + static already_AddRefed<nsIOService> GetInstance(); + + nsresult Init(); + nsresult NewURI(const char* aSpec, nsIURI* aBaseURI, nsIURI** result, + nsIProtocolHandler** hdlrResult); + + // Called by channels before a redirect happens. This notifies the global + // redirect observers. + nsresult AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan, + uint32_t flags, + nsAsyncRedirectVerifyHelper* helper); + + bool IsOffline() { return mOffline; } + PRIntervalTime LastOfflineStateChange() { return mLastOfflineStateChange; } + PRIntervalTime LastConnectivityChange() { return mLastConnectivityChange; } + PRIntervalTime LastNetworkLinkChange() { return mLastNetworkLinkChange; } + bool IsNetTearingDown() { + return mShutdown || mOfflineForProfileChange || + mHttpHandlerAlreadyShutingDown; + } + PRIntervalTime NetTearingDownStarted() { return mNetTearingDownStarted; } + + // nsHttpHandler is going to call this function to inform nsIOService that + // network is in process of tearing down. Moving nsHttpConnectionMgr::Shutdown + // to nsIOService caused problems (bug 1242755) so we doing it in this way. As + // soon as nsIOService gets notification that it is shutdown it is going to + // reset mHttpHandlerAlreadyShutingDown. + void SetHttpHandlerAlreadyShutingDown(); + + bool IsLinkUp(); + + // Converts an internal URI (e.g. one that has a username and password in + // it) into one which we can expose to the user, for example on the URL bar. + static already_AddRefed<nsIURI> CreateExposableURI(nsIURI*); + + // Used to count the total number of HTTP requests made + void IncrementRequestNumber() { mTotalRequests++; } + uint32_t GetTotalRequestNumber() { return mTotalRequests; } + // Used to keep "race cache with network" stats + void IncrementCacheWonRequestNumber() { mCacheWon++; } + uint32_t GetCacheWonRequestNumber() { return mCacheWon; } + void IncrementNetWonRequestNumber() { mNetWon++; } + uint32_t GetNetWonRequestNumber() { return mNetWon; } + + // Used to trigger a recheck of the captive portal status + nsresult RecheckCaptivePortal(); + + void OnProcessLaunchComplete(SocketProcessHost* aHost, bool aSucceeded); + void OnProcessUnexpectedShutdown(SocketProcessHost* aHost); + bool SocketProcessReady(); + static void NotifySocketProcessPrefsChanged(const char* aName, void* aSelf); + void NotifySocketProcessPrefsChanged(const char* aName); + static bool UseSocketProcess(bool aCheckAgain = false); + + bool IsSocketProcessLaunchComplete(); + + // Call func immediately if socket process is launched completely. Otherwise, + // |func| will be queued and then executed in the *main thread* once socket + // process is launced. + void CallOrWaitForSocketProcess(const std::function<void()>& aFunc); + + int32_t SocketProcessPid(); + SocketProcessHost* SocketProcess() { return mSocketProcess; } + + friend SocketProcessMemoryReporter; + RefPtr<MemoryReportingProcess> GetSocketProcessMemoryReporter(); + + // Lookup the ProtocolHandlerInfo based on a given scheme. + // Safe to call from any thread. + ProtocolHandlerInfo LookupProtocolHandler(const nsACString& aScheme); + + static void OnTLSPrefChange(const char* aPref, void* aSelf); + + nsresult LaunchSocketProcess(); + + static bool TooManySocketProcessCrash(); + static void IncreaseSocketProcessCrashCount(); + + private: + // These shouldn't be called directly: + // - construct using GetInstance + // - destroy using Release + nsIOService(); + ~nsIOService(); + nsresult SetConnectivityInternal(bool aConnectivity); + + nsresult OnNetworkLinkEvent(const char* data); + + nsresult InitializeCaptivePortalService(); + nsresult RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan); + + // Prefs wrangling + static void PrefsChanged(const char* pref, void* self); + void PrefsChanged(const char* pref = nullptr); + void ParsePortList(const char* pref, bool remove); + + nsresult InitializeSocketTransportService(); + nsresult InitializeNetworkLinkService(); + nsresult InitializeProtocolProxyService(); + + // consolidated helper function + void LookupProxyInfo(nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags, + nsCString* aScheme, nsIProxyInfo** outPI); + + nsresult NewChannelFromURIWithProxyFlagsInternal( + nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags, + nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + const mozilla::Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo, + const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController, + uint32_t aSecurityFlags, nsContentPolicyType aContentPolicyType, + uint32_t aSandboxFlags, bool aSkipCheckForBrokenURLOrZeroSized, + nsIChannel** result); + + nsresult NewChannelFromURIWithProxyFlagsInternal(nsIURI* aURI, + nsIURI* aProxyURI, + uint32_t aProxyFlags, + nsILoadInfo* aLoadInfo, + nsIChannel** result); + + nsresult SpeculativeConnectInternal( + nsIURI* aURI, nsIPrincipal* aPrincipal, + Maybe<OriginAttributes>&& aOriginAttributes, + nsIInterfaceRequestor* aCallbacks, bool aAnonymous); + + void DestroySocketProcess(); + + nsresult SetOfflineInternal(bool offline, bool notifySocketProcess = true); + + bool UsesExternalProtocolHandler(const nsACString& aScheme) + MOZ_REQUIRES_SHARED(mLock); + + private: + mozilla::Atomic<bool, mozilla::Relaxed> mOffline{true}; + mozilla::Atomic<bool, mozilla::Relaxed> mOfflineForProfileChange{false}; + bool mManageLinkStatus{false}; + mozilla::Atomic<bool, mozilla::Relaxed> mConnectivity{true}; + + // Used to handle SetOffline() reentrancy. See the comment in + // SetOffline() for more details. + bool mSettingOffline{false}; + bool mSetOfflineValue{false}; + + bool mSocketProcessLaunchComplete{false}; + + mozilla::Atomic<bool, mozilla::Relaxed> mShutdown{false}; + mozilla::Atomic<bool, mozilla::Relaxed> mHttpHandlerAlreadyShutingDown{false}; + + nsCOMPtr<nsPISocketTransportService> mSocketTransportService; + nsCOMPtr<nsICaptivePortalService> mCaptivePortalService; + nsCOMPtr<nsINetworkLinkService> mNetworkLinkService; + bool mNetworkLinkServiceInitialized{false}; + + // cached categories + nsCategoryCache<nsIChannelEventSink> mChannelEventSinks{ + NS_CHANNEL_EVENT_SINK_CATEGORY}; + + RWLock mLock{"nsIOService::mLock"}; + nsTArray<int32_t> mRestrictedPortList MOZ_GUARDED_BY(mLock); + nsTArray<nsCString> mForceExternalSchemes MOZ_GUARDED_BY(mLock); + nsTHashMap<nsCString, RuntimeProtocolHandler> mRuntimeProtocolHandlers + MOZ_GUARDED_BY(mLock); + + uint32_t mTotalRequests{0}; + uint32_t mCacheWon{0}; + uint32_t mNetWon{0}; + static uint32_t sSocketProcessCrashedCount; + + // These timestamps are needed for collecting telemetry on PR_Connect, + // PR_ConnectContinue and PR_Close blocking time. If we spend very long + // time in any of these functions we want to know if and what network + // change has happened shortly before. + mozilla::Atomic<PRIntervalTime> mLastOfflineStateChange; + mozilla::Atomic<PRIntervalTime> mLastConnectivityChange; + mozilla::Atomic<PRIntervalTime> mLastNetworkLinkChange; + + // Time a network tearing down started. + mozilla::Atomic<PRIntervalTime> mNetTearingDownStarted{0}; + + SocketProcessHost* mSocketProcess{nullptr}; + + // Events should be executed after the socket process is launched. Will + // dispatch these events while socket process fires OnProcessLaunchComplete. + // Note: this array is accessed only on the main thread. + nsTArray<std::function<void()>> mPendingEvents; + + // The observer notifications need to be forwarded to socket process. + nsTHashSet<nsCString> mObserverTopicForSocketProcess; + // Some noticications (e.g., NS_XPCOM_SHUTDOWN_OBSERVER_ID) are triggered in + // socket process, so we should not send the notifications again. + nsTHashSet<nsCString> mSocketProcessTopicBlockedList; + // Used to store the topics that are already observed by IOService. + nsTHashSet<nsCString> mIOServiceTopicList; + + nsCOMPtr<nsIObserverService> mObserverService; + + public: + // Used for all default buffer sizes that necko allocates. + static uint32_t gDefaultSegmentSize; + static uint32_t gDefaultSegmentCount; +}; + +/** + * Reference to the IO service singleton. May be null. + */ +extern nsIOService* gIOService; + +} // namespace net +} // namespace mozilla + +#endif // nsIOService_h__ diff --git a/netwerk/base/nsIParentChannel.idl b/netwerk/base/nsIParentChannel.idl new file mode 100644 index 0000000000..a351019d7d --- /dev/null +++ b/netwerk/base/nsIParentChannel.idl @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStreamListener.idl" +#include "nsIHttpChannel.idl" + +interface nsIRemoteTab; + +%{C++ +namespace mozilla { +namespace net { +class ParentChannelListener; +} +} +%} + +[ptr] native ParentChannelListener(mozilla::net::ParentChannelListener); + +/** + * Implemented by chrome side of IPC protocols. + */ + +[scriptable, uuid(e0fc4801-6030-4653-a59f-1fb282bd1a04)] +interface nsIParentChannel : nsIStreamListener +{ + /** + * Called to set the ParentChannelListener object (optional). + */ + [noscript] void setParentListener(in ParentChannelListener listener); + + /** + * Called to set matched information when URL matches SafeBrowsing list. + * @param aList + * Name of the list that matched + * @param aProvider + * Name of provider that matched + * @param aFullHash + * String represents full hash that matched + */ + [noscript] void setClassifierMatchedInfo(in ACString aList, + in ACString aProvider, + in ACString aFullHash); + + /** + * Called to set matched tracking information when URL matches tracking annotation list. + * @param aList + * Comma-separated list of tables that matched + * @param aFullHashes + * Comma-separated list of base64 encoded full hashes that matched + */ + [noscript] void setClassifierMatchedTrackingInfo(in ACString aLists, + in ACString aFullHashes); + + /** + * Called to notify the HttpChannelChild that the resource being loaded + * has been classified. + * @param aClassificationFlags + * What classifier identifies this channel. + * @param aIsThirdParty + * Whether or not the resourced is considered first-party + * with the URI of the window. + */ + [noscript] void notifyClassificationFlags(in uint32_t aClassificationFlags, + in bool aIsThirdParty); + + /** + * Called to invoke deletion of the IPC protocol. + */ + void delete(); + + /** + * The remote type of the target process for this load. + */ + readonly attribute AUTF8String remoteType; +}; diff --git a/netwerk/base/nsIParentRedirectingChannel.idl b/netwerk/base/nsIParentRedirectingChannel.idl new file mode 100644 index 0000000000..6302775c51 --- /dev/null +++ b/netwerk/base/nsIParentRedirectingChannel.idl @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIParentChannel.idl" + +interface nsIRemoteTab; +interface nsIChannel; +interface nsIAsyncVerifyRedirectCallback; + +[uuid(01987690-48cf-45de-bae3-e143c2adc2a8)] +interface nsIAsyncVerifyRedirectReadyCallback : nsISupports +{ + /** + * Asynchronous callback when redirected channel finishes the preparation for + * completing the verification procedure. + * + * @param result + * SUCCEEDED if preparation for redirection verification succceed. + * If FAILED the redirection must be aborted. + */ + void readyToVerify(in nsresult result); +}; + +/** + * Implemented by chrome side of IPC protocols that support redirect responses. + */ + +[scriptable, uuid(3ed1d288-5324-46ee-8a98-33ac37d1080b)] +interface nsIParentRedirectingChannel : nsIParentChannel +{ + /** + * Called when the channel got a response that redirects it to a different + * URI. The implementation is responsible for calling the redirect observers + * on the child process and provide the decision result to the callback. + * + * @param newURI + * the URI we redirect to + * @param callback + * redirect result callback, usage is compatible with how + * nsIChannelEventSink defines it + */ + void startRedirect(in nsIChannel newChannel, + in uint32_t redirectFlags, + in nsIAsyncVerifyRedirectCallback callback); + + /** + * Called to new channel when the original channel got Redirect2Verify + * response from child. Callback will be invoked when the new channel + * finishes the preparation for Redirect2Verify and can be called immediately. + * + * @param callback + * redirect ready callback, will be called when redirect verification + * procedure can proceed. + */ + void continueVerification(in nsIAsyncVerifyRedirectReadyCallback callback); + + /** + * Called after we are done with redirecting process and we know if to + * redirect or not. Forward the redirect result to the child process. From + * that moment the nsIParentChannel implementation expects it will be + * forwarded all notifications from the 'real' channel. + * + * Primarilly used by HttpChannelParent::OnRedirectResult and kept as + * mActiveChannel and mRedirectChannel in that class. + */ + void completeRedirect(in nsresult succeeded); +}; diff --git a/netwerk/base/nsIPermission.idl b/netwerk/base/nsIPermission.idl new file mode 100644 index 0000000000..029f0d8395 --- /dev/null +++ b/netwerk/base/nsIPermission.idl @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIPrincipal; +interface nsIURI; + +/** + * This interface defines a "permission" object, + * used to specify allowed/blocked objects from + * user-specified sites (cookies, images etc). + */ +[scriptable, uuid(bb409a51-2371-4fea-9dc9-b7286a458b8c)] +interface nsIPermission : nsISupports +{ + /** + * The principal for which this permission applies. + */ + readonly attribute nsIPrincipal principal; + + /** + * a case-sensitive ASCII string, indicating the type of permission + * (e.g., "cookie", "image", etc). + * This string is specified by the consumer when adding a permission + * via nsIPermissionManager. + * @see nsIPermissionManager + */ + readonly attribute ACString type; + + /** + * The permission (see nsIPermissionManager.idl for allowed values) + */ + readonly attribute uint32_t capability; + + /** + * The expiration type of the permission (session, time-based or none). + * Constants are EXPIRE_*, defined in nsIPermissionManager. + * @see nsIPermissionManager + */ + readonly attribute uint32_t expireType; + + /** + * The expiration time of the permission (milliseconds since Jan 1 1970 + * 0:00:00). + */ + readonly attribute int64_t expireTime; + + /** + * The last modification time of the permission (milliseconds since Jan 1 1970 + * 0:00:00). + */ + readonly attribute int64_t modificationTime; + + /** + * Test whether a principal would be affected by this permission. + * + * @param principal the principal to test + * @param exactHost If true, only the specific host will be matched, + * @see nsIPermissionManager::testExactPermission. + * If false, subdomains will also be searched, + * @see nsIPermissionManager::testPermission. + */ + boolean matches(in nsIPrincipal principal, + in boolean exactHost); + + /** + * Test whether a URI would be affected by this permission. + * NOTE: This performs matches with default origin attribute values. + * + * @param uri the uri to test + * @param exactHost If true, only the specific host will be matched, + * @see nsIPermissionManager::testExactPermission. + * If false, subdomains will also be searched, + * @see nsIPermissionManager::testPermission. + */ + boolean matchesURI(in nsIURI uri, + in boolean exactHost); +}; diff --git a/netwerk/base/nsIPermissionManager.idl b/netwerk/base/nsIPermissionManager.idl new file mode 100644 index 0000000000..f234958010 --- /dev/null +++ b/netwerk/base/nsIPermissionManager.idl @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file contains an interface to the Permission Manager, + * used to persistenly store permissions for different object types (cookies, + * images etc) on a site-by-site basis. + * + * This service broadcasts the following notification when the permission list + * is changed: + * + * topic : "perm-changed" (PERM_CHANGE_NOTIFICATION) + * broadcast whenever the permission list changes in some way. there + * are four possible data strings for this notification; one + * notification will be broadcast for each change, and will involve + * a single permission. + * subject: an nsIPermission interface pointer representing the permission object + * that changed. + * data : "deleted" + * a permission was deleted. the subject is the deleted permission. + * "added" + * a permission was added. the subject is the added permission. + * "changed" + * a permission was changed. the subject is the new permission. + * "cleared" + * the entire permission list was cleared. the subject is null. + */ + +#include "nsISupports.idl" + +interface nsIPrincipal; +interface nsIPermission; + +[scriptable, builtinclass, uuid(4dcb3851-eba2-4e42-b236-82d2596fca22)] +interface nsIPermissionManager : nsISupports +{ + /** + * Predefined return values for the testPermission method and for + * the permission param of the add method + * NOTE: UNKNOWN_ACTION (0) is reserved to represent the + * default permission when no entry is found for a host, and + * should not be used by consumers to indicate otherwise. + */ + const uint32_t UNKNOWN_ACTION = 0; + const uint32_t ALLOW_ACTION = 1; + const uint32_t DENY_ACTION = 2; + const uint32_t PROMPT_ACTION = 3; + + /** + * Predefined expiration types for permissions. Permissions can be permanent + * (never expire), expire at the end of the session, or expire at a specified + * time. Permissions that expire at the end of a session may also have a + * specified expiration time. + * + * EXPIRE_POLICY is a special expiration status. It is set when the permission + * is set by reading an enterprise policy. These permissions cannot be overridden. + */ + const uint32_t EXPIRE_NEVER = 0; + const uint32_t EXPIRE_SESSION = 1; + const uint32_t EXPIRE_TIME = 2; + const uint32_t EXPIRE_POLICY = 3; + + + /** + * Get all custom permissions for a given nsIPrincipal. This will return an + * enumerator of all permissions which are not set to default and which + * belong to the matching principal of the given nsIPrincipal. + * + * @param principal the URI to get all permissions for + */ + Array<nsIPermission> getAllForPrincipal(in nsIPrincipal principal); + + /** + * Get all custom permissions of a specific type, specified with a prefix + * string. This will return an array of all permissions which are not set to + * default. Also the passed type argument is either equal to or a prefix of + * the type of the returned permissions. + * + * @param prefix the type prefix string + */ + Array<nsIPermission> getAllWithTypePrefix(in ACString prefix); + + + /** + * Get all custom permissions whose type exactly match one of the types defined + * in the passed array argument. + * This will return an array of all permissions which are not set to default. + * + * @param types an array of case-sensitive ASCII strings, identifying the + * permissions to be matched. + */ + Array<nsIPermission> getAllByTypes(in Array<ACString> types); + + /** + * Get all custom permissions of a specific type and that were modified after + * the specified date. This will return an array of all permissions which are + * not set to default. + * + * @param type a case-sensitive ASCII string, identifying the permission. + * @param since a unix timestamp representing the number of milliseconds from + * Jan 1, 1970 00:00:00 UTC. + */ + Array<nsIPermission> getAllByTypeSince(in ACString type, in int64_t since); + + /** + * Add permission information for a given principal. + * It is internally calling the other add() method using the nsIURI from the + * principal. + * Passing a system principal will be a no-op because they will always be + * granted permissions. + */ + void addFromPrincipal(in nsIPrincipal principal, in ACString type, + in uint32_t permission, + [optional] in uint32_t expireType, + [optional] in int64_t expireTime); + + /** + * Add permanent permission information for a given principal in private + * browsing. + * + * Normally permissions in private browsing are cleared at the end of the + * session, this method allows you to override this behavior and set + * permanent permissions. + * + * WARNING: setting permanent permissions _will_ leak data in private + * browsing. Only use if you understand the consequences and trade-offs. If + * you are unsure, |addFromPrincipal| is very likely what you want to use + * instead. + */ + void addFromPrincipalAndPersistInPrivateBrowsing(in nsIPrincipal principal, + in ACString type, + in uint32_t permission); + + /** + * Remove permission information for a given principal. + * This is internally calling remove() with the host from the principal's URI. + * Passing system principal will be a no-op because we never add them to the + * database. + */ + void removeFromPrincipal(in nsIPrincipal principal, in ACString type); + + /** + * Remove the given permission from the permission manager. + * + * @param perm a permission obtained from the permission manager. + */ + void removePermission(in nsIPermission perm); + + /** + * Clear permission information for all websites. + */ + void removeAll(); + + /** + * Clear all permission information added since the specified time. + */ + void removeAllSince(in int64_t since); + + /** + * Clear all permissions of the passed type. + */ + void removeByType(in ACString type); + + /** + * Clear all permissions of the passed type added since the specified time. + * @param type a case-sensitive ASCII string, identifying the permission. + * @param since a unix timestamp representing the number of milliseconds from + * Jan 1, 1970 00:00:00 UTC. + */ + void removeByTypeSince(in ACString type, in int64_t since); + + /** + * Test whether the principal has the permission to perform a given action. + * System principals will always have permissions granted. + * This function will perform a pref lookup to permissions.default.<type> + * if the specific permission type is part of the whitelist for that functionality. + */ + uint32_t testPermissionFromPrincipal(in nsIPrincipal principal, + in ACString type); + + /** + * Test whether the principal has the permission to perform a given action. + * This requires an exact hostname match. Subdomain principals do not match + * permissions of base domains. + * System principals will always have permissions granted. + * This function will perform a pref lookup to permissions.default.<type> + * if the specific permission type is part of the whitelist for that functionality. + */ + uint32_t testExactPermissionFromPrincipal(in nsIPrincipal principal, + in ACString type); + + /** + * Test whether a website has permission to perform the given action + * ignoring active sessions. + * System principals will always have permissions granted. + * This function will perform a pref lookup to permissions.default.<type> + * if the specific permission type is part of the whitelist for that functionality. + * + * @param principal the principal + * @param type a case-sensitive ASCII string, identifying the consumer + * @param return see add(), param permission. returns UNKNOWN_ACTION when + * there is no stored permission for this uri and / or type. + */ + uint32_t testExactPermanentPermission(in nsIPrincipal principal, + in ACString type); + + /** + * Get the permission object associated with the given principal and action. + * @param principal The principal + * @param type A case-sensitive ASCII string identifying the consumer + * @param exactHost If true, only the specific host will be matched. + * If false, base domains of the principal will also + * be searched. + * @returns The matching permission object, or null if no matching object + * was found. No matching object is equivalent to UNKNOWN_ACTION. + * @note Clients in general should prefer the test* methods unless they + * need to know the specific stored details. + * @note This method will always return null for the system principal. + */ + nsIPermission getPermissionObject(in nsIPrincipal principal, + in ACString type, + in boolean exactHost); + + /** + * Returns all stored permissions. + * @return an array of nsIPermission objects + */ + readonly attribute Array<nsIPermission> all; + + /** + * Remove all permissions that will match the origin pattern. + */ + void removePermissionsWithAttributes(in AString patternAsJSON); +}; + +%{ C++ +#define NS_PERMISSIONMANAGER_CONTRACTID "@mozilla.org/permissionmanager;1" + +#define PERM_CHANGE_NOTIFICATION "perm-changed" +%} diff --git a/netwerk/base/nsIPrivateBrowsingChannel.idl b/netwerk/base/nsIPrivateBrowsingChannel.idl new file mode 100644 index 0000000000..3bc822f018 --- /dev/null +++ b/netwerk/base/nsIPrivateBrowsingChannel.idl @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface is implemented by channels which support overriding the + * privacy state of the channel. + * + * This interface must be used only from the XPCOM main thread. + */ +[scriptable, uuid(df702bb0-55b8-11e2-bcfd-0800200c9a66)] +interface nsIPrivateBrowsingChannel : nsISupports +{ + /** + * Determine whether the channel is tied to a private browsing window. + * + * This value can be set only before the channel is opened. Setting it + * after that does not have any effect. This value overrides the privacy + * state of the channel, which means that if you call this method, then + * the loadGroup and load context will no longer be consulted when we + * need to know the private mode status for a channel. + * + * Note that this value is only meant to be used when the channel's privacy + * status cannot be obtained from the loadGroup or load context (for + * example, when the channel is not associated with any loadGroup or load + * context.) Setting this value directly should be avoided if possible. + * + * Implementations must enforce the ordering semantics of this function by + * raising errors if setPrivate is called on a channel which has a loadGroup + * and/or callbacks that implement nsILoadContext, or if the loadGroup + * or notificationCallbacks are set after setPrivate has been called. + * + * @param aPrivate whether the channel should be opened in private mode. + */ + void setPrivate(in boolean aPrivate); + + /** + * States whether the channel is in private browsing mode. This may either + * happen because the channel is opened from a private mode context or + * when the mode is explicitly set with ::setPrivate(). + * + * This attribute is equivalent to NS_UsePrivateBrowsing(), but scriptable. + */ + readonly attribute boolean isChannelPrivate; + + /* + * This function is used to determine whether the channel's private mode + * has been overridden by a call to setPrivate. It is intended to be used + * by NS_UsePrivateBrowsing(), and you should not call it directly. + * + * @param aValue the overridden value. This will only be set if the function + * returns true. + */ + [noscript] boolean isPrivateModeOverriden(out boolean aValue); +}; diff --git a/netwerk/base/nsIProgressEventSink.idl b/netwerk/base/nsIProgressEventSink.idl new file mode 100644 index 0000000000..5420239e4c --- /dev/null +++ b/netwerk/base/nsIProgressEventSink.idl @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; +interface nsIRequest; + +/** + * nsIProgressEventSink + * + * This interface is used to asynchronously convey channel status and progress + * information that is generally not critical to the processing of the channel. + * The information is intended to be displayed to the user in some meaningful + * way. + * + * An implementation of this interface can be passed to a channel via the + * channel's notificationCallbacks attribute. See nsIChannel for more info. + * + * The channel will begin passing notifications to the progress event sink + * after its asyncOpen method has been called. Notifications will cease once + * the channel calls its listener's onStopRequest method or once the channel + * is canceled (via nsIRequest::cancel). + * + * NOTE: This interface is actually not specific to channels and may be used + * with other implementations of nsIRequest. + */ +[scriptable, uuid(87d55fba-cb7e-4f38-84c1-5c6c2b2a55e9)] +interface nsIProgressEventSink : nsISupports +{ + /** + * Called to notify the event sink that progress has occurred for the + * given request. + * + * @param aRequest + * the request being observed (may QI to nsIChannel). + * @param aProgress + * numeric value in the range 0 to aProgressMax indicating the + * number of bytes transfered thus far. + * @param aProgressMax + * numeric value indicating maximum number of bytes that will be + * transfered (or -1 if total is unknown). + */ + void onProgress(in nsIRequest aRequest, + in long long aProgress, + in long long aProgressMax); + + /** + * Called to notify the event sink with a status message for the given + * request. + * + * @param aRequest + * the request being observed (may QI to nsIChannel). + * @param aStatus + * status code (not necessarily an error code) indicating the + * state of the channel (usually the state of the underlying + * transport). see nsISocketTransport for socket specific status + * codes. + * @param aStatusArg + * status code argument to be used with the string bundle service + * to convert the status message into localized, human readable + * text. the meaning of this parameter is specific to the value + * of the status code. for socket status codes, this parameter + * indicates the host:port associated with the status code. + */ + void onStatus(in nsIRequest aRequest, + in nsresult aStatus, + in wstring aStatusArg); + +}; diff --git a/netwerk/base/nsIPrompt.idl b/netwerk/base/nsIPrompt.idl new file mode 100644 index 0000000000..6fbed3355d --- /dev/null +++ b/netwerk/base/nsIPrompt.idl @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This is the prompt interface which can be used without knowlege of a + * parent window. The parentage is hidden by the GetInterface though + * which it is gotten. This interface is identical to nsIPromptService + * but without the parent nsIDOMWindow parameter. See nsIPromptService + * for all documentation. + * + * Accesskeys can be attached to buttons and checkboxes by inserting + * an & before the accesskey character. For a real &, use && instead. + */ + +#include "nsISupports.idl" + +[scriptable, uuid(a63f70c0-148b-11d3-9333-00104ba0fd40)] +interface nsIPrompt : nsISupports +{ + void alert(in wstring dialogTitle, + in wstring text); + + void alertCheck(in wstring dialogTitle, + in wstring text, + in wstring checkMsg, + inout boolean checkValue); + + boolean confirm(in wstring dialogTitle, + in wstring text); + + boolean confirmCheck(in wstring dialogTitle, + in wstring text, + in wstring checkMsg, + inout boolean checkValue); + + const unsigned long BUTTON_POS_0 = 1; + const unsigned long BUTTON_POS_1 = 1 << 8; + const unsigned long BUTTON_POS_2 = 1 << 16; + + const unsigned long BUTTON_TITLE_OK = 1; + const unsigned long BUTTON_TITLE_CANCEL = 2; + const unsigned long BUTTON_TITLE_YES = 3; + const unsigned long BUTTON_TITLE_NO = 4; + const unsigned long BUTTON_TITLE_SAVE = 5; + const unsigned long BUTTON_TITLE_DONT_SAVE = 6; + const unsigned long BUTTON_TITLE_REVERT = 7; + + const unsigned long BUTTON_TITLE_IS_STRING = 127; + + const unsigned long BUTTON_POS_0_DEFAULT = 0 << 24; + const unsigned long BUTTON_POS_1_DEFAULT = 1 << 24; + const unsigned long BUTTON_POS_2_DEFAULT = 2 << 24; + + /* used for security dialogs, buttons are initially disabled */ + const unsigned long BUTTON_DELAY_ENABLE = 1 << 26; + + const unsigned long SHOW_SPINNER = 1 << 27; + + const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) + + (BUTTON_TITLE_CANCEL * BUTTON_POS_1); + const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) + + (BUTTON_TITLE_NO * BUTTON_POS_1); + + + // Indicates whether a prompt should be shown in-content, on tab level or as a separate window + const unsigned long MODAL_TYPE_CONTENT = 1; + const unsigned long MODAL_TYPE_TAB = 2; + const unsigned long MODAL_TYPE_WINDOW = 3; + // Like MODAL_TYPE_WINDOW, but shown inside a parent window (with similar + // styles as _TAB and _CONTENT types) rather than as a new window: + const unsigned long MODAL_TYPE_INTERNAL_WINDOW = 4; + + int32_t confirmEx(in wstring dialogTitle, + in wstring text, + in unsigned long buttonFlags, + in wstring button0Title, + in wstring button1Title, + in wstring button2Title, + in wstring checkMsg, + inout boolean checkValue); + + boolean prompt(in wstring dialogTitle, + in wstring text, + inout wstring value, + in wstring checkMsg, + inout boolean checkValue); + + boolean promptPassword(in wstring dialogTitle, + in wstring text, + inout wstring password); + + boolean promptUsernameAndPassword(in wstring dialogTitle, + in wstring text, + inout wstring username, + inout wstring password); + + boolean select(in wstring dialogTitle, + in wstring text, + in Array<AString> selectList, + out long outSelection); +}; diff --git a/netwerk/base/nsIProtocolHandler.idl b/netwerk/base/nsIProtocolHandler.idl new file mode 100644 index 0000000000..890f3ef02f --- /dev/null +++ b/netwerk/base/nsIProtocolHandler.idl @@ -0,0 +1,317 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#include "nsCOMPtr.h" + +/** + * Protocol handlers are registered with XPCOM under the following CONTRACTID prefix: + */ +#define NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "@mozilla.org/network/protocol;1?name=" +/** + * For example, "@mozilla.org/network/protocol;1?name=http" + */ + +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) +#define IS_ORIGIN_IS_FULL_SPEC_DEFINED 1 +#endif +%} + +interface nsIURI; +interface nsIChannel; +interface nsILoadInfo; + +/** + * nsIProtocolHandlerWithDynamicFlags + * + * Protocols that wish to return different flags depending on the URI should + * implement this interface. + */ +[scriptable, builtinclass, uuid(65a8e823-0591-4fc0-a56a-03265e0a4ce8)] +interface nsIProtocolHandlerWithDynamicFlags : nsISupports +{ + /* + * Returns protocol flags for the given URI, which may be different from the + * flags for another URI of the same scheme. + * + * Only DYNAMIC_URI_FLAGS may be different from the registered flags for the + * protocol handler. + */ + unsigned long getFlagsForURI(in nsIURI aURI); +}; + +/** + * nsIProtocolHandler + */ +[scriptable, uuid(a87210e6-7c8c-41f7-864d-df809015193e)] +interface nsIProtocolHandler : nsISupports +{ + /** + * The scheme of this protocol (e.g., "file"). + */ + readonly attribute ACString scheme; + + /** + * Constructs a new channel from the given URI for this protocol handler and + * sets the loadInfo for the constructed channel. + */ + nsIChannel newChannel(in nsIURI aURI, in nsILoadInfo aLoadinfo); + + /** + * Allows a protocol to override blacklisted ports. + * + * This method will be called when there is an attempt to connect to a port + * that is blacklisted. For example, for most protocols, port 25 (Simple Mail + * Transfer) is banned. When a URI containing this "known-to-do-bad-things" + * port number is encountered, this function will be called to ask if the + * protocol handler wants to override the ban. + */ + boolean allowPort(in long port, in string scheme); + + + /************************************************************************** + * Constants for the protocol flags (the first is the default mask, the + * others are deviations): + * + * NOTE: Protocol flags are provided when the protocol handler is + * registered, either through a static component or dynamically with + * `nsIIOService.registerProtocolHandler`. + * + * NOTE: Implementation must ignore any flags they do not understand. + */ + + /** + * standard full URI with authority component and concept of relative + * URIs (http, ...) + */ + const unsigned long URI_STD = 0; + + /** + * no concept of relative URIs (about, javascript, finger, ...) + */ + const unsigned long URI_NORELATIVE = (1<<0); + + /** + * no authority component (file, ...) + */ + const unsigned long URI_NOAUTH = (1<<1); + + /** + * This protocol handler can be proxied via a proxy (socks or http) + * (e.g., irc, smtp, http, etc.). If the protocol supports transparent + * proxying, the handler should implement nsIProxiedProtocolHandler. + * + * If it supports only HTTP proxying, then it need not support + * nsIProxiedProtocolHandler, but should instead set the ALLOWS_PROXY_HTTP + * flag (see below). + * + * @see nsIProxiedProtocolHandler + */ + const unsigned long ALLOWS_PROXY = (1<<2); + + /** + * This protocol handler can be proxied using a http proxy (e.g., http, + * etc.). nsIIOService::newChannelFromURI will feed URIs from this + * protocol handler to the HTTP protocol handler instead. This flag is + * ignored if ALLOWS_PROXY is not set. + */ + const unsigned long ALLOWS_PROXY_HTTP = (1<<3); + + /** + * The URIs for this protocol have no inherent security context, so + * documents loaded via this protocol should inherit the security context + * from the document that loads them. + */ + const unsigned long URI_INHERITS_SECURITY_CONTEXT = (1<<4); + + /** + * "Automatic" loads that would replace the document (e.g. <meta> refresh, + * certain types of XLinks, possibly other loads that the application + * decides are not user triggered) are not allowed if the originating (NOT + * the target) URI has this protocol flag. Note that the decision as to + * what constitutes an "automatic" load is made externally, by the caller + * of nsIScriptSecurityManager::CheckLoadURI. See documentation for that + * method for more information. + * + * A typical protocol that might want to set this flag is a protocol that + * shows highly untrusted content in a viewing area that the user expects + * to have a lot of control over, such as an e-mail reader. + */ + const unsigned long URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT = (1<<5); + + /** + * +-------------------------------------------------------------------+ + * | | + * | ALL PROTOCOL HANDLERS MUST SET ONE OF THE FOLLOWING FIVE FLAGS. | + * | | + * +-------------------------------------------------------------------+ + * + * * URI_LOADABLE_BY_ANYONE + * * URI_DANGEROUS_TO_LOAD + * * URI_IS_UI_RESOURCE + * * URI_IS_LOCAL_FILE + * * URI_LOADABLE_BY_SUBSUMERS + * + * These flags are used to determine who is allowed to load URIs for this + * protocol. Note that if a URI is nested, only the flags for the + * innermost URI matter. See nsINestedURI. + * + * If none of these five flags are set, the ContentSecurityManager will + * deny the load. + */ + + /** + * The URIs for this protocol can be loaded by anyone. For example, any + * website should be allowed to trigger a load of a URI for this protocol. + * Web-safe protocols like "http" should set this flag. + */ + const unsigned long URI_LOADABLE_BY_ANYONE = (1<<6); + + /** + * The URIs for this protocol are UNSAFE if loaded by untrusted (web) + * content and may only be loaded by privileged code (for example, code + * which has the system principal). Various internal protocols should set + * this flag. + */ + const unsigned long URI_DANGEROUS_TO_LOAD = (1<<7); + + /** + * The URIs for this protocol point to resources that are part of the + * application's user interface. There are cases when such resources may + * be made accessible to untrusted content such as web pages, so this is + * less restrictive than URI_DANGEROUS_TO_LOAD but more restrictive than + * URI_LOADABLE_BY_ANYONE. See the documentation for + * nsIScriptSecurityManager::CheckLoadURI. + */ + const unsigned long URI_IS_UI_RESOURCE = (1<<8); + + /** + * Loading of URIs for this protocol from other origins should only be + * allowed if those origins should have access to the local filesystem. + * It's up to the application to decide what origins should have such + * access. Protocols like "file" that point to local data should set this + * flag. + */ + const unsigned long URI_IS_LOCAL_FILE = (1<<9); + + /** + * The URIs for this protocol can be loaded only by callers with a + * principal that subsumes this uri. For example, privileged code and + * websites that are same origin as this uri. + */ + const unsigned long URI_LOADABLE_BY_SUBSUMERS = (1<<10); + + /** + * Channels using this protocol never call OnDataAvailable + * on the listener passed to AsyncOpen and they therefore + * do not return any data that we can use. + */ + const unsigned long URI_DOES_NOT_RETURN_DATA = (1<<11); + + /** + * URIs for this protocol are considered to be local resources. This could + * be a local file (URI_IS_LOCAL_FILE), a UI resource (URI_IS_UI_RESOURCE), + * or something else that would not hit the network. + */ + const unsigned long URI_IS_LOCAL_RESOURCE = (1<<12); + + /** + * URIs for this protocol execute script when they are opened. + */ + const unsigned long URI_OPENING_EXECUTES_SCRIPT = (1<<13); + + /** + * Loading channels from this protocol has side-effects that make + * it unsuitable for saving to a local file. + */ + const unsigned long URI_NON_PERSISTABLE = (1<<14); + + /** + * URIs for this protocol require the webapps permission on the principal + * when opening URIs for a different domain. See bug#773886 + */ + const unsigned long URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM = (1<<15); + + /** + * Channels for this protocol don't need to spin the event loop to handle + * Open() and reads on the resulting stream. + */ + const unsigned long URI_SYNC_LOAD_IS_OK = (1<<16); + + /** + * All the origins whose URI has this scheme are considered potentially + * trustworthy. + * Per the SecureContext spec, https: and wss: should be considered + * a priori secure, and implementations may consider other, + * implementation-specific URI schemes as secure. + */ + const unsigned long URI_IS_POTENTIALLY_TRUSTWORTHY = (1<<17); + + /** + * This URI may be fetched and the contents are visible to anyone. This is + * semantically equivalent to the resource being served with all-access CORS + * headers. This is only used in MV2 Extensions and should not otherwise + * be used. + */ + const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 18); + + /** + * If this flag is set, then the origin for this protocol is the full URI + * spec, not just the scheme + host + port. + * + * Note: this is not supported in Firefox. It is currently only available + * in Thunderbird and SeaMonkey. + */ + const unsigned long ORIGIN_IS_FULL_SPEC = (1 << 19); + + /** + * If this flag is set, the URI does not always allow content using the same + * protocol to link to it. + */ + const unsigned long URI_SCHEME_NOT_SELF_LINKABLE = (1 << 20); + + /** + * The URIs for this protocol can be loaded by extensions. + */ + const unsigned long URI_LOADABLE_BY_EXTENSIONS = (1 << 21); + + /** + * The URIs for this protocol can not be loaded into private contexts. + */ + const unsigned long URI_DISALLOW_IN_PRIVATE_CONTEXT = (1 << 22); + + /** + * This protocol handler forbids accessing cookies e.g. for mail related + * protocols. Only used in Mailnews (comm-central). + */ + const unsigned long URI_FORBIDS_COOKIE_ACCESS = (1 << 23); + + /** + * This is an extension web accessible uri that is loadable if checked + * against an allowlist using ExtensionPolicyService::SourceMayLoadExtensionURI. + */ + const unsigned long WEBEXT_URI_WEB_ACCESSIBLE = (1 << 24); + + /** + * This URI has a webexposed origin, meaning the URI has a non-null origin + * See https://url.spec.whatwg.org/#origin + */ + const unsigned long URI_HAS_WEB_EXPOSED_ORIGIN = (1 << 25); + + /** + * Flags which are allowed to be different from the static flags when + * returned from `nsIProtocolHandlerWithDynamicFlags::getFlagsForURI`. + * + * All other flags must match the flags provided when the protocol handler + * was registered. + */ + const unsigned long DYNAMIC_URI_FLAGS = + URI_LOADABLE_BY_ANYONE | URI_DANGEROUS_TO_LOAD | + URI_IS_POTENTIALLY_TRUSTWORTHY | URI_FETCHABLE_BY_ANYONE | + URI_LOADABLE_BY_EXTENSIONS | URI_DISALLOW_IN_PRIVATE_CONTEXT | + WEBEXT_URI_WEB_ACCESSIBLE | URI_HAS_WEB_EXPOSED_ORIGIN; +}; diff --git a/netwerk/base/nsIProtocolProxyCallback.idl b/netwerk/base/nsIProtocolProxyCallback.idl new file mode 100644 index 0000000000..96c2181eca --- /dev/null +++ b/netwerk/base/nsIProtocolProxyCallback.idl @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIChannel; +interface nsIProxyInfo; +interface nsICancelable; + +/** + * This interface serves as a closure for nsIProtocolProxyService's + * asyncResolve method. + */ +[scriptable, uuid(fbb6eff6-0cc2-4d99-8d6f-0a12b462bdeb)] +interface nsIProtocolProxyCallback : nsISupports +{ + /** + * This method is called when proxy info is available or when an error + * in the proxy resolution occurs. + * + * @param aRequest + * The value returned from asyncResolve. + * @param aChannel + * The channel passed to asyncResolve. + * @param aProxyInfo + * The resulting proxy info or null if there is no associated proxy + * info for aURI. As with the result of nsIProtocolProxyService's + * resolve method, a null result implies that a direct connection + * should be used. + * @param aStatus + * The status of the callback. This is a failure code if the request + * could not be satisfied, in which case the value of aStatus + * indicates the reason for the failure and aProxyInfo will be null. + */ + void onProxyAvailable(in nsICancelable aRequest, + in nsIChannel aChannel, + in nsIProxyInfo aProxyInfo, + in nsresult aStatus); +}; diff --git a/netwerk/base/nsIProtocolProxyFilter.idl b/netwerk/base/nsIProtocolProxyFilter.idl new file mode 100644 index 0000000000..a771af5f43 --- /dev/null +++ b/netwerk/base/nsIProtocolProxyFilter.idl @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIChannel; +interface nsIProtocolProxyService; +interface nsIProxyInfo; +interface nsIURI; + +/** + * Recipient of the result of implementers of nsIProtocolProxy(Channel)Filter + * allowing the proxyinfo be provided asynchronously. + */ +[scriptable, uuid(009E6C3F-FB64-40C5-8093-F1495C64773E)] +interface nsIProxyProtocolFilterResult : nsISupports +{ + /** + * It's mandatory to call this method exactly once when the applyFilter() + * implementation doesn't throw and to not call it when applyFilter() does + * throw. + * + * It's mandatory to call this method on the same thread as the call to + * applyFilter() has been made on. + * + * Following the above conditions, can be called either from within + * applyFilter() or asynchronouly any time later. + */ + void onProxyFilterResult(in nsIProxyInfo aProxy); +}; + +/** + * This interface is used to apply filters to the proxies selected for a given + * URI. Use nsIProtocolProxyService::registerFilter to hook up instances of + * this interface. See also nsIProtocolProxyChannelFilter. + */ +[scriptable, uuid(f424abd3-32b4-456c-9f45-b7e3376cb0d1)] +interface nsIProtocolProxyFilter : nsISupports +{ + /** + * This method is called to apply proxy filter rules for the given URI + * and proxy object (or list of proxy objects). + * + * @param aURI + * The URI for which these proxy settings apply. + * @param aProxy + * The proxy (or list of proxies) that would be used by default for + * the given URI. This may be null. + * + * @param aCallback + * An object that the implementer is obligated to call on with + * the result (from within applyFilter() or asynchronously) when + * applyFilter didn't throw. The argument passed to onProxyFilterResult + * is the proxy (or list of proxies) that should be used in place of + * aProxy. This can be just be aProxy if the filter chooses not to + * modify the proxy. It can also be null to indicate that a direct + * connection should be used. Use nsIProtocolProxyService.newProxyInfo + * to construct nsIProxyInfo objects. + */ + void applyFilter(in nsIURI aURI, in nsIProxyInfo aProxy, + in nsIProxyProtocolFilterResult aCallback); +}; + +/** + * This interface is used to apply filters to the proxies selected for a given + * channel. Use nsIProtocolProxyService::registerChannelFilter to hook up instances of + * this interface. See also nsIProtocolProxyFilter. + */ +[scriptable, uuid(245b0880-82c5-4e6e-be6d-bc586aa55a90)] +interface nsIProtocolProxyChannelFilter : nsISupports +{ + /** + * This method is called to apply proxy filter rules for the given channel + * and proxy object (or list of proxy objects). + * + * @param aChannel + * The channel for which these proxy settings apply. + * @param aProxy + * The proxy (or list of proxies) that would be used by default for + * the given channel. This may be null. + * + * @param aCallback + * An object that the implementer is obligated to call on with + * the result (from within applyFilter() or asynchronously) when + * applyFilter didn't throw. The argument passed to onProxyFilterResult + * is the proxy (or list of proxies) that should be used in place of + * aProxy. This can be just be aProxy if the filter chooses not to + * modify the proxy. It can also be null to indicate that a direct + * connection should be used. Use nsIProtocolProxyService.newProxyInfo + * to construct nsIProxyInfo objects. + */ + void applyFilter(in nsIChannel aChannel, in nsIProxyInfo aProxy, + in nsIProxyProtocolFilterResult aCallback); +}; diff --git a/netwerk/base/nsIProtocolProxyService.idl b/netwerk/base/nsIProtocolProxyService.idl new file mode 100644 index 0000000000..9bd65f5084 --- /dev/null +++ b/netwerk/base/nsIProtocolProxyService.idl @@ -0,0 +1,330 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsICancelable; +interface nsIProtocolProxyCallback; +interface nsIProtocolProxyFilter; +interface nsIProtocolProxyChannelFilter; +interface nsIProxyInfo; +interface nsIChannel; +interface nsIURI; +interface nsISerialEventTarget; + +[scriptable, uuid(77984234-aad5-47fc-a412-03398c2134a5)] +interface nsIProxyConfigChangedCallback : nsISupports +{ + /** + * Called when one of the following conditions are changed. + * 1. System proxy settings changed. + * 2. A proxy filter is registered or unregistered. + * 3. Proxy related prefs changed. + */ + void onProxyConfigChanged(); +}; + +/** + * nsIProtocolProxyService provides methods to access information about + * various network proxies. + */ +[scriptable, builtinclass, uuid(ef57c8b6-e09d-4cd4-9222-2a5d2402e15d)] +interface nsIProtocolProxyService : nsISupports +{ + /** Flag 1 << 0 is unused **/ + + /** + * When the proxy configuration is manual this flag may be passed to the + * resolve and asyncResolve methods to request to prefer the SOCKS proxy + * to HTTP ones. + */ + const unsigned long RESOLVE_PREFER_SOCKS_PROXY = 1 << 1; + + /** + * When the proxy configuration is manual this flag may be passed to the + * resolve and asyncResolve methods to request to not analyze the uri's + * scheme specific proxy. When this flag is set the main HTTP proxy is the + * preferred one. + * + * NOTE: if RESOLVE_PREFER_SOCKS_PROXY is set then the SOCKS proxy is + * the preferred one. + * + * NOTE: if RESOLVE_PREFER_HTTPS_PROXY is set then the HTTPS proxy + * is the preferred one. + */ + const unsigned long RESOLVE_IGNORE_URI_SCHEME = 1 << 2; + + /** + * When the proxy configuration is manual this flag may be passed to the + * resolve and asyncResolve methods to request to prefer the HTTPS proxy + * to the others HTTP ones. + * + * NOTE: RESOLVE_PREFER_SOCKS_PROXY takes precedence over this flag. + * + * NOTE: This flag implies RESOLVE_IGNORE_URI_SCHEME. + */ + const unsigned long RESOLVE_PREFER_HTTPS_PROXY = + (1 << 3) | RESOLVE_IGNORE_URI_SCHEME; + + /** + * When the proxy configuration is manual this flag may be passed to the + * resolve and asyncResolve methods to that all methods will be tunneled via + * CONNECT through the http proxy. + */ + const unsigned long RESOLVE_ALWAYS_TUNNEL = (1 << 4); + + /** + * This method returns via callback a nsIProxyInfo instance that identifies + * a proxy to be used for the given channel. Otherwise, this method returns + * null indicating that a direct connection should be used. + * + * @param aChannelOrURI + * The channel for which a proxy is to be found, or, if no channel is + * available, a URI indicating the same. This method will return + * NS_ERROR_NOINTERFACE if this argument isn't either an nsIURI or an + * nsIChannel. + * @param aFlags + * A bit-wise combination of the RESOLVE_ flags defined above. Pass + * 0 to specify the default behavior. Any additional bits that do + * not correspond to a RESOLVE_ flag are reserved for future use. + * @param aCallback + * The object to be notified when the result is available. + * @param aMainThreadTarget + * A labelled event target for dispatching runnables to main thread. + * + * @return An object that can be used to cancel the asychronous operation. + * If canceled, the cancelation status (aReason) will be forwarded + * to the callback's onProxyAvailable method via the aStatus param. + * + * NOTE: If this proxy is unavailable, getFailoverForProxy may be called + * to determine the correct secondary proxy to be used. + * + * NOTE: If the protocol handler for the given URI supports + * nsIProxiedProtocolHandler, then the nsIProxyInfo instance returned from + * resolve may be passed to the newProxiedChannel method to create a + * nsIChannel to the given URI that uses the specified proxy. + * + * NOTE: However, if the nsIProxyInfo type is "http", then it means that + * the given URI should be loaded using the HTTP protocol handler, which + * also supports nsIProxiedProtocolHandler. + * + * @see nsIProxiedProtocolHandler::newProxiedChannel + */ + nsICancelable asyncResolve( + in nsISupports aChannelOrURI, in unsigned long aFlags, + in nsIProtocolProxyCallback aCallback, + [optional] in nsISerialEventTarget aMainThreadTarget); + + /** + * This method may be called to construct a nsIProxyInfo instance from + * the given parameters. This method may be useful in conjunction with + * nsISocketTransportService::createTransport for creating, for example, + * a SOCKS connection. + * + * @param aType + * The proxy type. This is a string value that identifies the proxy + * type. Standard values include: + * "http" - specifies a HTTP proxy + * "https" - specifies HTTP proxying over TLS connection to proxy + * "socks" - specifies a SOCKS version 5 proxy + * "socks4" - specifies a SOCKS version 4 proxy + * "direct" - specifies a direct connection (useful for failover) + * The type name is case-insensitive. Other string values may be + * possible, and new types may be defined by a future version of + * this interface. + * @param aHost + * The proxy hostname or IP address. + * @param aPort + * The proxy port. + * @param aFlags + * Flags associated with this connection. See nsIProxyInfo.idl + * for currently defined flags. + * @param aFailoverTimeout + * Specifies the length of time (in seconds) to ignore this proxy if + * this proxy fails. Pass UINT32_MAX to specify the default + * timeout value, causing nsIProxyInfo::failoverTimeout to be + * assigned the default value. + * @param aFailoverProxy + * Specifies the next proxy to try if this proxy fails. This + * parameter may be null. + */ + nsIProxyInfo newProxyInfo(in ACString aType, in AUTF8String aHost, + in long aPort, + in ACString aProxyAuthorizationHeader, + in ACString aConnectionIsolationKey, + in unsigned long aFlags, + in unsigned long aFailoverTimeout, + in nsIProxyInfo aFailoverProxy); + + /** + * This method may be called to construct a nsIProxyInfo instance for + * with the specified username and password. + * Currently implemented for SOCKS proxies only. + * @param aType + * The proxy type. This is a string value that identifies the proxy + * type. Standard values include: + * "socks" - specifies a SOCKS version 5 proxy + * "socks4" - specifies a SOCKS version 4 proxy + * The type name is case-insensitive. Other string values may be + * possible, and new types may be defined by a future version of + * this interface. + * @param aHost + * The proxy hostname or IP address. + * @param aPort + * The proxy port. + * @param aUsername + * The proxy username + * @param aPassword + * The proxy password + * @param aFlags + * Flags associated with this connection. See nsIProxyInfo.idl + * for currently defined flags. + * @param aFailoverTimeout + * Specifies the length of time (in seconds) to ignore this proxy if + * this proxy fails. Pass UINT32_MAX to specify the default + * timeout value, causing nsIProxyInfo::failoverTimeout to be + * assigned the default value. + * @param aFailoverProxy + * Specifies the next proxy to try if this proxy fails. This + * parameter may be null. + */ + nsIProxyInfo newProxyInfoWithAuth(in ACString aType, in AUTF8String aHost, + in long aPort, + in AUTF8String aUsername, in AUTF8String aPassword, + in ACString aProxyAuthorizationHeader, + in ACString aConnectionIsolationKey, + in unsigned long aFlags, + in unsigned long aFailoverTimeout, + in nsIProxyInfo aFailoverProxy); + + /** + * If the proxy identified by aProxyInfo is unavailable for some reason, + * this method may be called to access an alternate proxy that may be used + * instead. As a side-effect, this method may affect future result values + * from resolve/asyncResolve as well as from getFailoverForProxy. + * + * @param aProxyInfo + * The proxy that was unavailable. + * @param aURI + * The URI that was originally passed to resolve/asyncResolve. + * @param aReason + * The error code corresponding to the proxy failure. This value + * may be used to tune the delay before this proxy is used again. + * + * @throw NS_ERROR_NOT_AVAILABLE if there is no alternate proxy available. + */ + nsIProxyInfo getFailoverForProxy(in nsIProxyInfo aProxyInfo, + in nsIURI aURI, + in nsresult aReason); + + /** + * This method may be used to register a proxy filter instance. Each proxy + * filter is registered with an associated position that determines the + * order in which the filters are applied (starting from position 0). When + * resolve/asyncResolve is called, it generates a list of proxies for the + * given URI, and then it applies the proxy filters. The filters have the + * opportunity to modify the list of proxies. + * + * If two filters register for the same position, then the filters will be + * visited in the order in which they were registered. + * + * If the filter is already registered, then its position will be updated. + * + * After filters have been run, any disabled or disallowed proxies will be + * removed from the list. A proxy is disabled if it had previously failed- + * over to another proxy (see getFailoverForProxy). A proxy is disallowed, + * for example, if it is a HTTP proxy and the nsIProtocolHandler for the + * queried URI does not permit proxying via HTTP. + * + * If a nsIProtocolHandler disallows all proxying, then filters will never + * have a chance to intercept proxy requests for such URLs. + * + * @param aFilter + * The nsIProtocolProxyFilter instance to be registered. + * @param aPosition + * The position of the filter. + * + * NOTE: It is possible to construct filters that compete with one another + * in undesirable ways. This API does not attempt to protect against such + * problems. It is recommended that any extensions that choose to call + * this method make their position value configurable at runtime (perhaps + * via the preferences service). + */ + void registerFilter(in nsIProtocolProxyFilter aFilter, + in unsigned long aPosition); + + /** + * Similar to registerFilter, but accepts an nsIProtocolProxyChannelFilter, + * which selects proxies according to channel rather than URI. + * + * @param aFilter + * The nsIProtocolProxyChannelFilter instance to be registered. + * @param aPosition + * The position of the filter. + */ + void registerChannelFilter(in nsIProtocolProxyChannelFilter aFilter, + in unsigned long aPosition); + + /** + * This method may be used to unregister a proxy filter instance. All + * filters will be automatically unregistered at XPCOM shutdown. + * + * @param aFilter + * The nsIProtocolProxyFilter instance to be unregistered. + */ + void unregisterFilter(in nsIProtocolProxyFilter aFilter); + + /** + * This method may be used to unregister a proxy channel filter instance. All + * filters will be automatically unregistered at XPCOM shutdown. + * + * @param aFilter + * The nsIProtocolProxyChannelFilter instance to be unregistered. + */ + void unregisterChannelFilter(in nsIProtocolProxyChannelFilter aFilter); + + /** + * This method is used to register a nsIProxyConfigChangedCallback. + * + * @param aCallback + * The aCallback instance to be registered. + */ + void addProxyConfigCallback(in nsIProxyConfigChangedCallback aCallback); + + /** + * This method is used to unregister a nsIProxyConfigChangedCallback. + * + * @param aCallback + * The aCallback instance to be unregistered. + */ + void removeProxyConfigCallback(in nsIProxyConfigChangedCallback aCallback); + + + /** + * This method is used internal only. Called when proxy config is changed. + */ + void notifyProxyConfigChangedInternal(); + + /** + * These values correspond to the possible integer values for the + * network.proxy.type preference. + */ + const unsigned long PROXYCONFIG_DIRECT = 0; + const unsigned long PROXYCONFIG_MANUAL = 1; + const unsigned long PROXYCONFIG_PAC = 2; + const unsigned long PROXYCONFIG_WPAD = 4; + const unsigned long PROXYCONFIG_SYSTEM = 5; + + /** + * This attribute specifies the current type of proxy configuration. + */ + readonly attribute unsigned long proxyConfigType; + + /** + * True if there is a PAC download in progress. + */ + [notxpcom, nostdcall] readonly attribute boolean isPACLoading; +}; diff --git a/netwerk/base/nsIProtocolProxyService2.idl b/netwerk/base/nsIProtocolProxyService2.idl new file mode 100644 index 0000000000..fe031bac40 --- /dev/null +++ b/netwerk/base/nsIProtocolProxyService2.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIProtocolProxyService.idl" + +/** + * An extension of nsIProtocolProxyService + */ +[scriptable, builtinclass, uuid(b2e5b2c0-e21e-4845-b336-be6d60a38951)] +interface nsIProtocolProxyService2 : nsIProtocolProxyService +{ + /** + * Call this method to cause the PAC file (if any is configured) to be + * reloaded. The PAC file is loaded asynchronously. + */ + void reloadPAC(); + + /** + * This method is identical to asyncResolve() except: + * - it only accepts an nsIChannel, not an nsIURI; + * - it may execute the callback function immediately (i.e from the stack + * of asyncResolve2()) if it is immediately ready to run. + * The nsICancelable return value will be null in that case. + */ + nsICancelable asyncResolve2( + in nsIChannel aChannel, in unsigned long aFlags, + in nsIProtocolProxyCallback aCallback, + [optional] in nsISerialEventTarget aMainThreadTarget); +}; diff --git a/netwerk/base/nsIProxiedChannel.idl b/netwerk/base/nsIProxiedChannel.idl new file mode 100644 index 0000000000..c6796b9f9d --- /dev/null +++ b/netwerk/base/nsIProxiedChannel.idl @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIProxyInfo; + +/** + * An interface for accessing the proxy info that a channel was + * constructed with. + * + * @see nsIProxiedProtocolHandler + */ +[scriptable, uuid(6238f134-8c3f-4354-958f-dfd9d54a4446)] +interface nsIProxiedChannel : nsISupports +{ + /** + * Gets the proxy info the channel was constructed with. null or a + * proxyInfo with type "direct" mean no proxy. + * + * The returned proxy info must not be modified. + */ + readonly attribute nsIProxyInfo proxyInfo; + + /** + * The HTTP response code returned from the proxy to the CONNECT method. + * The response code is only available when we get the response from + * the proxy server, so this value is known in and after OnStartRequest. + * + * If CONNECT method is not used, httpProxyConnectResponseCode is always -1. + * After OnStartRequest, httpProxyConnectResponseCode is the real HTTP + * response code or 0 if we can't reach to the proxy. + */ + readonly attribute int32_t httpProxyConnectResponseCode; +}; diff --git a/netwerk/base/nsIProxiedProtocolHandler.idl b/netwerk/base/nsIProxiedProtocolHandler.idl new file mode 100644 index 0000000000..d9436e94a7 --- /dev/null +++ b/netwerk/base/nsIProxiedProtocolHandler.idl @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIProtocolHandler.idl" + +interface nsIChannel; +interface nsIURI; +interface nsIProxyInfo; +interface nsILoadInfo; + +[scriptable, builtinclass, uuid(3756047a-fa2b-4b45-9948-3b5f8fc375e7)] +interface nsIProxiedProtocolHandler : nsIProtocolHandler +{ + /** Create a new channel with the given proxyInfo + * + * @param uri the channel uri + * @param proxyInfo any proxy information that has already been determined, + * or null if channel should later determine the proxy on its own using + * proxyResolveFlags/proxyURI + * @param proxyResolveFlags used if the proxy is later determined + * from nsIProtocolProxyService::asyncResolve + * @param proxyURI used if the proxy is later determined from + * nsIProtocolProxyService::asyncResolve with this as the proxyURI name. + * Generally this is the same as uri (or null which has the same + * effect), except in the case of websockets which wants to bootstrap + * to an http:// channel but make its proxy determination based on + * a ws:// uri. + * @param aLoadInfo used to evaluate who initated the resource request. + */ + nsIChannel newProxiedChannel(in nsIURI uri, in nsIProxyInfo proxyInfo, + in unsigned long proxyResolveFlags, + in nsIURI proxyURI, + in nsILoadInfo aLoadInfo); +}; diff --git a/netwerk/base/nsIProxyInfo.idl b/netwerk/base/nsIProxyInfo.idl new file mode 100644 index 0000000000..dc762fbd2d --- /dev/null +++ b/netwerk/base/nsIProxyInfo.idl @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface identifies a proxy server. + */ +[scriptable, uuid(63fff172-2564-4138-96c6-3ae7d245fbed)] +interface nsIProxyInfo : nsISupports +{ + /** + * This attribute specifies the hostname of the proxy server. + */ + readonly attribute AUTF8String host; + + /** + * This attribute specifies the port number of the proxy server. + */ + readonly attribute long port; + + /** + * This attribute specifies the type of the proxy server as an ASCII string. + * + * Some special values for this attribute include (but are not limited to) + * the following: + * "http" HTTP proxy (or SSL CONNECT for HTTPS) + * "https" HTTP proxying over TLS connection to proxy + * "socks" SOCKS v5 proxy + * "socks4" SOCKS v4 proxy + * "direct" no proxy + * "unknown" unknown proxy (see nsIProtocolProxyService::resolve) + * + * A future version of this interface may define additional types. + */ + readonly attribute ACString type; + + /** + * This attribute specifies flags that modify the proxy type. The value of + * this attribute is the bit-wise combination of the Proxy Flags defined + * below. Any undefined bits are reserved for future use. + */ + readonly attribute unsigned long flags; + + /** + * This attribute specifies flags that were used by nsIProxyProtocolService when + * creating this ProxyInfo element. + */ + readonly attribute unsigned long resolveFlags; + + /** + * Specifies a proxy username. + */ + readonly attribute ACString username; + + /** + * Specifies a proxy password. + */ + readonly attribute ACString password; + + /** + * This attribute specifies the failover timeout in seconds for this proxy. + * If a nsIProxyInfo is reported as failed via nsIProtocolProxyService:: + * getFailoverForProxy, then the failed proxy will not be used again for this + * many seconds. + */ + readonly attribute unsigned long failoverTimeout; + + /** + * This attribute specifies the proxy to failover to when this proxy fails. + */ + attribute nsIProxyInfo failoverProxy; + + /** + * Specifies an ID related to the source of this proxy configuration. If + * it is created in response to an extension API, it will be the extension ID. + */ + attribute ACString sourceId; + + /** + * Any non-empty value will be passed directly as Proxy-Authorization header + * value for the CONNECT request attempt. However, this header set on the + * resource request itself takes precedence. + */ + readonly attribute ACString proxyAuthorizationHeader; + + /** + * An optional key used for additional isolation of this proxy connection. + */ + readonly attribute ACString connectionIsolationKey; + + /**************************************************************************** + * The following "Proxy Flags" may be bit-wise combined to construct the + * flags attribute defined on this interface. All unspecified bits are + * reserved for future use. + */ + + /** + * This flag is set if the proxy is to perform name resolution itself. If + * this is the case, the hostname is used in some fashion, and we shouldn't + * do any form of DNS lookup ourselves. + */ + const unsigned short TRANSPARENT_PROXY_RESOLVES_HOST = 1 << 0; +}; diff --git a/netwerk/base/nsIRandomGenerator.idl b/netwerk/base/nsIRandomGenerator.idl new file mode 100644 index 0000000000..576483b549 --- /dev/null +++ b/netwerk/base/nsIRandomGenerator.idl @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#include <type_traits> +%} + +/** + * Interface used to generate random data. + * + * @threadsafe + */ +[scriptable, uuid(2362d97a-747a-4576-8863-697667309209)] +interface nsIRandomGenerator : nsISupports { + /** + * Generates the specified amount of random bytes. + * + * @param aLength + * The length of the data to generate. + * @param aBuffer + * A buffer that contains random bytes of size aLength. + */ + void generateRandomBytes(in unsigned long aLength, + [retval, array, size_is(aLength)] out octet aBuffer); + + /** + * Fills aBuffer with random bytes. + * + * @param aBuffer + * A buffer to fill with random bytes. + * @param aLength + * Length of aBuffer. + */ + void generateRandomBytesInto([array, size_is(aLength)] in octet aBuffer, + in unsigned long aLength); + +%{C++ + template<typename T> + std::enable_if_t<!std::is_pointer_v<T>, nsresult> GenerateRandomBytesInto(T& aResult) { + return GenerateRandomBytesInto(reinterpret_cast<uint8_t*>(&aResult), sizeof(T)); + } +%} +}; diff --git a/netwerk/base/nsIRedirectChannelRegistrar.idl b/netwerk/base/nsIRedirectChannelRegistrar.idl new file mode 100644 index 0000000000..26dfdf3c6f --- /dev/null +++ b/netwerk/base/nsIRedirectChannelRegistrar.idl @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIChannel; +interface nsIParentChannel; + +/** + * Used on the chrome process as a service to join channel implementation + * and parent IPC protocol side under a unique id. Provides this way a generic + * communication while redirecting to various protocols. + * + * See also nsIChildChannel and nsIParentChannel. + */ + +[scriptable, uuid (efa36ea2-5b07-46fc-9534-a5acb8b77b72)] +interface nsIRedirectChannelRegistrar : nsISupports +{ + /** + * Register the redirect target channel. The passed id needs to be a + * unique ID for that channel (see `nsContentUtils::GenerateLoadIdentifier`). + * + * Primarily used in ParentChannelListener::AsyncOnChannelRedirect to get + * a channel id sent to the HttpChannelChild being redirected. + */ + void registerChannel(in nsIChannel channel, in uint64_t id); + + /** + * First, search for the channel registered under the id. If found return + * it. Then, register under the same id the parent side of IPC protocol + * to let it be later grabbed back by the originator of the redirect and + * notifications from the real channel could be forwarded to this parent + * channel. + * + * Primarily used in parent side of an IPC protocol implementation + * in reaction to nsIChildChannel.connectParent(id) called from the child + * process. + */ + nsIChannel linkChannels(in uint64_t id, in nsIParentChannel channel); + + /** + * Returns back the channel previously registered under the ID with + * registerChannel method. + * + * Primarilly used in chrome IPC side of protocols when attaching a redirect + * target channel to an existing 'real' channel implementation. + */ + nsIChannel getRegisteredChannel(in uint64_t id); + + /** + * Returns the stream listener that shall be attached to the redirect target + * channel, all notification from the redirect target channel will be + * forwarded to this stream listener. + * + * Primarilly used in HttpChannelParent::OnRedirectResult callback to grab + * the created parent side of the channel and forward notifications to it. + */ + nsIParentChannel getParentChannel(in uint64_t id); + + /** + * To not force all channel implementations to support weak reference + * consumers of this service must ensure release of registered channels them + * self. This releases both the real and parent channel registered under + * the id. + * + * Primarilly used in HttpChannelParent::OnRedirectResult callback. + */ + void deregisterChannels(in uint64_t id); +}; diff --git a/netwerk/base/nsIRedirectHistoryEntry.idl b/netwerk/base/nsIRedirectHistoryEntry.idl new file mode 100644 index 0000000000..50086574f8 --- /dev/null +++ b/netwerk/base/nsIRedirectHistoryEntry.idl @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIPrincipal; +interface nsIURI; + +/** + * This nsIRedirectHistoryEntry defines an interface for specifying channel + * redirect information + */ + +[scriptable, uuid(133b2905-0eba-411c-a8bb-f59787142aa2)] +interface nsIRedirectHistoryEntry : nsISupports +{ + /** + * The principal of this redirect entry + */ + readonly attribute nsIPrincipal principal; + + /** + * The referring URI of this redirect entry. This may be null. + */ + readonly attribute nsIURI referrerURI; + + /** + * The remote address of this redirect entry. + */ + readonly attribute ACString remoteAddress; + +}; diff --git a/netwerk/base/nsIRedirectResultListener.idl b/netwerk/base/nsIRedirectResultListener.idl new file mode 100644 index 0000000000..57241350a4 --- /dev/null +++ b/netwerk/base/nsIRedirectResultListener.idl @@ -0,0 +1,22 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(85cd2640-e91e-41ac-bdca-1dbf10dc131e)] +interface nsIRedirectResultListener : nsISupports +{ + /** + * When an HTTP redirect has been processed (either successfully or not) + * nsIHttpChannel will call this function if its callbacks implement this + * interface. + * + * @param proceeding + * Indicated whether the redirect will be proceeding, or not (i.e. + * has been canceled, or failed). + */ + void onRedirectResult(in nsresult status); +}; diff --git a/netwerk/base/nsIRequest.idl b/netwerk/base/nsIRequest.idl new file mode 100644 index 0000000000..8fa9462136 --- /dev/null +++ b/netwerk/base/nsIRequest.idl @@ -0,0 +1,332 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +#include "nsString.h" +%} + +interface nsILoadGroup; + +typedef unsigned long nsLoadFlags; + +/** + * nsIRequest + */ +[scriptable, uuid(ef6bfbd2-fd46-48d8-96b7-9f8f0fd387fe)] +interface nsIRequest : nsISupports +{ + /** + * The name of the request. Often this is the URI of the request. + */ + readonly attribute AUTF8String name; + + /** + * Indicates whether the request is pending. nsIRequest::isPending is + * true when there is an outstanding asynchronous event that will make + * the request no longer be pending. Requests do not necessarily start + * out pending; in some cases, requests have to be explicitly initiated + * (e.g. nsIChannel implementations are only pending once asyncOpen + * returns successfully). + * + * Requests can become pending multiple times during their lifetime. + * + * @return TRUE if the request has yet to reach completion. + * @return FALSE if the request has reached completion (e.g., after + * OnStopRequest has fired). + * @note Suspended requests are still considered pending. + */ + boolean isPending(); + + /** + * The error status associated with the request. + */ + readonly attribute nsresult status; + + /** + * Cancels the current request. This will close any open input or + * output streams and terminate any async requests. Users should + * normally pass NS_BINDING_ABORTED, although other errors may also + * be passed. The error passed in will become the value of the + * status attribute. + * + * Implementations must not send any notifications (e.g. via + * nsIRequestObserver) synchronously from this function. Similarly, + * removal from the load group (if any) must also happen asynchronously. + * + * Requests that use nsIStreamListener must not call onDataAvailable + * anymore after cancel has been called. + * + * @param aStatus the reason for canceling this request. + * + * NOTE: most nsIRequest implementations expect aStatus to be a + * failure code; however, some implementations may allow aStatus to + * be a success code such as NS_OK. In general, aStatus should be + * a failure code. + */ + void cancel(in nsresult aStatus); + + /** + * Suspends the current request. This may have the effect of closing + * any underlying transport (in order to free up resources), although + * any open streams remain logically opened and will continue delivering + * data when the transport is resumed. + * + * Calling cancel() on a suspended request must not send any + * notifications (such as onstopRequest) until the request is resumed. + * + * NOTE: some implementations are unable to immediately suspend, and + * may continue to deliver events already posted to an event queue. In + * general, callers should be capable of handling events even after + * suspending a request. + */ + void suspend(); + + /** + * Resumes the current request. This may have the effect of re-opening + * any underlying transport and will resume the delivery of data to + * any open streams. + */ + void resume(); + + /** + * The load group of this request. While pending, the request is a + * member of the load group. It is the responsibility of the request + * to implement this policy. + */ + attribute nsILoadGroup loadGroup; + + /** + * The load flags of this request. Bits 0-15 are reserved. + * + * When added to a load group, this request's load flags are merged with + * the load flags of the load group. + */ + attribute nsLoadFlags loadFlags; + + /** + * Mask defining the bits reserved for nsIRequest LoadFlags + */ + const unsigned long LOAD_REQUESTMASK = 0xFFFF; + + /************************************************************************** + * Listed below are the various load flags which may be or'd together. + */ + + /** + * No special load flags: + */ + const unsigned long LOAD_NORMAL = 0; + + /** + * Do not deliver status notifications to the nsIProgressEventSink and + * do not block the loadgroup from completing (should this load belong to one). + * Note: Progress notifications will still be delivered. + */ + const unsigned long LOAD_BACKGROUND = 1 << 0; + + /** + * This flag marks the request as being made to load the data for an html + * <object> tag. This means that the LOAD_DOCUMENT_URI flag may be set after + * the channel has been provided with the MIME type. + */ + const unsigned long LOAD_HTML_OBJECT_DATA = 1 << 1; + + /** + * This flag marks the request as belonging to a document that requires access + * to the document.cookies API. + */ + const unsigned long LOAD_DOCUMENT_NEEDS_COOKIE = 1 << 2; + + cenum TRRMode : 32 { + TRR_DEFAULT_MODE = 0, + TRR_DISABLED_MODE = 1, + TRR_FIRST_MODE = 2, + TRR_ONLY_MODE = 3 + }; + + /** + * These methods encode/decode the TRR mode to/from the loadFlags. + * Helper methods Get/SetTRRModeImpl are provided so implementations don't + * need to duplicate code. + * + * Requests with TRR_DEFAULT_MODE will use the mode indicated by the pref + * - see network.trr.mode in all.js + * Requests with TRR_DISABLED_MODE will always use native DNS, even if the + * pref is set to mode3 (TRR-only). + * Requests with TRR_FIRST_MODE will first use TRR then fallback to regular + * DNS, unless TRR is disabled by setting the pref to mode5, parental + * control being enabled, or the domain being in the exclusion list. + * Requests with TRR_ONLY_MODE will only use TRR, unless not allowed by + * the same conditions mentioned above. + */ + nsIRequest_TRRMode getTRRMode(); + void setTRRMode(in nsIRequest_TRRMode mode); + + %{C++ + inline TRRMode GetTRRMode() { + TRRMode mode = TRR_DEFAULT_MODE; + GetTRRMode(&mode); + return mode; + } + + inline nsresult GetTRRModeImpl(nsIRequest::TRRMode* aTRRMode) { + NS_ENSURE_ARG_POINTER(aTRRMode); + nsLoadFlags flags = nsIRequest::LOAD_NORMAL; + nsresult rv = GetLoadFlags(&flags); + if (NS_FAILED(rv)) { + return rv; + } + *aTRRMode = static_cast<nsIRequest::TRRMode>( + (flags & nsIRequest::LOAD_TRR_MASK) >> 3); + return NS_OK; + } + + inline nsresult SetTRRModeImpl(nsIRequest::TRRMode aTRRMode) { + MOZ_ASSERT(aTRRMode <= 3, "invalid value"); + nsLoadFlags flags = nsIRequest::LOAD_NORMAL; + nsresult rv = GetLoadFlags(&flags); + if (NS_FAILED(rv)) { + return rv; + } + flags = (flags & ~nsIRequest::LOAD_TRR_MASK) | (aTRRMode << 3); + return SetLoadFlags(flags); + } + %} + + /** + * These two bits encode the TRR mode. + * Do not get/set manually, rather use the getTRRMode/setTRRMode methods. + */ + const unsigned long LOAD_TRR_MASK = (1 << 3) | (1 << 4); + const unsigned long LOAD_TRR_DISABLED_MODE = 1 << 3; + const unsigned long LOAD_TRR_FIRST_MODE = 1 << 4; + const unsigned long LOAD_TRR_ONLY_MODE = (1 << 3) | (1 << 4); + + void cancelWithReason(in nsresult aStatus, in ACString aReason); + attribute ACString canceledReason; + + %{C++ + protected: + nsCString mCanceledReason; + + public: + inline nsresult SetCanceledReasonImpl(const nsACString& aReason) { + if (mCanceledReason.IsEmpty()) { + mCanceledReason.Assign(aReason); + } + + return NS_OK; + } + + inline nsresult CancelWithReasonImpl(nsresult aStatus, + const nsACString& aReason) { + SetCanceledReasonImpl(aReason); + return Cancel(aStatus); + } + + inline nsresult GetCanceledReasonImpl(nsACString& aReason) { + aReason.Assign(mCanceledReason); + return NS_OK; + } + %} + + /** + * This is used for a temporary workaround for a web-compat issue. The flag is + * only set on CORS preflight request to allowed sending client certificates + * on a connection for an anonymous request. + */ + const long LOAD_ANONYMOUS_ALLOW_CLIENT_CERT = 1 << 5; + + /************************************************************************** + * The following flags control the flow of data into the cache. + */ + + /** + * This flag prevents caching of any kind. It does not, however, prevent + * cached content from being used to satisfy this request. + */ + const unsigned long INHIBIT_CACHING = 1 << 7; + + /** + * This flag prevents caching on disk (or other persistent media), which + * may be needed to preserve privacy. + */ + const unsigned long INHIBIT_PERSISTENT_CACHING = 1 << 8; + + /************************************************************************** + * The following flags control what happens when the cache contains data + * that could perhaps satisfy this request. They are listed in descending + * order of precidence. + */ + + /** + * Force an end-to-end download of content data from the origin server. + * This flag is used for a shift-reload. + */ + const unsigned long LOAD_BYPASS_CACHE = 1 << 9; + + /** + * Attempt to force a load from the cache, bypassing ALL validation logic + * (note: this is stronger than VALIDATE_NEVER, which still validates for + * certain conditions). + * + * If the resource is not present in cache, it will be loaded from the + * network. Combine this flag with LOAD_ONLY_FROM_CACHE if you wish to + * perform cache-only loads without validation checks. + * + * This flag is used when browsing via history. It is not recommended for + * normal browsing as it may likely violate reasonable assumptions made by + * the server and confuse users. + */ + const unsigned long LOAD_FROM_CACHE = 1 << 10; + + /** + * The following flags control the frequency of cached content validation + * when neither LOAD_BYPASS_CACHE or LOAD_FROM_CACHE are set. By default, + * cached content is automatically validated if necessary before reuse. + * + * VALIDATE_ALWAYS forces validation of any cached content independent of + * its expiration time (unless it is https with Cache-Control: immutable) + * + * VALIDATE_NEVER disables validation of cached content, unless it arrived + * with the "Cache: no-store" header, or arrived via HTTPS with the + * "Cache: no-cache" header. + * + * VALIDATE_ONCE_PER_SESSION disables validation of expired content, + * provided it has already been validated (at least once) since the start + * of this session. + * + * NOTE TO IMPLEMENTORS: + * These flags are intended for normal browsing, and they should therefore + * not apply to content that must be validated before each use. Consider, + * for example, a HTTP response with a "Cache-control: no-cache" header. + * According to RFC2616, this response must be validated before it can + * be taken from a cache. Breaking this requirement could result in + * incorrect and potentially undesirable side-effects. + */ + const unsigned long VALIDATE_ALWAYS = 1 << 11; + const unsigned long VALIDATE_NEVER = 1 << 12; + const unsigned long VALIDATE_ONCE_PER_SESSION = 1 << 13; + + /** + * When set, this flag indicates that no user-specific data should be added + * to the request when opened. This means that things like authorization + * tokens or cookie headers should not be added. + */ + const unsigned long LOAD_ANONYMOUS = 1 << 14; + + /** + * When set, this flag indicates that caches of network connections, + * particularly HTTP persistent connections, should not be used. + * Use this together with LOAD_INITIAL_DOCUMENT_URI as otherwise it has no + * effect. + */ + const unsigned long LOAD_FRESH_CONNECTION = 1 << 15; + + // Note that all flags are taken, the rest of the flags + // are located in nsIChannel and nsICachingChannel +}; diff --git a/netwerk/base/nsIRequestContext.idl b/netwerk/base/nsIRequestContext.idl new file mode 100644 index 0000000000..6c9f265af2 --- /dev/null +++ b/netwerk/base/nsIRequestContext.idl @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +// Forward-declare mozilla::net::SpdyPushCache +namespace mozilla { +namespace net { +class SpdyPushCache; +} +} +%} + +interface nsILoadGroup; +interface nsIChannel; +interface nsIStreamListener; + +[ptr] native SpdyPushCachePtr(mozilla::net::SpdyPushCache); + +/** + * Requests capable of tail-blocking must implement this + * interfaces (typically channels). + * If the request is tail-blocked, it will be held in its request + * context queue until unblocked. + */ +[uuid(7EB361D4-37A5-42C9-AFAE-F6C88FE7C394)] +interface nsIRequestTailUnblockCallback : nsISupports +{ + /** + * Called when the requests is unblocked and proceed. + * @param result + * NS_OK - the request is OK to go, unblocking is not + * caused by cancelation of the request. + * any error - the request must behave as it were canceled + * with the result as status. + */ + void onTailUnblock(in nsresult aResult); +}; + +/** + * The nsIRequestContext is used to maintain state about connections + * that are in some way associated with each other (often by being part + * of the same load group) and how they interact with blocking items like + * HEAD css/js loads. + * + * This used to be known as nsILoadGroupConnectionInfo and nsISchedulingContext. + */ +[uuid(658e3e6e-8633-4b1a-8d66-fa9f72293e63)] +interface nsIRequestContext : nsISupports +{ + /** + * A unique identifier for this request context + */ + [notxpcom, nostdcall] readonly attribute unsigned long long ID; + + /** + * Called by the associated document when its load starts. This resets + * context's internal states. + */ + void beginLoad(); + + /** + * Called when the associated document notified the DOMContentLoaded event. + */ + void DOMContentLoaded(); + + /** + * Number of active blocking transactions associated with this context + */ + readonly attribute unsigned long blockingTransactionCount; + + /** + * Increase the number of active blocking transactions associated + * with this context by one. + */ + void addBlockingTransaction(); + + /** + * Decrease the number of active blocking transactions associated + * with this context by one. The return value is the number of remaining + * blockers. + */ + unsigned long removeBlockingTransaction(); + + /** + * This gives out a weak pointer to the push cache. + * The nsIRequestContext implementation owns the cache + * and will destroy it when overwritten or when the context + * ends. + */ + [notxpcom,nostdcall] attribute SpdyPushCachePtr spdyPushCache; + + /** + * Increases/decrease the number of non-tailed requests in this context. + * If the count drops to zero, all tail-blocked callbacks are notified + * shortly after that to be unblocked. + */ + void addNonTailRequest(); + void removeNonTailRequest(); + + /** + * If the request context is in tail-blocked state, the callback + * is queued and result is true. The callback will be notified + * about tail-unblocking or when the request context is canceled. + */ + [must_use] boolean isContextTailBlocked(in nsIRequestTailUnblockCallback callback); + + /** + * Called when the request is sitting in the tail queue but has been + * canceled before untailing. This just removes the request from the + * queue so that it is not notified on untail and not referenced. + */ + void cancelTailedRequest(in nsIRequestTailUnblockCallback request); + + /** + * This notifies all queued tail-blocked requests, they will be notified + * aResult and released afterwards. Called by the load group when + * it's canceled. + */ + void cancelTailPendingRequests(in nsresult aResult); +}; + +/** + * The nsIRequestContextService is how anyone gets access to a request + * context when they haven't been explicitly given a strong reference to an + * existing one. It is responsible for creating and handing out strong + * references to nsIRequestContexts, but only keeps weak references itself. + * The shared request context will go away once no one else is keeping a + * reference to it. If you ask for a request context that has no one else + * holding a reference to it, you'll get a brand new request context. Anyone + * who asks for the same request context while you're holding a reference + * will get a reference to the same request context you have. + */ +[uuid(7fcbf4da-d828-4acc-b144-e5435198f727)] +interface nsIRequestContextService : nsISupports +{ + /** + * Get an existing request context from its ID + */ + nsIRequestContext getRequestContext(in unsigned long long id); + /** + * Shorthand to get request context from a load group + */ + nsIRequestContext getRequestContextFromLoadGroup(in nsILoadGroup lg); + + /** + * Create a new request context + */ + nsIRequestContext newRequestContext(); + + /** + * Remove an existing request context from its ID + */ + void removeRequestContext(in unsigned long long id); +}; diff --git a/netwerk/base/nsIRequestObserver.idl b/netwerk/base/nsIRequestObserver.idl new file mode 100644 index 0000000000..674e923a32 --- /dev/null +++ b/netwerk/base/nsIRequestObserver.idl @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIRequest; + +/** + * nsIRequestObserver + */ +[scriptable, uuid(fd91e2e0-1481-11d3-9333-00104ba0fd40)] +interface nsIRequestObserver : nsISupports +{ + /** + * Called to signify the beginning of an asynchronous request. + * + * @param aRequest request being observed + * + * An exception thrown from onStartRequest has the side-effect of + * causing the request to be canceled. + * + * Note: if this request is an nsIMultiPartChannelListener then + * OnStartRequest may be called multiple times. + */ + void onStartRequest(in nsIRequest aRequest); + + /** + * Called to signify the end of an asynchronous request. This + * call is always preceded by a call to onStartRequest. + * + * @param aRequest request being observed + * @param aStatusCode reason for stopping (NS_OK if completed successfully) + * + * An exception thrown from onStopRequest is generally ignored. + * + * Note: if this request is an nsIMultiPartChannelListener then + * OnStopRequest may be called multiple times. + */ + void onStopRequest(in nsIRequest aRequest, + in nsresult aStatusCode); +}; diff --git a/netwerk/base/nsIRequestObserverProxy.idl b/netwerk/base/nsIRequestObserverProxy.idl new file mode 100644 index 0000000000..7b79f53427 --- /dev/null +++ b/netwerk/base/nsIRequestObserverProxy.idl @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequestObserver.idl" + +interface nsIEventTarget; + +/** + * A request observer proxy is used to ship data over to another thread + * specified by the thread's dispatch target. The "true" request observer's + * methods are invoked on the other thread. + * + * This interface only provides the initialization needed after construction. + * Otherwise, these objects are used simply as nsIRequestObserver's. + */ +[scriptable, uuid(c2b06151-1bf8-4eef-aea9-1532f12f5a10)] +interface nsIRequestObserverProxy : nsIRequestObserver +{ + /** + * Initializes an nsIRequestObserverProxy. + * + * @param observer - receives observer notifications on the main thread + * @param context - the context argument that will be passed to OnStopRequest + * and OnStartRequest. This has to be stored permanently on + * initialization because it sometimes can't be + * AddRef/Release'd off-main-thread. + */ + void init(in nsIRequestObserver observer, in nsISupports context); +}; diff --git a/netwerk/base/nsIResumableChannel.idl b/netwerk/base/nsIResumableChannel.idl new file mode 100644 index 0000000000..6737e8bf9d --- /dev/null +++ b/netwerk/base/nsIResumableChannel.idl @@ -0,0 +1,39 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIStreamListener; + +[scriptable, uuid(4ad136fa-83af-4a22-a76e-503642c0f4a8)] +interface nsIResumableChannel : nsISupports { + /** + * Prepare this channel for resuming. The request will not start until + * asyncOpen or open is called. Calling resumeAt after open or asyncOpen + * has been called has undefined behaviour. + * + * @param startPos the starting offset, in bytes, to use to download + * @param entityID information about the file, to match before obtaining + * the file. Pass an empty string to use anything. + * + * During OnStartRequest, this channel will have a status of + * NS_ERROR_NOT_RESUMABLE if the file cannot be resumed, eg because the + * server doesn't support this. This error may occur even if startPos + * is 0, so that the front end can warn the user. + * Similarly, the status of this channel during OnStartRequest may be + * NS_ERROR_ENTITY_CHANGED, which indicates that the entity has changed, + * as indicated by a changed entityID. + * In both of these cases, no OnDataAvailable will be called, and + * OnStopRequest will immediately follow with the same status code. + */ + void resumeAt(in unsigned long long startPos, + in ACString entityID); + + /** + * The entity id for this URI. Available after OnStartRequest. + * @throw NS_ERROR_NOT_RESUMABLE if this load is not resumable. + */ + readonly attribute ACString entityID; +}; diff --git a/netwerk/base/nsISecCheckWrapChannel.idl b/netwerk/base/nsISecCheckWrapChannel.idl new file mode 100644 index 0000000000..21f4d0c290 --- /dev/null +++ b/netwerk/base/nsISecCheckWrapChannel.idl @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIChannel; + +/** + * nsISecCheckWrapChannel + * Describes an XPCOM component used to wrap channels for performing + * security checks. Channels wrapped inside this class can use + * this interface to query the wrapped inner channel. + */ + +[scriptable, uuid(9446c5d5-c9fb-4a6e-acf9-ca4fc666efe0)] +interface nsISecCheckWrapChannel : nsISupports +{ + /** + * Returns the wrapped channel inside this class. + */ + readonly attribute nsIChannel innerChannel; + +}; diff --git a/netwerk/base/nsISecureBrowserUI.idl b/netwerk/base/nsISecureBrowserUI.idl new file mode 100644 index 0000000000..3984310d93 --- /dev/null +++ b/netwerk/base/nsISecureBrowserUI.idl @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsITransportSecurityInfo; + +[scriptable, builtinclass, uuid(718c662a-f810-4a80-a6c9-0b1810ecade2)] +interface nsISecureBrowserUI : nsISupports +{ + readonly attribute unsigned long state; + readonly attribute bool isSecureContext; + readonly attribute nsITransportSecurityInfo secInfo; +}; + +%{C++ +#define NS_SECURE_BROWSER_UI_CONTRACTID "@mozilla.org/secure_browser_ui;1" +%} diff --git a/netwerk/base/nsISensitiveInfoHiddenURI.idl b/netwerk/base/nsISensitiveInfoHiddenURI.idl new file mode 100644 index 0000000000..abb3f082b9 --- /dev/null +++ b/netwerk/base/nsISensitiveInfoHiddenURI.idl @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(a5761968-6e1a-4f2d-8191-ec749602b178)] +interface nsISensitiveInfoHiddenURI : nsISupports +{ + /** + * Returns the spec attribute with sensitive information hidden. This will + * only affect uri with password. The password part of uri will be + * transformed into "****". + */ + AUTF8String getSensitiveInfoHiddenSpec(); +}; diff --git a/netwerk/base/nsISerializationHelper.idl b/netwerk/base/nsISerializationHelper.idl new file mode 100644 index 0000000000..740927f407 --- /dev/null +++ b/netwerk/base/nsISerializationHelper.idl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Simple scriptable serialization helper. Can be used as a service. + */ + +interface nsISerializable; + +[scriptable, uuid(31654c0f-35f3-44c6-b31e-37a11516e6bc)] +interface nsISerializationHelper : nsISupports +{ + /** + * Serialize the object to a base64 string. This string can be later passed + * as an input to deserializeObject method. + */ + ACString serializeToString(in nsISerializable serializable); + + /** + * Takes base64 encoded string that cointains serialization of a single + * object. Most commonly, input is result of previous call to + * serializeToString. + */ + nsISupports deserializeObject(in ACString input); +}; diff --git a/netwerk/base/nsIServerSocket.idl b/netwerk/base/nsIServerSocket.idl new file mode 100644 index 0000000000..d6fd348778 --- /dev/null +++ b/netwerk/base/nsIServerSocket.idl @@ -0,0 +1,269 @@ +/* vim:set ts=4 sw=4 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIServerSocketListener; +interface nsISocketTransport; + +native PRNetAddr(union PRNetAddr); +[ptr] native PRNetAddrPtr(union PRNetAddr); + +typedef unsigned long nsServerSocketFlag; + +/** + * nsIServerSocket + * + * An interface to a server socket that can accept incoming connections. + */ +[scriptable, uuid(7a9c39cb-a13f-4eef-9bdf-a74301628742)] +interface nsIServerSocket : nsISupports +{ + /** + * @name Server Socket Flags + * These flags define various socket options. + * @{ + */ + /// The server socket will only respond to connections on the + /// local loopback interface. Otherwise, it will accept connections + /// from any interface. To specify a particular network interface, + /// use initWithAddress. + const nsServerSocketFlag LoopbackOnly = 0x00000001; + /// The server socket will not be closed when Gecko is set + /// offline. + const nsServerSocketFlag KeepWhenOffline = 0x00000002; + /** @} */ + + /** + * init + * + * This method initializes a server socket. + * + * @param aPort + * The port of the server socket. Pass -1 to indicate no preference, + * and a port will be selected automatically. + * @param aLoopbackOnly + * If true, the server socket will only respond to connections on the + * local loopback interface. Otherwise, it will accept connections + * from any interface. To specify a particular network interface, + * use initWithAddress. + * @param aBackLog + * The maximum length the queue of pending connections may grow to. + * This parameter may be silently limited by the operating system. + * Pass -1 to use the default value. + */ + void init(in long aPort, + in boolean aLoopbackOnly, + in long aBackLog); + + /** + * the same as init(), but initializes an IPv6 server socket + */ + void initIPv6(in long aPort, + in boolean aLoopbackOnly, + in long aBackLog); + + /** + * Similar to init(), but initializes a server socket that supports + * both IPv4 and IPv6. + */ + void initDualStack(in long aPort, + in long aBackLog); + + /** + * initSpecialConnection + * + * This method initializes a server socket and offers the ability to have + * that socket not get terminated if Gecko is set offline. + * + * @param aPort + * The port of the server socket. Pass -1 to indicate no preference, + * and a port will be selected automatically. + * @param aFlags + * Flags for the socket. + * @param aBackLog + * The maximum length the queue of pending connections may grow to. + * This parameter may be silently limited by the operating system. + * Pass -1 to use the default value. + */ + void initSpecialConnection(in long aPort, + in nsServerSocketFlag aFlags, + in long aBackLog); + + + /** + * initWithAddress + * + * This method initializes a server socket, and binds it to a particular + * local address (and hence a particular local network interface). + * + * @param aAddr + * The address to which this server socket should be bound. + * @param aBackLog + * The maximum length the queue of pending connections may grow to. + * This parameter may be silently limited by the operating system. + * Pass -1 to use the default value. + */ + [noscript] void initWithAddress([const] in PRNetAddrPtr aAddr, in long aBackLog); + + /** + * initWithFilename + * + * This method initializes a Unix domain or "local" server socket. Such + * a socket has a name in the filesystem, like an ordinary file. To + * connect, a client supplies the socket's filename, and the usual + * permission checks on socket apply. + * + * This makes Unix domain sockets useful for communication between the + * programs being run by a specific user on a single machine: the + * operating system takes care of authentication, and the user's home + * directory or profile directory provide natural per-user rendezvous + * points. + * + * Since Unix domain sockets are always local to the machine, they are + * not affected by the nsIIOService's 'offline' flag. + * + * The system-level socket API may impose restrictions on the length of + * the filename that are stricter than those of the underlying + * filesystem. If the file name is too long, this returns + * NS_ERROR_FILE_NAME_TOO_LONG. + * + * All components of the path prefix of |aPath| must name directories; + * otherwise, this returns NS_ERROR_FILE_NOT_DIRECTORY. + * + * This call requires execute permission on all directories containing + * the one in which the socket is to be created, and write and execute + * permission on the directory itself. Otherwise, this returns + * NS_ERROR_CONNECTION_REFUSED. + * + * This call creates the socket's directory entry. There must not be + * any existing entry with the given name. If there is, this returns + * NS_ERROR_SOCKET_ADDRESS_IN_USE. + * + * On systems that don't support Unix domain sockets at all, this + * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED. + * + * @param aPath nsIFile + * The file name at which the socket should be created. + * + * @param aPermissions unsigned long + * Unix-style permission bits to be applied to the new socket. + * + * Note about permissions: Linux's unix(7) man page claims that some + * BSD-derived systems ignore permissions on UNIX-domain sockets; + * NetBSD's bind(2) man page agrees, but says it does check now (dated + * 2005). POSIX has required 'connect' to fail if write permission on + * the socket itself is not granted since 2003 (Issue 6). NetBSD says + * that the permissions on the containing directory (execute) have + * always applied, so creating sockets in appropriately protected + * directories should be secure on both old and new systems. + */ + void initWithFilename(in nsIFile aPath, in unsigned long aPermissions, + in long aBacklog); + + /** + * initWithAbstractAddress + * + * This mehtod is a flavor of initWithFilename method. This initializes + * a UNIX domain socket that uses abstract socket address. + * This socket type is only supported on Linux and Android. + * + * On systems that don't support this type's UNIX domain sockets at all, + * this returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED. + * + * @param aName + * The abstract socket address which the socket should be created. + * @param aBacklog + * The maximum length the queue of pending connections may grow to. + */ + void initWithAbstractAddress(in AUTF8String aName, + in long aBacklog); + + /** + * close + * + * This method closes a server socket. This does not affect already + * connected client sockets (i.e., the nsISocketTransport instances + * created from this server socket). This will cause the onStopListening + * event to asynchronously fire with a status of NS_BINDING_ABORTED. + */ + void close(); + + /** + * asyncListen + * + * This method puts the server socket in the listening state. It will + * asynchronously listen for and accept client connections. The listener + * will be notified once for each client connection that is accepted. The + * listener's onSocketAccepted method will be called on the same thread + * that called asyncListen (the calling thread must have a nsIEventTarget). + * + * The listener will be passed a reference to an already connected socket + * transport (nsISocketTransport). See below for more details. + * + * @param aListener + * The listener to be notified when client connections are accepted. + */ + void asyncListen(in nsIServerSocketListener aListener); + + /** + * Returns the port of this server socket. + */ + readonly attribute long port; + + /** + * Returns the address to which this server socket is bound. Since a + * server socket may be bound to multiple network devices, this address + * may not necessarily be specific to a single network device. In the + * case of an IP socket, the IP address field would be zerod out to + * indicate a server socket bound to all network devices. Therefore, + * this method cannot be used to determine the IP address of the local + * system. See nsIDNSService::myHostName if this is what you need. + */ + [noscript] PRNetAddr getAddress(); +}; + +/** + * nsIServerSocketListener + * + * This interface is notified whenever a server socket accepts a new connection. + * The transport is in the connected state, and read/write streams can be opened + * using the normal nsITransport API. The address of the client can be found by + * calling the nsISocketTransport::GetAddress method or by inspecting + * nsISocketTransport::GetHost, which returns a string representation of the + * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal). + */ +[scriptable, uuid(836d98ec-fee2-4bde-b609-abd5e966eabd)] +interface nsIServerSocketListener : nsISupports +{ + /** + * onSocketAccepted + * + * This method is called when a client connection is accepted. + * + * @param aServ + * The server socket. + * @param aTransport + * The connected socket transport. + */ + void onSocketAccepted(in nsIServerSocket aServ, + in nsISocketTransport aTransport); + + /** + * onStopListening + * + * This method is called when the listening socket stops for some reason. + * The server socket is effectively dead after this notification. + * + * @param aServ + * The server socket. + * @param aStatus + * The reason why the server socket stopped listening. If the + * server socket was manually closed, then this value will be + * NS_BINDING_ABORTED. + */ + void onStopListening(in nsIServerSocket aServ, in nsresult aStatus); +}; diff --git a/netwerk/base/nsISimpleStreamListener.idl b/netwerk/base/nsISimpleStreamListener.idl new file mode 100644 index 0000000000..99169481f5 --- /dev/null +++ b/netwerk/base/nsISimpleStreamListener.idl @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStreamListener.idl" + +interface nsIOutputStream; + +/** + * A simple stream listener can be used with AsyncRead to supply data to + * a output stream. + */ +[scriptable, uuid(a9b84f6a-0824-4278-bae6-bfca0570a26e)] +interface nsISimpleStreamListener : nsIStreamListener +{ + /** + * Initialize the simple stream listener. + * + * @param aSink data will be read from the channel to this output stream. + * Must implement writeFrom. + * @param aObserver optional stream observer (can be NULL) + */ + void init(in nsIOutputStream aSink, + in nsIRequestObserver aObserver); +}; diff --git a/netwerk/base/nsISimpleURIMutator.idl b/netwerk/base/nsISimpleURIMutator.idl new file mode 100644 index 0000000000..786c8701d2 --- /dev/null +++ b/netwerk/base/nsISimpleURIMutator.idl @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIURIMutator; + +[scriptable, builtinclass, uuid(e055bddd-f3c2-404b-adec-db9304e93be2)] +interface nsISimpleURIMutator : nsISupports +{ + /** + * Same behaviour as nsIURISetSpec.setSpec() but filters whitespace. + */ + nsIURIMutator setSpecAndFilterWhitespace(in AUTF8String aSpec); +}; diff --git a/netwerk/base/nsISocketFilter.idl b/netwerk/base/nsISocketFilter.idl new file mode 100644 index 0000000000..0846fa2eda --- /dev/null +++ b/netwerk/base/nsISocketFilter.idl @@ -0,0 +1,53 @@ +/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsINetAddr.idl" + +native NetAddr(mozilla::net::NetAddr); +[ptr] native NetAddrPtr(mozilla::net::NetAddr); + + +/** + * Filters are created and run on the parent, and filter all packets, both + * ingoing and outgoing. The child must specify the name of a recognized filter + * in order to create a socket. + */ +[uuid(afe2c40c-b9b9-4207-b898-e5cde18c6139)] +interface nsISocketFilter : nsISupports +{ + const long SF_INCOMING = 0; + const long SF_OUTGOING = 1; + + bool filterPacket([const]in NetAddrPtr remote_addr, + [const, array, size_is(len)]in uint8_t data, + in unsigned long len, + in long direction); +}; + +/** + * Factory of a specified filter. + */ +[uuid(81ee76c6-4753-4125-9c8c-290ed9ba62fb)] +interface nsISocketFilterHandler : nsISupports +{ + nsISocketFilter newFilter(); +}; + +%{C++ +/** + * Filter handlers are registered with XPCOM under the following CONTRACTID prefix: + */ +#define NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX "@mozilla.org/network/udp-filter-handler;1?name=" +#define NS_NETWORK_TCP_SOCKET_FILTER_HANDLER_PREFIX "@mozilla.org/network/tcp-filter-handler;1?name=" + +#define NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX "stun" + +#define NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX + + +#define NS_STUN_TCP_SOCKET_FILTER_HANDLER_CONTRACTID NS_NETWORK_TCP_SOCKET_FILTER_HANDLER_PREFIX NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX +%} diff --git a/netwerk/base/nsISocketTransport.idl b/netwerk/base/nsISocketTransport.idl new file mode 100644 index 0000000000..58b869203e --- /dev/null +++ b/netwerk/base/nsISocketTransport.idl @@ -0,0 +1,382 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsITransport.idl" +#include "nsIRequest.idl" +#include "nsITRRSkipReason.idl" + +interface nsIInterfaceRequestor; +interface nsINetAddr; +interface nsITLSSocketControl; + +%{ C++ +#include "mozilla/BasePrincipal.h" +namespace mozilla { +namespace net { +union NetAddr; +} +} +%} +native NetAddr(mozilla::net::NetAddr); +[ptr] native NetAddrPtr(mozilla::net::NetAddr); +native OriginAttributes(mozilla::OriginAttributes); +[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes); + +/** + * nsISocketTransport + * + * NOTE: Connection setup is triggered by opening an input or output stream, + * it does not start on its own. Completion of the connection setup is + * indicated by a STATUS_CONNECTED_TO notification to the event sink (if set). + * + * NOTE: This is a free-threaded interface, meaning that the methods on + * this interface may be called from any thread. + */ +[scriptable, builtinclass, uuid(79221831-85e2-43a8-8152-05d77d6fde31)] +interface nsISocketTransport : nsITransport +{ + /** + * Get the peer's host for the underlying socket connection. + * For Unix domain sockets, this is a pathname, or the empty string for + * unnamed and abstract socket addresses. + */ + readonly attribute AUTF8String host; + + /** + * Get the port for the underlying socket connection. + * For Unix domain sockets, this is zero. + */ + readonly attribute long port; + + /** + * The origin attributes are used to create sockets. The first party domain + * will eventually be used to isolate OCSP cache and is only non-empty when + * "privacy.firstparty.isolate" is enabled. Setting this is the only way to + * carry origin attributes down to NSPR layers which are final consumers. + * It must be set before the socket transport is built. + */ + [implicit_jscontext, binaryname(ScriptableOriginAttributes)] + attribute jsval originAttributes; + + [noscript, nostdcall, binaryname(GetOriginAttributes)] + OriginAttributes binaryGetOriginAttributes(); + + [noscript, nostdcall, binaryname(SetOriginAttributes)] + void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs); + + /** + * Returns the IP address of the socket connection peer. This + * attribute is defined only once a connection has been established. + */ + [noscript] NetAddr getPeerAddr(); + + /** + * Returns the IP address of the initiating end. This attribute + * is defined only once a connection has been established. + */ + [noscript] NetAddr getSelfAddr(); + + /** + * Bind to a specific local address. + */ + [noscript] void bind(in NetAddrPtr aLocalAddr); + + /** + * Returns a scriptable version of getPeerAddr. This attribute is defined + * only once a connection has been established. + */ + nsINetAddr getScriptablePeerAddr(); + + /** + * Returns a scriptable version of getSelfAddr. This attribute is defined + * only once a connection has been established. + */ + nsINetAddr getScriptableSelfAddr(); + + /** + * TLS socket control object. This attribute is only available once the + * socket is connected. + */ + readonly attribute nsITLSSocketControl tlsSocketControl; + + /** + * Security notification callbacks passed to the secure socket provider + * via nsITLSSocketControl at socket creation time. + * + * NOTE: this attribute cannot be changed once a stream has been opened. + */ + attribute nsIInterfaceRequestor securityCallbacks; + + /** + * Test if this socket transport is (still) connected. + */ + boolean isAlive(); + + /** + * Socket timeouts in seconds. To specify no timeout, pass UINT32_MAX + * as aValue to setTimeout. The implementation may truncate timeout values + * to a smaller range of values (e.g., 0 to 0xFFFF). + */ + unsigned long getTimeout(in unsigned long aType); + void setTimeout(in unsigned long aType, in unsigned long aValue); + + /** + * Sets the SO_LINGER option with the specified values for the l_onoff and + * l_linger parameters. This applies PR_SockOpt_Linger before PR_Close and + * can be used with a timeout of zero to send an RST packet when closing. + */ + void setLinger(in boolean aPolarity, in short aTimeout); + + /** + * True to set addr and port reuse socket options. + */ + void setReuseAddrPort(in bool reuseAddrPort); + + /** + * Values for the aType parameter passed to get/setTimeout. + */ + const unsigned long TIMEOUT_CONNECT = 0; + const unsigned long TIMEOUT_READ_WRITE = 1; + + /** + * nsITransportEventSink status codes. + * + * Although these look like XPCOM error codes and are passed in an nsresult + * variable, they are *not* error codes. Note that while they *do* overlap + * with existing error codes in Necko, these status codes are confined + * within a very limited context where no error codes may appear, so there + * is no ambiguity. + * + * The values of these status codes must never change. + * + * The status codes appear in near-chronological order (not in numeric + * order). STATUS_RESOLVING may be skipped if the host does not need to be + * resolved. STATUS_WAITING_FOR is an optional status code, which the impl + * of this interface may choose not to generate. + * + * In C++, these constants have a type of uint32_t, so C++ callers must use + * the NS_NET_STATUS_* constants defined below, which have a type of + * nsresult. + */ + const unsigned long STATUS_RESOLVING = 0x4b0003; + const unsigned long STATUS_RESOLVED = 0x4b000b; + const unsigned long STATUS_CONNECTING_TO = 0x4b0007; + const unsigned long STATUS_CONNECTED_TO = 0x4b0004; + const unsigned long STATUS_SENDING_TO = 0x4b0005; + const unsigned long STATUS_WAITING_FOR = 0x4b000a; + const unsigned long STATUS_RECEIVING_FROM = 0x4b0006; + const unsigned long STATUS_TLS_HANDSHAKE_STARTING = 0x4b000c; + const unsigned long STATUS_TLS_HANDSHAKE_ENDED = 0x4b000d; + + /** + * connectionFlags is a bitmask that can be used to modify underlying + * behavior of the socket connection. See the flags below. + */ + attribute unsigned long connectionFlags; + + /** + * Values for the connectionFlags + * + * When making a new connection BYPASS_CACHE will force the Necko DNS + * cache entry to be refreshed with a new call to NSPR if it is set before + * opening the new stream. + */ + const unsigned long BYPASS_CACHE = (1 << 0); + + /** + * When setting this flag, the socket will not apply any + * credentials when establishing a connection. For example, + * an SSL connection would not send any client-certificates + * if this flag is set. + */ + const unsigned long ANONYMOUS_CONNECT = (1 << 1); + + /** + * If set, we will skip all IPv6 addresses the host may have and only + * connect to IPv4 ones. + */ + const unsigned long DISABLE_IPV6 = (1 << 2); + + /** + * If set, indicates that the connection was initiated from a source + * defined as being private in the sense of Private Browsing. Generally, + * there should be no state shared between connections that are private + * and those that are not; it is OK for multiple private connections + * to share state with each other, and it is OK for multiple non-private + * connections to share state with each other. + */ + const unsigned long NO_PERMANENT_STORAGE = (1 << 3); + + /** + * If set, we will skip all IPv4 addresses the host may have and only + * connect to IPv6 ones. + */ + const unsigned long DISABLE_IPV4 = (1 << 4); + + /** + * If set, indicates that the socket should not connect if the hostname + * resolves to an RFC1918 address or IPv6 equivalent. + */ + const unsigned long DISABLE_RFC1918 = (1 << 5); + + /** + * If set, do not use newer protocol features that might have interop problems + * on the Internet. Intended only for use with critical infra like the updater. + * default is false. + */ + const unsigned long BE_CONSERVATIVE = (1 << 6); + + /** + * If set, do not use TRR for resolving the host name. Intended only for + * retries or other scenarios when TRR is deemed likely to have returned a + * wrong adddress. + */ + const unsigned long DISABLE_TRR = (1 << 7); + + /** + * Values for the connectionFlags + * + * When using BYPASS_CACHE, setting this bit will invalidate the existing + * cached entry immediately while the new resolve is being done to avoid + * other users from using stale content in the mean time. + */ + const unsigned long REFRESH_CACHE = (1 << 8); + + /** + * If this flag is set then it means that if connecting the preferred ip + * family has failed, retry with the oppsite one once more. + */ + const unsigned long RETRY_WITH_DIFFERENT_IP_FAMILY = (1 << 9); + + /** + * If we know that a server speaks only tls <1.3 there is no need to try + * to use ech. + */ + const unsigned long DONT_TRY_ECH = (1 << 10); + + /** + * These two bits encode the TRR mode of the request. + * Use the static helper methods convert between the TRR mode and flags. + */ + const unsigned long TRR_MODE_FLAGS = (1 << 11) | (1 << 12); + +%{C++ + + static uint32_t GetFlagsFromTRRMode(nsIRequest::TRRMode aMode) { + return static_cast<uint32_t>(aMode) << 11; + } + + static nsIRequest::TRRMode GetTRRModeFromFlags(uint32_t aFlags) { + return static_cast<nsIRequest::TRRMode>((aFlags & TRR_MODE_FLAGS) >> 11); + } +%} + + /** + * If set, we will use IP hint addresses to connect to the host. + */ + const unsigned long USE_IP_HINT_ADDRESS = (1 << 13); + + /** + * This is used for a temporary workaround for a web-compat issue. The flag is + * only set on CORS preflight request to allowed sending client certificates + * on a connection for an anonymous request. + */ + const unsigned long ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT = (1 << 14); + + /** + * If set, we've retrying after a failed connection attempt. + */ + const unsigned long IS_RETRY = (1 << 15); + + /** + * If set, this is a speculative connection. + */ + const unsigned long IS_SPECULATIVE_CONNECTION = (1 << 16); + + /** + * An opaque flags for non-standard behavior of the TLS system. + * It is unlikely this will need to be set outside of telemetry studies + * relating to the TLS implementation. + */ + attribute unsigned long tlsFlags; + + /** + * Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or + * IPTOS_CLASS_CSx (or IPTOS_DSCP_EF, but currently no supported + * services require expedited-forwarding). + * Not setting this value will leave the socket with the default + * ToS value, which on most systems if IPTOS_CLASS_CS0 (formerly + * IPTOS_PREC_ROUTINE). + */ + attribute octet QoSBits; + + /** + * TCP send and receive buffer sizes. A value of 0 means OS level + * auto-tuning is in effect. + */ + attribute unsigned long recvBufferSize; + attribute unsigned long sendBufferSize; + + /** + * TCP keepalive configuration (support varies by platform). + * Note that the attribute as well as the setter can only accessed + * in the socket thread. + */ + attribute boolean keepaliveEnabled; + void setKeepaliveVals(in long keepaliveIdleTime, + in long keepaliveRetryInterval); + + /** + * If true, this socket transport has found out the prefered family + * according it's connection flags could not be used to establish + * connections any more. Hence, the preference should be reset. + */ + readonly attribute boolean resetIPFamilyPreference; + + /** + * This attribute holds information whether echConfig has been used. + * The value is set after PR_Connect is called. + */ + readonly attribute boolean echConfigUsed; + + /** + * Called to set the echConfig to the securityInfo object. + */ + void setEchConfig(in ACString echConfig); + + /** + * IP address resolved using TRR. + */ + bool resolvedByTRR(); + + /** + * Returns the effectiveTRRMode used for the DNS resolution. + */ + readonly attribute nsIRequest_TRRMode effectiveTRRMode; + + /** + * Returns the TRR skip reason used for the DNS resolution. + */ + readonly attribute nsITRRSkipReason_value trrSkipReason; + + /** + * Indicate whether this socket is created from a private window. If yes, + * this socket will be closed when the last private window is closed. + */ + [noscript] void setIsPrivate(in boolean isPrivate); + + /** + * If DNS is performed externally, this flag informs the caller that it may + * retry connecting with a different DNS configuration (e.g. different IP + * family preference). The flag is set only if a network error is encounder, + * e.g. NS_ERROR_CONNECTION_REFUSED, NS_ERROR_RESET, etc. + */ + readonly attribute boolean retryDnsIfPossible; + + /** + * Return the current status of the socket. + */ + [noscript] readonly attribute nsresult status; +}; diff --git a/netwerk/base/nsISocketTransportService.idl b/netwerk/base/nsISocketTransportService.idl new file mode 100644 index 0000000000..64891fcfa0 --- /dev/null +++ b/netwerk/base/nsISocketTransportService.idl @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDNSRecord; +interface nsIFile; +interface nsISocketTransport; +interface nsIProxyInfo; +interface nsIRunnable; + +%{C++ +class nsASocketHandler; +struct PRFileDesc; +%} + +[ptr] native PRFileDescPtr(PRFileDesc); +[ptr] native nsASocketHandlerPtr(nsASocketHandler); + +[scriptable, function, uuid(338947df-2f3b-4d24-9ce4-ecf161c1b7df)] +interface nsISTSShutdownObserver : nsISupports { + + /** + * Observe will be called when the SocketTransportService is shutting down, + * before threads are stopped. + */ + void observe(); +}; + +[builtinclass, scriptable, uuid(ad56b25f-e6bb-4db3-9f7b-5b7db33fd2b1)] +interface nsISocketTransportService : nsISupports +{ + /** + * Creates a transport for a specified host and port. + * + * @param aSocketTypes + * array of socket type strings. Empty array if using default + * socket type. + * @param aHost + * specifies the target hostname or IP address literal of the peer + * for this socket. + * @param aPort + * specifies the target port of the peer for this socket. + * @param aProxyInfo + * specifies the transport-layer proxy type to use. null if no + * proxy. used for communicating information about proxies like + * SOCKS (which are transparent to upper protocols). + * @param aDnsRecord + * the dns record to be used for the connection + * + * @see nsIProxiedProtocolHandler + * @see nsIProtocolProxyService::GetProxyInfo + * + * NOTE: this function can be called from any thread + */ + nsISocketTransport createTransport(in Array<ACString> aSocketTypes, + in AUTF8String aHost, + in long aPort, + in nsIProxyInfo aProxyInfo, + in nsIDNSRecord dnsRecord); + + /** + * Create a transport built on a Unix domain socket, connecting to the + * given filename. + * + * Since Unix domain sockets are always local to the machine, they are + * not affected by the nsIIOService's 'offline' flag. + * + * On systems that don't support Unix domain sockets at all, this + * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED. + * + * The system-level socket API may impose restrictions on the length of + * the filename that are stricter than those of the underlying + * filesystem. If the file name is too long, this returns + * NS_ERROR_FILE_NAME_TOO_LONG. + * + * The |aPath| parameter must specify an existing directory entry. + * Otherwise, this returns NS_ERROR_FILE_NOT_FOUND. + * + * The program must have search permission on all components of the + * path prefix of |aPath|, and read and write permission on |aPath| + * itself. Without such permission, this returns + * NS_ERROR_CONNECTION_REFUSED. + * + * The |aPath| parameter must refer to a unix-domain socket. Otherwise, + * this returns NS_ERROR_CONNECTION_REFUSED. (POSIX specifies + * ECONNREFUSED when "the target address was not listening for + * connections", and this is what Linux returns.) + * + * @param aPath + * The file name of the Unix domain socket to which we should + * connect. + */ + nsISocketTransport createUnixDomainTransport(in nsIFile aPath); + + /** + * Create a transport built on a Unix domain socket that uses abstract + * address name. + * + * If abstract socket address isn't supported on System, this returns + * NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED. + * + * @param aName + * The name of abstract socket adress of the Unix domain socket to + * which we should connect. + */ + nsISocketTransport + createUnixDomainAbstractAddressTransport(in ACString aName); + + /** + * Adds a new socket to the list of controlled sockets. + * + * This will fail with the error code NS_ERROR_NOT_AVAILABLE if the maximum + * number of sockets is already reached. + * In this case, the notifyWhenCanAttachSocket method should be used. + * + * @param aFd + * Open file descriptor of the socket to control. + * @param aHandler + * Socket handler that will receive notifications when the socket is + * ready or detached. + * + * NOTE: this function may only be called from an event dispatch on the + * socket thread. + */ + [noscript] void attachSocket(in PRFileDescPtr aFd, + in nsASocketHandlerPtr aHandler); + + /** + * if the number of sockets reaches the limit, then consumers can be + * notified when the number of sockets becomes less than the limit. the + * notification is asynchronous, delivered via the given nsIRunnable + * instance on the socket transport thread. + * + * @param aEvent + * Event that will receive the notification when a new socket can + * be attached + * + * NOTE: this function may only be called from an event dispatch on the + * socket thread. + */ + [noscript] void notifyWhenCanAttachSocket(in nsIRunnable aEvent); + + [noscript] void addShutdownObserver(in nsISTSShutdownObserver aObserver); + [noscript] void removeShutdownObserver(in nsISTSShutdownObserver aObserver); +}; + +[builtinclass, scriptable, uuid(c5204623-5b58-4a16-8b2e-67c34dd02e3f)] +interface nsIRoutedSocketTransportService : nsISocketTransportService +{ + // use this instead of createTransport when you have a transport + // that distinguishes between origin and route (aka connection) + nsISocketTransport createRoutedTransport(in Array<ACString> aSocketTypes, + in AUTF8String aHost, // origin + in long aPort, // origin + in AUTF8String aHostRoute, + in long aPortRoute, + in nsIProxyInfo aProxyInfo, + in nsIDNSRecord aDnsRecord); +}; diff --git a/netwerk/base/nsISpeculativeConnect.idl b/netwerk/base/nsISpeculativeConnect.idl new file mode 100644 index 0000000000..e1f890a04b --- /dev/null +++ b/netwerk/base/nsISpeculativeConnect.idl @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIPrincipal; +interface nsIURI; +interface nsIInterfaceRequestor; + +%{C++ +namespace mozilla { + +class OriginAttributes; + +} +%} + +native OriginAttributes(mozilla::OriginAttributes&&); + +[scriptable, builtinclass, uuid(d74a17ac-5b8a-4824-a309-b1f04a3c4aed)] +interface nsISpeculativeConnect : nsISupports +{ + /** + * Called as a hint to indicate a new transaction for the URI is likely coming + * soon. The implementer may use this information to start a TCP + * and/or SSL level handshake for that resource immediately so that it is + * ready and/or progressed when the transaction is actually submitted. + * + * No obligation is taken on by the implementer, nor is the submitter obligated + * to actually open the new channel. + * + * @param aURI the URI of the hinted transaction + * @param aPrincipal the principal that will be used for opening the + * channel of the hinted transaction. + * @param aCallbacks any security callbacks for use with SSL for interfaces. + * May be null. + * @param aAnonymous indicates if this is an anonymous connection. + * + */ + void speculativeConnect(in nsIURI aURI, + in nsIPrincipal aPrincipal, + in nsIInterfaceRequestor aCallbacks, + in boolean aAnonymous); + + /** + * This method is similar to speculativeConnect, but it use + * the partition key of the originAttributes directly to create the + * connection. + */ + [implicit_jscontext] + void speculativeConnectWithOriginAttributes( + in nsIURI aURI, + in jsval originAttributes, + in nsIInterfaceRequestor aCallbacks, + in boolean aAnonymous); + + [notxpcom] + void speculativeConnectWithOriginAttributesNative( + in nsIURI aURI, + in OriginAttributes originAttributes, + in nsIInterfaceRequestor aCallbacks, + in boolean aAnonymous); +}; + +/** + * This is used to override the default values for various values (documented + * inline) to determine whether or not to actually make a speculative + * connection. + */ +[uuid(1040ebe3-6ed1-45a6-8587-995e082518d7)] +interface nsISpeculativeConnectionOverrider : nsISupports +{ + /** + * Used to determine the maximum number of unused speculative connections + * we will have open for a host at any one time + */ + [infallible] readonly attribute unsigned long parallelSpeculativeConnectLimit; + + /** + * Used to determine if we will ignore the existence of any currently idle + * connections when we decide whether or not to make a speculative + * connection. + */ + [infallible] readonly attribute boolean ignoreIdle; + + /* + * Used by the Predictor to gather telemetry data on speculative connection + * usage. + */ + [infallible] readonly attribute boolean isFromPredictor; + + /** + * by default speculative connections are not made to RFC 1918 addresses + */ + [infallible] readonly attribute boolean allow1918; +}; diff --git a/netwerk/base/nsIStandardURL.idl b/netwerk/base/nsIStandardURL.idl new file mode 100644 index 0000000000..dae8ecc13c --- /dev/null +++ b/netwerk/base/nsIStandardURL.idl @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; +interface nsIURIMutator; + +/** + * nsIStandardURL defines the interface to an URL with the standard + * file path format common to protocols like http, ftp, and file. + * It supports initialization from a relative path and provides + * some customization on how URLs are normalized. + */ +[scriptable, builtinclass, uuid(babd6cca-ebe7-4329-967c-d6b9e33caa81)] +interface nsIStandardURL : nsISupports +{ + /** + * blah:foo/bar => blah://foo/bar + * blah:/foo/bar => blah:///foo/bar + * blah://foo/bar => blah://foo/bar + * blah:///foo/bar => blah:///foo/bar + */ + const unsigned long URLTYPE_STANDARD = 1; + + /** + * blah:foo/bar => blah://foo/bar + * blah:/foo/bar => blah://foo/bar + * blah://foo/bar => blah://foo/bar + * blah:///foo/bar => blah://foo/bar + */ + const unsigned long URLTYPE_AUTHORITY = 2; + + /** + * blah:foo/bar => blah:///foo/bar + * blah:/foo/bar => blah:///foo/bar + * blah://foo/bar => blah://foo/bar + * blah:///foo/bar => blah:///foo/bar + */ + const unsigned long URLTYPE_NO_AUTHORITY = 3; +}; + +[scriptable, builtinclass, uuid(fc894e98-23a1-43cd-a7fe-72876f8ea2ee)] +interface nsIStandardURLMutator : nsISupports +{ + /** + * Initialize a standard URL. + * + * @param aUrlType - one of the URLTYPE_ flags listed above. + * @param aDefaultPort - if the port parsed from the URL string matches + * this port, then the port will be removed from the + * canonical form of the URL. + * @param aSpec - URL string. + * @param aOriginCharset - the charset from which this URI string + * originated. this corresponds to the charset + * that should be used when communicating this + * URI to an origin server, for example. if + * null, then provide aBaseURI implements this + * interface, the origin charset of aBaseURI will + * be assumed, otherwise defaulting to UTF-8 (i.e., + * no charset transformation from aSpec). + * @param aBaseURI - if null, aSpec must specify an absolute URI. + * otherwise, aSpec will be resolved relative + * to aBaseURI. + */ + nsIURIMutator init(in unsigned long aUrlType, + in long aDefaultPort, + in AUTF8String aSpec, + in string aOriginCharset, + in nsIURI aBaseURI); + + /** + * Set the default port. + * + * Note: If this object is already using its default port (i.e. if it has + * mPort == -1), then it will now implicitly be using the new default port. + * + * @param aNewDefaultPort - if the URI has (or is later given) a port that + * matches this default, then we won't include a + * port number in the canonical form of the URL. + */ + nsIURIMutator setDefaultPort(in long aNewDefaultPort); +}; diff --git a/netwerk/base/nsIStreamListener.idl b/netwerk/base/nsIStreamListener.idl new file mode 100644 index 0000000000..68ade60cd1 --- /dev/null +++ b/netwerk/base/nsIStreamListener.idl @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRequestObserver.idl" + +interface nsIInputStream; + +/** + * nsIStreamListener + */ +[scriptable, uuid(3b4c8a77-76ba-4610-b316-678c73a3b88c)] +interface nsIStreamListener : nsIRequestObserver +{ + /** + * Called when the next chunk of data (corresponding to the request) may + * be read without blocking the calling thread. The onDataAvailable impl + * must read exactly |aCount| bytes of data before returning. + * + * @param aRequest request corresponding to the source of the data + * @param aInputStream input stream containing the data chunk + * @param aOffset + * Number of bytes that were sent in previous onDataAvailable calls + * for this request. In other words, the sum of all previous count + * parameters. + * @param aCount number of bytes available in the stream + * + * NOTE: The aInputStream parameter must implement readSegments. + * + * An exception thrown from onDataAvailable has the side-effect of + * causing the request to be canceled. + */ + void onDataAvailable(in nsIRequest aRequest, + in nsIInputStream aInputStream, + in unsigned long long aOffset, + in unsigned long aCount); +}; diff --git a/netwerk/base/nsIStreamListenerTee.idl b/netwerk/base/nsIStreamListenerTee.idl new file mode 100644 index 0000000000..2aa9c34877 --- /dev/null +++ b/netwerk/base/nsIStreamListenerTee.idl @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIThreadRetargetableStreamListener.idl" + +interface nsIOutputStream; +interface nsIRequestObserver; +interface nsIEventTarget; + +/** + * As data "flows" into a stream listener tee, it is copied to the output stream + * and then forwarded to the real listener. + */ +[scriptable, uuid(62b27fc1-6e8c-4225-8ad0-b9d44252973a)] +interface nsIStreamListenerTee : nsIThreadRetargetableStreamListener +{ + /** + * Initalize the tee. + * + * @param listener + * the original listener the tee will propagate onStartRequest, + * onDataAvailable and onStopRequest notifications to, exceptions from + * the listener will be propagated back to the channel + * @param sink + * the stream the data coming from the channel will be written to, + * should be blocking + * @param requestObserver + * optional parameter, listener that gets only onStartRequest and + * onStopRequest notifications; exceptions threw within this optional + * observer are also propagated to the channel, but exceptions from + * the original listener (listener parameter) are privileged + */ + void init(in nsIStreamListener listener, + in nsIOutputStream sink, + [optional] in nsIRequestObserver requestObserver); + + /** + * Initalize the tee like above, but with the extra parameter to make it + * possible to copy the output asynchronously + * @param anEventTarget + * if set, this event-target is used to copy data to the output stream, + * giving an asynchronous tee + */ + void initAsync(in nsIStreamListener listener, + in nsIEventTarget eventTarget, + in nsIOutputStream sink, + [optional] in nsIRequestObserver requestObserver); + +}; diff --git a/netwerk/base/nsIStreamLoader.idl b/netwerk/base/nsIStreamLoader.idl new file mode 100644 index 0000000000..1b54f59074 --- /dev/null +++ b/netwerk/base/nsIStreamLoader.idl @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIThreadRetargetableStreamListener.idl" + +interface nsIRequest; +interface nsIStreamLoader; + +[scriptable, uuid(359F7990-D4E9-11d3-A1A5-0050041CAF44)] +interface nsIStreamLoaderObserver : nsISupports +{ + /** + * Called when the entire stream has been loaded. + * + * @param loader the stream loader that loaded the stream. + * @param ctxt the context parameter of the underlying channel + * @param status the status of the underlying channel + * @param resultLength the length of the data loaded + * @param result the data + * + * This method will always be called asynchronously by the + * nsIStreamLoader involved, on the thread that called the + * loader's init() method. + * + * If the observer wants to take over responsibility for the + * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA + * in place of NS_OK as its success code. The loader will then + * "forget" about the data and not free() it after + * onStreamComplete() returns; observer must call free() + * when the data is no longer required. + */ + void onStreamComplete(in nsIStreamLoader loader, + in nsISupports ctxt, + in nsresult status, + in unsigned long resultLength, + [const,array,size_is(resultLength)] in octet result); +}; + +/** + * Asynchronously loads a channel into a memory buffer. + * + * To use this interface, first call init() with a nsIStreamLoaderObserver + * that will be notified when the data has been loaded. Then call asyncOpen() + * on the channel with the nsIStreamLoader as the listener. The context + * argument in the asyncOpen() call will be passed to the onStreamComplete() + * callback. + * + * XXX define behaviour for sizes >4 GB + */ +[scriptable, uuid(323bcff1-7513-4e1f-a541-1c9213c2ed1b)] +interface nsIStreamLoader : nsIThreadRetargetableStreamListener +{ + /** + * Initialize this stream loader, and start loading the data. + * + * @param aStreamObserver + * An observer that will be notified when the data is complete. + * @param aRequestObserver + * An optional observer that will be notified when the request + * has started or stopped. + */ + void init(in nsIStreamLoaderObserver aStreamObserver, + [optional] in nsIRequestObserver aRequestObserver); + + /** + * Gets the number of bytes read so far. + */ + readonly attribute unsigned long numBytesRead; + + /** + * Gets the request that loaded this file. + * null after the request has finished loading. + */ + readonly attribute nsIRequest request; +}; diff --git a/netwerk/base/nsIStreamTransportService.idl b/netwerk/base/nsIStreamTransportService.idl new file mode 100644 index 0000000000..3a1c5a4239 --- /dev/null +++ b/netwerk/base/nsIStreamTransportService.idl @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsITransport; +interface nsIInputStream; +interface nsIOutputStream; +interface nsIInputAvailableCallback; + +/** + * This service read/writes a stream on a background thread. + * + * Note: instead of using this interface, probably you want to use + * NS_MakeAsyncNonBlockingInputStream. + * + * Use this service to transform any blocking stream (e.g., file stream) + * into a fully asynchronous stream that can be read/written without + * blocking the main thread. + */ +[builtinclass, scriptable, uuid(5e0adf7d-9785-45c3-a193-04f25a75da8f)] +interface nsIStreamTransportService : nsISupports +{ + /** + * CreateInputTransport + * + * @param aStream + * The input stream that will be read on a background thread. + * This stream must implement "blocking" stream semantics. + * @param aCloseWhenDone + * Specify this flag to have the input stream closed once its + * contents have been completely read. + * + * @return nsITransport instance. + */ + nsITransport createInputTransport(in nsIInputStream aStream, + in boolean aCloseWhenDone); + + void InputAvailable(in nsIInputStream aStream, + in nsIInputAvailableCallback aCallback); +}; + +[uuid(ff2da731-44d0-4dd9-8236-c99387fec721)] +interface nsIInputAvailableCallback : nsISupports +{ + void onInputAvailableComplete(in unsigned long long available, + in nsresult available_return_code); +}; diff --git a/netwerk/base/nsISyncStreamListener.idl b/netwerk/base/nsISyncStreamListener.idl new file mode 100644 index 0000000000..9a46dda787 --- /dev/null +++ b/netwerk/base/nsISyncStreamListener.idl @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStreamListener.idl" + +[scriptable, uuid(7e1aa658-6e3f-4521-9946-9685a169f764)] +interface nsISyncStreamListener : nsIStreamListener +{ + /** + * Returns an input stream that when read will fetch data delivered to the + * sync stream listener. The nsIInputStream implementation will wait for + * OnDataAvailable events before returning from Read. + * + * NOTE: Reading from the returned nsIInputStream may spin the current + * thread's event queue, which could result in any event being processed. + */ + readonly attribute nsIInputStream inputStream; +}; diff --git a/netwerk/base/nsISystemProxySettings.idl b/netwerk/base/nsISystemProxySettings.idl new file mode 100644 index 0000000000..9cd561fb64 --- /dev/null +++ b/netwerk/base/nsISystemProxySettings.idl @@ -0,0 +1,42 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface allows the proxy code to use platform-specific proxy + * settings when the proxy preference is set to "automatic discovery". This service + * acts like a PAC parser to netwerk, but it will actually read the system settings and + * either return the proper proxy data from the autoconfig URL specified in the system proxy, + * or generate proxy data based on the system's manual proxy settings. + */ +[scriptable, uuid(971591cd-277e-409a-bbf6-0a79879cd307)] +interface nsISystemProxySettings : nsISupports +{ + /** + * Whether or not it is appropriate to execute getProxyForURI off the main thread. + * If that method can block (e.g. for WPAD as windows does) then it must be + * not mainThreadOnly to avoid creating main thread jank. The main thread only option is + * provided for implementations that do not block but use other main thread only + * functions such as dbus. + */ + readonly attribute bool mainThreadOnly; + + /** + * If non-empty, use this PAC file. If empty, call getProxyForURI instead. + */ + readonly attribute AUTF8String PACURI; + + /** + * See ProxyAutoConfig::getProxyForURI; this function behaves similarly except + * a more relaxed return string is allowed that includes full urls instead of just + * host:port syntax. e.g. "PROXY http://www.foo.com:8080" instead of + * "PROXY www.foo.com:8080" + */ + AUTF8String getProxyForURI(in AUTF8String testSpec, + in AUTF8String testScheme, + in AUTF8String testHost, + in int32_t testPort); +}; diff --git a/netwerk/base/nsITLSServerSocket.idl b/netwerk/base/nsITLSServerSocket.idl new file mode 100644 index 0000000000..e944f23af7 --- /dev/null +++ b/netwerk/base/nsITLSServerSocket.idl @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIServerSocket.idl" + +interface nsIX509Cert; +interface nsITLSServerSecurityObserver; +interface nsISocketTransport; + +[scriptable, uuid(cc2c30f9-cfaa-4b8a-bd44-c24881981b74)] +interface nsITLSServerSocket : nsIServerSocket +{ + /** + * serverCert + * + * The server's certificate that is presented to the client during the TLS + * handshake. This is required to be set before calling |asyncListen|. + */ + attribute nsIX509Cert serverCert; + + /** + * setSessionTickets + * + * Whether the server should support session tickets. Defaults to true. This + * should be set before calling |asyncListen| if you wish to change the + * default. + */ + void setSessionTickets(in boolean aSessionTickets); + + /** + * Values for setRequestClientCertificate + */ + // Never request + const unsigned long REQUEST_NEVER = 0; + // Request (but do not require) during the first handshake only + const unsigned long REQUEST_FIRST_HANDSHAKE = 1; + // Request (but do not require) during each handshake + const unsigned long REQUEST_ALWAYS = 2; + // Require during the first handshake only + const unsigned long REQUIRE_FIRST_HANDSHAKE = 3; + // Require during each handshake + const unsigned long REQUIRE_ALWAYS = 4; + + /** + * setRequestClientCertificate + * + * Whether the server should request and/or require a client auth certificate + * from the client. Defaults to REQUEST_NEVER. See the possible options + * above. This should be set before calling |asyncListen| if you wish to + * change the default. + */ + void setRequestClientCertificate(in unsigned long aRequestClientCert); + + /** + * setVersionRange + * + * The server's TLS versions that is used by the TLS handshake. + * This is required to be set before calling |asyncListen|. + * + * aMinVersion and aMaxVersion is a TLS version value from + * |nsITLSClientStatus| constants. + */ + void setVersionRange(in unsigned short aMinVersion, + in unsigned short aMaxVersion); +}; + +/** + * Security summary for a given TLS client connection being handled by a + * |nsITLSServerSocket| server. + * + * This is accessible through the security info object on the transport, which + * will be an instance of |nsITLSServerConnectionInfo| (see below). + * + * The values of these attributes are available once the |onHandshakeDone| + * method of the security observer has been called (see + * |nsITLSServerSecurityObserver| below). + */ +[scriptable, uuid(19668ea4-e5ad-4182-9698-7e890d48f327)] +interface nsITLSClientStatus : nsISupports +{ + /** + * peerCert + * + * The client's certificate, if one was requested via |requestCertificate| + * above and supplied by the client. + */ + readonly attribute nsIX509Cert peerCert; + + /** + * Values for tlsVersionUsed, as defined by TLS + */ + const short SSL_VERSION_3 = 0x0300; + const short TLS_VERSION_1 = 0x0301; + const short TLS_VERSION_1_1 = 0x0302; + const short TLS_VERSION_1_2 = 0x0303; + const short TLS_VERSION_1_3 = 0x0304; + const short TLS_VERSION_UNKNOWN = -1; + + /** + * tlsVersionUsed + * + * The version of TLS used by the connection. See values above. + */ + readonly attribute short tlsVersionUsed; + + /** + * cipherName + * + * Name of the cipher suite used, such as + * "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256". + * See security/nss/lib/ssl/sslinfo.c for the possible values. + */ + readonly attribute ACString cipherName; + + /** + * keyLength + * + * The "effective" key size of the symmetric key in bits. + */ + readonly attribute unsigned long keyLength; + + /** + * macLength + * + * The size of the MAC in bits. + */ + readonly attribute unsigned long macLength; +}; + +/** + * Connection info for a given TLS client connection being handled by a + * |nsITLSServerSocket| server. This object is thread-safe. + * + * This is exposed as the security info object on the transport, so it can be + * accessed via |transport.securityInfo|. + * + * This interface is available by the time the |onSocketAttached| is called, + * which is the first time the TLS server consumer is notified of a new client. + */ +[scriptable, uuid(8a93f5d5-eddd-4c62-a4bd-bfd297653184)] +interface nsITLSServerConnectionInfo : nsISupports +{ + /** + * setSecurityObserver + * + * Set the security observer to be notified when the TLS handshake has + * completed. + */ + void setSecurityObserver(in nsITLSServerSecurityObserver observer); + + /** + * serverSocket + * + * The nsITLSServerSocket instance that accepted this client connection. + */ + readonly attribute nsITLSServerSocket serverSocket; + + /** + * status + * + * Security summary for this TLS client connection. Note that the values of + * this interface are not available until the TLS handshake has completed. + * See |nsITLSClientStatus| above for more details. + */ + readonly attribute nsITLSClientStatus status; +}; + +[scriptable, uuid(1f62e1ae-e546-4a38-8917-d428472ed736)] +interface nsITLSServerSecurityObserver : nsISupports +{ + /** + * onHandsakeDone + * + * This method is called once the TLS handshake is completed. This takes + * place after |onSocketAccepted| has been called, which typically opens the + * streams to keep things moving along. It's important to be aware that the + * handshake has not completed at the point that |onSocketAccepted| is called, + * so no security verification can be done until this method is called. + */ + void onHandshakeDone(in nsITLSServerSocket aServer, + in nsITLSClientStatus aStatus); +}; diff --git a/netwerk/base/nsIThreadRetargetableRequest.idl b/netwerk/base/nsIThreadRetargetableRequest.idl new file mode 100644 index 0000000000..5087987567 --- /dev/null +++ b/netwerk/base/nsIThreadRetargetableRequest.idl @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsISerialEventTarget; + +/** + * nsIThreadRetargetableRequest + * + * Should be implemented by requests that support retargeting delivery of + * data off the main thread. + */ +[uuid(27b84c48-5a73-4ba4-a8a4-8b5e649a145e)] +interface nsIThreadRetargetableRequest : nsISupports +{ + /** + * Called to retarget delivery of OnDataAvailable to another thread. Should + * only be called before AsyncOpen for nsIWebsocketChannels, or during + * OnStartRequest for nsIChannels. + * Note: For nsIChannels, OnStartRequest and OnStopRequest will still be + * delivered on the main thread. + * + * @param aNewTarget New event target, e.g. thread or threadpool. + * + * Note: no return value is given. If the retargeting cannot be handled, + * normal delivery to the main thread will continue. As such, listeners + * should be ready to deal with OnDataAvailable on either the main thread or + * the new target thread. + */ + void retargetDeliveryTo(in nsISerialEventTarget aNewTarget); + + /** + * Returns the event target where OnDataAvailable events will be dispatched. + * + * This is only valid after OnStartRequest has been called. Any time before + * that point, the value may be changed by `retargetDeliveryTo` calls. + */ + readonly attribute nsISerialEventTarget deliveryTarget; +}; diff --git a/netwerk/base/nsIThreadRetargetableStreamListener.idl b/netwerk/base/nsIThreadRetargetableStreamListener.idl new file mode 100644 index 0000000000..8d4ce396fa --- /dev/null +++ b/netwerk/base/nsIThreadRetargetableStreamListener.idl @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIStreamListener.idl" +/** + * nsIThreadRetargetableStreamListener + * + * To be used by classes which implement nsIStreamListener and whose + * OnDataAvailable callback may be retargeted for delivery off the main thread. + */ +[scriptable, uuid(fb2304b8-f82f-4433-af68-d874a2ebbdc1)] +interface nsIThreadRetargetableStreamListener : nsIStreamListener +{ + /** + * Checks this listener and any next listeners it may have to verify that + * they can receive OnDataAvailable off the main thread. It is the + * responsibility of the implementing class to decide on the criteria to + * determine if retargeted delivery of these methods is possible, but it must + * check any and all nsIStreamListener objects that might be called in the + * listener chain. + * + * An exception should be thrown if a listener in the chain does not + * support retargeted delivery, i.e. if the next listener does not implement + * nsIThreadRetargetableStreamListener, or a call to its checkListenerChain() + * fails. + */ + void checkListenerChain(); + + /** + * Used for notifying listeners about data stop. + * After this notification, the listeners could potentially start processing + * the data. Note that onDataFinished can be called on or off the main thread. + * It is the responsibility of the listeners to handle this correctly. + * + * The ChannelEventQueue implementation ensures that the OnDataFinished is + * run on the ODA target thread after the last OnDataAvailable is executed on + * the ODA target thread and before OnStopRequest is called. + * Hence, the following order is guaranteed for the listeners, even with ODA/ODF running off MainThread. + * 1. OnStartRequest + * 2. OnDataAvailable + * 3. OnDataFinished + * 4. OnStopRequest + */ + void onDataFinished(in nsresult aStatusCode); +}; diff --git a/netwerk/base/nsIThrottledInputChannel.idl b/netwerk/base/nsIThrottledInputChannel.idl new file mode 100644 index 0000000000..ae8d7321db --- /dev/null +++ b/netwerk/base/nsIThrottledInputChannel.idl @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; +interface nsIAsyncInputStream; + +/** + * An instance of this interface can be used to throttle the uploads + * of a group of associated channels. + */ +[scriptable, uuid(6b4b96fe-3c67-4587-af7b-58b6b17da411)] +interface nsIInputChannelThrottleQueue : nsISupports +{ + /** + * Initialize this object with the mean and maximum bytes per + * second that will be allowed. Neither value may be zero, and + * the maximum must not be less than the mean. + * + * @param aMeanBytesPerSecond + * Mean number of bytes per second. + * @param aMaxBytesPerSecond + * Maximum number of bytes per second. + */ + void init(in unsigned long aMeanBytesPerSecond, in unsigned long aMaxBytesPerSecond); + + /** + * Internal use only. Get the values set by init method. + */ + [noscript] readonly attribute unsigned long meanBytesPerSecond; + [noscript] readonly attribute unsigned long maxBytesPerSecond; + + + /** + * Return the number of bytes that are available to the caller in + * this time slice. + * + * @param aRemaining + * The number of bytes available to be processed + * @return the number of bytes allowed to be processed during this + * time slice; this will never be greater than aRemaining. + */ + unsigned long available(in unsigned long aRemaining); + + /** + * Record a successful read. + * + * @param aBytesRead + * The number of bytes actually read. + */ + void recordRead(in unsigned long aBytesRead); + + /** + * Return the number of bytes allowed through this queue. This is + * the sum of all the values passed to recordRead. This method is + * primarily useful for testing. + */ + unsigned long long bytesProcessed(); + + /** + * Wrap the given input stream in a new input stream which + * throttles the incoming data. + * + * @param aInputStream the input stream to wrap + * @return a new input stream that throttles the data. + */ + nsIAsyncInputStream wrapStream(in nsIInputStream aInputStream); +}; + +/** + * A throttled input channel can be managed by an + * nsIInputChannelThrottleQueue to limit how much data is sent during + * a given time slice. + */ +[scriptable, uuid(0a32a100-c031-45b6-9e8b-0444c7d4a143)] +interface nsIThrottledInputChannel : nsISupports +{ + /** + * The queue that manages this channel. Multiple channels can + * share a single queue. A null value means that no throttling + * will be done. + */ + attribute nsIInputChannelThrottleQueue throttleQueue; +}; diff --git a/netwerk/base/nsITimedChannel.idl b/netwerk/base/nsITimedChannel.idl new file mode 100644 index 0000000000..4707bf1b7a --- /dev/null +++ b/netwerk/base/nsITimedChannel.idl @@ -0,0 +1,128 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIArray; +interface nsIPrincipal; +%{C++ +namespace mozilla { +class TimeStamp; +} +#include "nsTArrayForwardDeclare.h" +#include "nsCOMPtr.h" +%} + +native TimeStamp(mozilla::TimeStamp); + +[scriptable, uuid(c2d9e95b-9cc9-4f47-9ef6-1de0cf7ebc75)] +interface nsIServerTiming : nsISupports { + [must_use] readonly attribute ACString name; + [must_use] readonly attribute double duration; + [must_use] readonly attribute ACString description; +}; + +[ref] native nsServerTimingArrayRef(nsTArray<nsCOMPtr<nsIServerTiming>>); + +// All properties return zero if the value is not available +[scriptable, uuid(ca63784d-959c-4c3a-9a59-234a2a520de0)] +interface nsITimedChannel : nsISupports { + // Set this attribute to true to enable collection of timing data. + // channelCreationTime will be available even with this attribute set to + // false. + attribute boolean timingEnabled; + + // The number of redirects + attribute uint8_t redirectCount; + attribute uint8_t internalRedirectCount; + + // These properties should only be written externally when they must be + // propagated across an internal redirect. For example, when a service + // worker interception falls back to network we need to copy the original + // timing values to the new nsHttpChannel. + [noscript] attribute TimeStamp channelCreation; + [noscript] attribute TimeStamp asyncOpen; + + // The following are only set when the request is intercepted by a service + // worker no matter the response is synthesized. + [noscript] attribute TimeStamp launchServiceWorkerStart; + [noscript] attribute TimeStamp launchServiceWorkerEnd; + [noscript] attribute TimeStamp dispatchFetchEventStart; + [noscript] attribute TimeStamp dispatchFetchEventEnd; + [noscript] attribute TimeStamp handleFetchEventStart; + [noscript] attribute TimeStamp handleFetchEventEnd; + + // The following are only set when the document is not (only) read from the + // cache + [noscript] readonly attribute TimeStamp domainLookupStart; + [noscript] readonly attribute TimeStamp domainLookupEnd; + [noscript] readonly attribute TimeStamp connectStart; + [noscript] readonly attribute TimeStamp tcpConnectEnd; + [noscript] readonly attribute TimeStamp secureConnectionStart; + [noscript] readonly attribute TimeStamp connectEnd; + [noscript] readonly attribute TimeStamp requestStart; + [noscript] readonly attribute TimeStamp responseStart; + [noscript] readonly attribute TimeStamp responseEnd; + + // The redirect attributes timings must be writeble, se we can transfer + // the data from one channel to the redirected channel. + [noscript] attribute TimeStamp redirectStart; + [noscript] attribute TimeStamp redirectEnd; + + // The initiator type + [noscript] attribute AString initiatorType; + + // This flag should be set to false only if a cross-domain redirect occurred + [noscript] attribute boolean allRedirectsSameOrigin; + // This flag is set to false if the timing allow check fails + [noscript] attribute boolean allRedirectsPassTimingAllowCheck; + // Implements the timing-allow-check to determine if we should report + // timing info for the resourceTiming object. + [noscript] boolean timingAllowCheck(in nsIPrincipal origin); + %{C++ + inline bool TimingAllowCheck(nsIPrincipal* aOrigin) { + bool allowed = false; + return NS_SUCCEEDED(TimingAllowCheck(aOrigin, &allowed)) && allowed; + } + %} + + // The following are only set if the document is (partially) read from the + // cache + [noscript] readonly attribute TimeStamp cacheReadStart; + [noscript] readonly attribute TimeStamp cacheReadEnd; + + // The time when the transaction was submitted to the Connection Manager. + // Not reported to resource/navigation timing, only for performance telemetry. + [noscript] readonly attribute TimeStamp transactionPending; + + // All following are PRTime versions of the above. + readonly attribute PRTime channelCreationTime; + readonly attribute PRTime asyncOpenTime; + readonly attribute PRTime launchServiceWorkerStartTime; + readonly attribute PRTime launchServiceWorkerEndTime; + readonly attribute PRTime dispatchFetchEventStartTime; + readonly attribute PRTime dispatchFetchEventEndTime; + readonly attribute PRTime handleFetchEventStartTime; + readonly attribute PRTime handleFetchEventEndTime; + readonly attribute PRTime domainLookupStartTime; + readonly attribute PRTime domainLookupEndTime; + readonly attribute PRTime connectStartTime; + readonly attribute PRTime tcpConnectEndTime; + readonly attribute PRTime secureConnectionStartTime; + readonly attribute PRTime connectEndTime; + readonly attribute PRTime requestStartTime; + readonly attribute PRTime responseStartTime; + readonly attribute PRTime responseEndTime; + readonly attribute PRTime cacheReadStartTime; + readonly attribute PRTime cacheReadEndTime; + readonly attribute PRTime redirectStartTime; + readonly attribute PRTime redirectEndTime; + // Not reported to resource/navigation timing, only for performance telemetry. + readonly attribute PRTime transactionPendingTime; + + // If this attribute is false, this resource MUST NOT be reported in resource timing. + [noscript] attribute boolean reportResourceTiming; + + readonly attribute nsIArray serverTiming; + nsServerTimingArrayRef getNativeServerTiming(); +}; diff --git a/netwerk/base/nsITraceableChannel.idl b/netwerk/base/nsITraceableChannel.idl new file mode 100644 index 0000000000..4d325d4fcb --- /dev/null +++ b/netwerk/base/nsITraceableChannel.idl @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIStreamListener; + +/** + * A channel implementing this interface allows one to intercept its data by + * inserting intermediate stream listeners. + */ +[scriptable, uuid(68167b0b-ef34-4d79-a09a-8045f7c5140e)] +interface nsITraceableChannel : nsISupports +{ + /* + * Replace the channel's listener with a new one, and return the listener + * the channel used to have. The new listener intercepts OnStartRequest, + * OnDataAvailable and OnStopRequest calls and must pass them to + * the original listener after examination. If multiple callers replace + * the channel's listener, a chain of listeners is created. + * The caller of setNewListener has no way to control at which place + * in the chain its listener is placed. + * + * @param aMustApplyContentConversion Pass true if the new listener requires + * content conversion to already be applied by the channel. + * + * Note: The caller of setNewListener must not delay passing + * OnStartRequest to the original listener. + * + * Note2: A channel may restrict when the listener can be replaced. + * It is not recommended to allow listener replacement after OnStartRequest + * has been called. + */ + nsIStreamListener setNewListener(in nsIStreamListener aListener, [optional] in boolean aMustApplyContentConversion); +}; diff --git a/netwerk/base/nsITransport.idl b/netwerk/base/nsITransport.idl new file mode 100644 index 0000000000..856c4e2000 --- /dev/null +++ b/netwerk/base/nsITransport.idl @@ -0,0 +1,162 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsITransportEventSink; +interface nsIEventTarget; + +/** + * nsITransport + * + * This interface provides a common way of accessing i/o streams connected + * to some resource. This interface does not in any way specify the resource. + * It provides methods to open blocking or non-blocking, buffered or unbuffered + * streams to the resource. The name "transport" is meant to connote the + * inherent data transfer implied by this interface (i.e., data is being + * transfered in some fashion via the streams exposed by this interface). + * + * A transport can have an event sink associated with it. The event sink + * receives transport-specific events as the transfer is occuring. For a + * socket transport, these events can include status about the connection. + * See nsISocketTransport for more info about socket transport specifics. + */ +[scriptable, uuid(2a8c6334-a5e6-4ec3-9865-1256541446fb)] +interface nsITransport : nsISupports +{ + /** + * Open flags. + */ + const unsigned long OPEN_BLOCKING = 1<<0; + const unsigned long OPEN_UNBUFFERED = 1<<1; + + /** + * Open an input stream on this transport. + * + * Flags have the following meaning: + * + * OPEN_BLOCKING + * If specified, then the resulting stream will have blocking stream + * semantics. This means that if the stream has no data and is not + * closed, then reading from it will block the calling thread until + * at least one byte is available or until the stream is closed. + * If this flag is NOT specified, then the stream has non-blocking + * stream semantics. This means that if the stream has no data and is + * not closed, then reading from it returns NS_BASE_STREAM_WOULD_BLOCK. + * In addition, in non-blocking mode, the stream is guaranteed to + * support nsIAsyncInputStream. This interface allows the consumer of + * the stream to be notified when the stream can again be read. + * + * OPEN_UNBUFFERED + * If specified, the resulting stream may not support ReadSegments. + * ReadSegments is only gauranteed to be implemented when this flag is + * NOT specified. + * + * @param aFlags + * optional transport specific flags. + * @param aSegmentSize + * if OPEN_UNBUFFERED is not set, then this parameter specifies the + * size of each buffer segment (pass 0 to use default value). + * @param aSegmentCount + * if OPEN_UNBUFFERED is not set, then this parameter specifies the + * maximum number of buffer segments (pass 0 to use default value). + */ + nsIInputStream openInputStream(in unsigned long aFlags, + in unsigned long aSegmentSize, + in unsigned long aSegmentCount); + + /** + * Open an output stream on this transport. + * + * Flags have the following meaning: + * + * OPEN_BLOCKING + * If specified, then the resulting stream will have blocking stream + * semantics. This means that if the stream is full and is not closed, + * then writing to it will block the calling thread until ALL of the + * data can be written or until the stream is closed. If this flag is + * NOT specified, then the stream has non-blocking stream semantics. + * This means that if the stream is full and is not closed, then writing + * to it returns NS_BASE_STREAM_WOULD_BLOCK. In addition, in non- + * blocking mode, the stream is guaranteed to support + * nsIAsyncOutputStream. This interface allows the consumer of the + * stream to be notified when the stream can again accept more data. + * + * OPEN_UNBUFFERED + * If specified, the resulting stream may not support WriteSegments and + * WriteFrom. WriteSegments and WriteFrom are only guaranteed to be + * implemented when this flag is NOT specified. + * + * @param aFlags + * optional transport specific flags. + * @param aSegmentSize + * if OPEN_UNBUFFERED is not set, then this parameter specifies the + * size of each buffer segment (pass 0 to use default value). + * @param aSegmentCount + * if OPEN_UNBUFFERED is not set, then this parameter specifies the + * maximum number of buffer segments (pass 0 to use default value). + */ + nsIOutputStream openOutputStream(in unsigned long aFlags, + in unsigned long aSegmentSize, + in unsigned long aSegmentCount); + + /** + * Close the transport and any open streams. + * + * @param aReason + * the reason for closing the stream. + */ + void close(in nsresult aReason); + + /** + * Set the transport event sink. + * + * @param aSink + * receives transport layer notifications + * @param aEventTarget + * indicates the event target to which the notifications should + * be delivered. if NULL, then the notifications may occur on + * any thread. + */ + void setEventSink(in nsITransportEventSink aSink, + in nsIEventTarget aEventTarget); + + /** + * Generic nsITransportEventSink status codes. nsITransport + * implementations may override these status codes with their own more + * specific status codes (e.g., see nsISocketTransport). + * + * In C++, these constants have a type of uint32_t, so C++ callers must use + * the NS_NET_STATUS_* constants defined below, which have a type of + * nsresult. + */ + const unsigned long STATUS_READING = 0x4b0008; + const unsigned long STATUS_WRITING = 0x4b0009; +}; + +[scriptable, uuid(EDA4F520-67F7-484b-A691-8C3226A5B0A6)] +interface nsITransportEventSink : nsISupports +{ + /** + * Transport status notification. + * + * @param aTransport + * the transport sending this status notification. + * @param aStatus + * the transport status. See nsISocketTransport for socket specific + * status codes and more comments. + * @param aProgress + * the amount of data either read or written depending on the value + * of the status code. this value is relative to aProgressMax. + * @param aProgressMax + * the maximum amount of data that will be read or written. if + * unknown, -1 will be passed. + */ + void onTransportStatus(in nsITransport aTransport, + in nsresult aStatus, + in long long aProgress, + in long long aProgressMax); +}; diff --git a/netwerk/base/nsIUDPSocket.idl b/netwerk/base/nsIUDPSocket.idl new file mode 100644 index 0000000000..41bd0ebc17 --- /dev/null +++ b/netwerk/base/nsIUDPSocket.idl @@ -0,0 +1,407 @@ +/* vim:set ts=4 sw=4 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsINetAddr; +interface nsIUDPSocketListener; +interface nsIUDPSocketSyncListener; +interface nsIUDPMessage; +interface nsISocketTransport; +interface nsIOutputStream; +interface nsIInputStream; +interface nsIPrincipal; + +%{ C++ +#include "nsTArrayForwardDeclare.h" +namespace mozilla { +namespace net { +union NetAddr; +} +} +%} +native NetAddr(mozilla::net::NetAddr); +[ptr] native NetAddrPtr(mozilla::net::NetAddr); +[ref] native Uint8TArrayRef(FallibleTArray<uint8_t>); + +/** + * nsIUDPSocket + * + * An interface to a UDP socket that can accept incoming connections. + */ +[scriptable, uuid(d423bf4e-4499-40cf-bc03-153e2bf206d1)] +interface nsIUDPSocket : nsISupports +{ + /** + * init + * + * This method initializes a UDP socket. + * + * @param aPort + * The port of the UDP socket. Pass -1 to indicate no preference, + * and a port will be selected automatically. + * @param aLoopbackOnly + * If true, the UDP socket will only respond to connections on the + * local loopback interface. Otherwise, it will accept connections + * from any interface. To specify a particular network interface, + * use initWithAddress. + * @param aPrincipal + * The principal connected to this socket. + * @param aAddressReuse + * If true, the socket is allowed to be bound to an address that is + * already in use. Default is true. + */ + [optional_argc] void init(in long aPort, + in boolean aLoopbackOnly, + in nsIPrincipal aPrincipal, + [optional] in boolean aAddressReuse); + + [optional_argc] void init2(in AUTF8String aAddr, + in long aPort, + in nsIPrincipal aPrincipal, + [optional] in boolean aAddressReuse); + + /** + * initWithAddress + * + * This method initializes a UDP socket, and binds it to a particular + * local address (and hence a particular local network interface). + * + * @param aAddr + * The address to which this UDP socket should be bound. + * @param aPrincipal + * The principal connected to this socket. + * @param aAddressReuse + * If true, the socket is allowed to be bound to an address that is + * already in use. Default is true. + */ + [noscript, optional_argc] void initWithAddress([const] in NetAddrPtr aAddr, + in nsIPrincipal aPrincipal, + [optional] in boolean aAddressReuse); + + /** + * close + * + * This method closes a UDP socket. This does not affect already + * connected client sockets (i.e., the nsISocketTransport instances + * created from this UDP socket). This will cause the onStopListening + * event to asynchronously fire with a status of NS_BINDING_ABORTED. + */ + void close(); + + /** + * asyncListen + * + * This method puts the UDP socket in the listening state. It will + * asynchronously listen for and accept client connections. The listener + * will be notified once for each client connection that is accepted. The + * listener's onSocketAccepted method will be called on the same thread + * that called asyncListen (the calling thread must have a nsIEventTarget). + * + * The listener will be passed a reference to an already connected socket + * transport (nsISocketTransport). See below for more details. + * + * @param aListener + * The listener to be notified when client connections are accepted. + */ + void asyncListen(in nsIUDPSocketListener aListener); + + /** + * This adds a nsIUDPSocketSyncListener listener (defined below). + * When data is available onPacketReceived is called and the lisener uses + * recvWithAddr to actually retrive data from the socket. + * The listener can be use only if it runs on the socket thread. + * If it is used off the socket thread there is a risk of triggering a bug + * in OS thatcan cause a crash. + */ + void syncListen(in nsIUDPSocketSyncListener aListener); + + /** + * connect + * + * This method connects the UDP socket to a remote UDP address. + * + * @param aRemoteAddr + * The remote address to connect to + */ + void connect([const] in NetAddrPtr aAddr); + + /** + * Returns the local address of this UDP socket + */ + readonly attribute nsINetAddr localAddr; + + /** + * Returns the port of this UDP socket. + */ + readonly attribute long port; + + /** + * Returns the address to which this UDP socket is bound. Since a + * UDP socket may be bound to multiple network devices, this address + * may not necessarily be specific to a single network device. In the + * case of an IP socket, the IP address field would be zerod out to + * indicate a UDP socket bound to all network devices. Therefore, + * this method cannot be used to determine the IP address of the local + * system. See nsIDNSService::myHostName if this is what you need. + */ + [noscript] NetAddr getAddress(); + + /** + * send + * + * Send out the datagram to specified remote host and port. + * DNS lookup will be triggered. + * + * @param host The remote host name. + * @param port The remote port. + * @param data The buffer containing the data to be written. + * @return number of bytes written. (0 or length of data) + */ + unsigned long send(in AUTF8String host, in unsigned short port, + in Array<uint8_t> data); + + /** + * sendWithAddr + * + * Send out the datagram to specified remote host and port. + * + * @param addr The remote host address. + * @param data The buffer containing the data to be written. + * @return number of bytes written. (0 or length of data) + */ + unsigned long sendWithAddr(in nsINetAddr addr, + in Array<uint8_t> data); + + + /** + * Receive a datagram. + * @param addr The remote host address. + * @param data The buffer to store received datagram. + */ + [noscript] void recvWithAddr(out NetAddr addr, + out Array<uint8_t> data); + + /** + * sendWithAddress + * + * Send out the datagram to specified remote address and port. + * + * @param addr The remote host address. + * @param data The buffer containing the data to be written. + * @return number of bytes written. (0 or length of data) + */ + [noscript] unsigned long sendWithAddress([const] in NetAddrPtr addr, + [array, size_is(length), const] in uint8_t data, + in unsigned long length); + + /** + * sendBinaryStream + * + * Send out the datagram to specified remote address and port. + * + * @param host The remote host name. + * @param port The remote port. + * @param stream The input stream to be sent. This must be a buffered stream implementation. + */ + void sendBinaryStream(in AUTF8String host, in unsigned short port, + in nsIInputStream stream); + + /** + * sendBinaryStreamWithAddress + * + * Send out the datagram to specified remote address and port. + * + * @param addr The remote host address. + * @param stream The input stream to be sent. This must be a buffered stream implementation. + */ + void sendBinaryStreamWithAddress([const] in NetAddrPtr addr, + in nsIInputStream stream); + + /** + * joinMulticast + * + * Join the multicast group specified by |addr|. You are then able to + * receive future datagrams addressed to the group. + * + * @param addr + * The multicast group address. + * @param iface + * The local address of the interface on which to join the group. If + * this is not specified, the OS may join the group on all interfaces + * or only the primary interface. + */ + void joinMulticast(in AUTF8String addr, [optional] in AUTF8String iface); + [noscript] void joinMulticastAddr([const] in NetAddr addr, + [const, optional] in NetAddrPtr iface); + + /** + * leaveMulticast + * + * Leave the multicast group specified by |addr|. You will no longer + * receive future datagrams addressed to the group. + * + * @param addr + * The multicast group address. + * @param iface + * The local address of the interface on which to leave the group. + * If this is not specified, the OS may leave the group on all + * interfaces or only the primary interface. + */ + void leaveMulticast(in AUTF8String addr, [optional] in AUTF8String iface); + [noscript] void leaveMulticastAddr([const] in NetAddr addr, + [const, optional] in NetAddrPtr iface); + + /** + * multicastLoopback + * + * Whether multicast datagrams sent via this socket should be looped back to + * this host (assuming this host has joined the relevant group). Defaults + * to true. + * Note: This is currently write-only. + */ + attribute boolean multicastLoopback; + + /** + * multicastInterface + * + * The interface that should be used for sending future multicast datagrams. + * Note: This is currently write-only. + */ + attribute AUTF8String multicastInterface; + + /** + * multicastInterfaceAddr + * + * The interface that should be used for sending future multicast datagrams. + * Note: This is currently write-only. + */ + [noscript] attribute NetAddr multicastInterfaceAddr; + + /** + * recvBufferSize + * + * The size of the receive buffer. Default depends on the OS. + */ + [noscript] attribute long recvBufferSize; + + /** + * sendBufferSize + * + * The size of the send buffer. Default depends on the OS. + */ + [noscript] attribute long sendBufferSize; + + /** + * dontFragment + * + * The don't fragment flag. + * The socket must be initialized before calling this function. + */ + [noscript] attribute boolean dontFragment; +}; + +/** + * nsIUDPSocketListener + * + * This interface is notified whenever a UDP socket accepts a new connection. + * The transport is in the connected state, and read/write streams can be opened + * using the normal nsITransport API. The address of the client can be found by + * calling the nsISocketTransport::GetAddress method or by inspecting + * nsISocketTransport::GetHost, which returns a string representation of the + * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal). + */ +[scriptable, uuid(2E4B5DD3-7358-4281-B81F-10C62EF39CB5)] +interface nsIUDPSocketListener : nsISupports +{ + /** + * onPacketReceived + * + * This method is called when a client sends an UDP packet. + * + * @param aSocket + * The UDP socket. + * @param aMessage + * The message. + */ + void onPacketReceived(in nsIUDPSocket aSocket, + in nsIUDPMessage aMessage); + + /** + * onStopListening + * + * This method is called when the listening socket stops for some reason. + * The UDP socket is effectively dead after this notification. + * + * @param aSocket + * The UDP socket. + * @param aStatus + * The reason why the UDP socket stopped listening. If the + * UDP socket was manually closed, then this value will be + * NS_BINDING_ABORTED. + */ + void onStopListening(in nsIUDPSocket aSocket, in nsresult aStatus); +}; + +/** + * nsIUDPMessage + * + * This interface is used to encapsulate an incomming UDP message + */ +[scriptable, builtinclass, uuid(afdc743f-9cc0-40d8-b442-695dc54bbb74)] +interface nsIUDPMessage : nsISupports +{ + /** + * Address of the source of the message + */ + readonly attribute nsINetAddr fromAddr; + + /** + * Data of the message + */ + readonly attribute ACString data; + + /** + * Stream to send a response + */ + readonly attribute nsIOutputStream outputStream; + + /** + * Raw Data of the message + */ + [implicit_jscontext] readonly attribute jsval rawData; + [noscript, notxpcom, nostdcall] Uint8TArrayRef getDataAsTArray(); +}; + +[uuid(99f3d085-3d69-45da-a2c2-a6176af617cb)] +interface nsIUDPSocketSyncListener : nsISupports +{ + /** + * onPacketReceived + * + * This method is called when a client sends an UDP packet. + * + * @param aSocket + * The UDP socket. + * @param aMessage + * The message. + */ + void onPacketReceived(in nsIUDPSocket aSocket); + + /** + * onStopListening + * + * This method is called when the listening socket stops for some reason. + * The UDP socket is effectively dead after this notification. + * + * @param aSocket + * The UDP socket. + * @param aStatus + * The reason why the UDP socket stopped listening. If the + * UDP socket was manually closed, then this value will be + * NS_BINDING_ABORTED. + */ + void onStopListening(in nsIUDPSocket aSocket, in nsresult aStatus); +}; diff --git a/netwerk/base/nsIURI.idl b/netwerk/base/nsIURI.idl new file mode 100644 index 0000000000..993e0374a8 --- /dev/null +++ b/netwerk/base/nsIURI.idl @@ -0,0 +1,332 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * URIs are essentially structured names for things -- anything. This interface + * provides accessors to get the most basic components of an URI. + * If you need to change some parts of the URI use nsIURIMutator. + * Subclasses, including nsIURL, impose greater structure on the URI. + * + * This interface follows Tim Berners-Lee's URI spec (RFC3986) [1], where the + * basic URI components are defined as such: + * <pre> + * ftp://username:password@hostname:portnumber/pathname?query#ref + * \ / \ / \ / \ /\ / \ / \ / + * - --------------- ------ -------- ------- --- - + * | | | | | | | + * | | | | FilePath Query Ref + * | | | Port \ / + * | | Host / ------------ + * | UserPass / | + * Scheme / Path + * \ / + * -------------------------------- + * | + * PrePath + * </pre> + * The definition of the URI components has been extended to allow for + * internationalized domain names [2] and the more generic IRI structure [3]. + * + * [1] https://tools.ietf.org/html/rfc3986 + * [2] https://tools.ietf.org/html/rfc5890 + * [3] https://tools.ietf.org/html/rfc3987 + */ + +%{C++ +#include "nsString.h" + +#undef GetPort // XXX Windows! +#undef SetPort // XXX Windows! + +namespace mozilla { +class Encoding; +namespace ipc { +class URIParams; +} // namespace ipc +} // namespace mozilla +%} + +[ptr] native Encoding(const mozilla::Encoding); +[ref] native URIParams(mozilla::ipc::URIParams); +interface nsIURIMutator; + +/** + * nsIURI - interface for an uniform resource identifier w/ i18n support. + * + * AUTF8String attributes may contain unescaped UTF-8 characters. + * Consumers should be careful to escape the UTF-8 strings as necessary, but + * should always try to "display" the UTF-8 version as provided by this + * interface. + * + * AUTF8String attributes may also contain escaped characters. + * + * Unescaping URI segments is unadvised unless there is intimate + * knowledge of the underlying charset or there is no plan to display (or + * otherwise enforce a charset on) the resulting URI substring. + * + * The correct way to create an nsIURI from a string is via + * nsIIOService.newURI. + * + * NOTE: nsBinaryInputStream::ReadObject contains a hackaround to intercept the + * old (pre-gecko6) nsIURI IID and swap in the current IID instead, in order + * for sessionstore to work after an upgrade. If this IID is revved further, + * we will need to add additional checks there for all intermediate IIDs, until + * ContentPrincipal is fixed to serialize its URIs as nsISupports (bug 662693). + */ +[scriptable, builtinclass, uuid(92073a54-6d78-4f30-913a-b871813208c6)] +interface nsIURI : nsISupports +{ + /************************************************************************ + * The URI is broken down into the following principal components: + */ + + /** + * Returns a string representation of the URI. + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String spec; + +%{ C++ + // An infallible wrapper for GetSpec() that returns a failure indication + // string if GetSpec() fails. It is most useful for creating + // logging/warning/error messages produced for human consumption, and when + // matching a URI spec against a fixed spec such as about:blank. + nsCString GetSpecOrDefault() + { + nsCString spec; + nsresult rv = GetSpec(spec); + if (NS_FAILED(rv)) { + spec.AssignLiteral("[nsIURI::GetSpec failed]"); + } + return spec; + } +%} + + /** + * The prePath (eg. scheme://user:password@host:port) returns the string + * before the path. This is useful for authentication or managing sessions. + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String prePath; + + /** + * The Scheme is the protocol to which this URI refers. The scheme is + * restricted to the US-ASCII charset per RFC3986. + */ + readonly attribute ACString scheme; + + /** + * The username:password (or username only if value doesn't contain a ':') + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String userPass; + + /** + * The optional username and password, assuming the preHost consists of + * username:password. + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String username; + readonly attribute AUTF8String password; + + /** + * The host:port (or simply the host, if port == -1). + */ + readonly attribute AUTF8String hostPort; + + /** + * The host is the internet domain name to which this URI refers. It could + * be an IPv4 (or IPv6) address literal. Otherwise it is an ASCII or punycode + * encoded string. + */ + readonly attribute AUTF8String host; + + /** + * A port value of -1 corresponds to the protocol's default port (eg. -1 + * implies port 80 for http URIs). + */ + readonly attribute long port; + + /** + * The path, typically including at least a leading '/' (but may also be + * empty, depending on the protocol). + * + * Some characters may be escaped. + * + * This attribute contains query and ref parts for historical reasons. + * Use the 'filePath' attribute if you do not want those parts included. + */ + readonly attribute AUTF8String pathQueryRef; + + + /************************************************************************ + * An URI supports the following methods: + */ + + /** + * URI equivalence test (not a strict string comparison). + * + * eg. http://foo.com:80/ == http://foo.com/ + */ + boolean equals(in nsIURI other); + + /** + * An optimization to do scheme checks without requiring the users of nsIURI + * to GetScheme, thereby saving extra allocating and freeing. Returns true if + * the schemes match (case ignored). + */ + [infallible] boolean schemeIs(in string scheme); + + /** + * This method resolves a relative string into an absolute URI string, + * using this URI as the base. + * + * NOTE: some implementations may have no concept of a relative URI. + */ + AUTF8String resolve(in AUTF8String relativePath); + + + /************************************************************************ + * Additional attributes: + */ + + /** + * The URI spec with an ASCII compatible encoding. Host portion follows + * the IDNA draft spec. Other parts are URL-escaped per the rules of + * RFC2396. The result is strictly ASCII. + */ + readonly attribute ACString asciiSpec; + + /** + * The host:port (or simply the host, if port == -1), with an ASCII compatible + * encoding. Host portion follows the IDNA draft spec. The result is strictly + * ASCII. + */ + readonly attribute ACString asciiHostPort; + + /** + * The URI host with an ASCII compatible encoding. Follows the IDNA + * draft spec for converting internationalized domain names (UTF-8) to + * ASCII for compatibility with existing internet infrasture. + */ + readonly attribute ACString asciiHost; + + /************************************************************************ + * Additional attribute & methods added for .ref support: + */ + + /** + * Returns the reference portion (the part after the "#") of the URI. + * If there isn't one, an empty string is returned. + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String ref; + + /** + * URI equivalence test (not a strict string comparison), ignoring + * the value of the .ref member. + * + * eg. http://foo.com/# == http://foo.com/ + * http://foo.com/#aaa == http://foo.com/#bbb + */ + boolean equalsExceptRef(in nsIURI other); + + /** + * returns a string for the current URI with the ref element cleared. + */ + readonly attribute AUTF8String specIgnoringRef; + + /** + * Returns if there is a reference portion (the part after the "#") of the URI. + */ + readonly attribute boolean hasRef; + + /** + * Returns if there is user and pass in the URI. + */ + readonly attribute boolean hasUserPass; + + /************************************************************************ + * Additional attributes added for .query support: + */ + + /** + * Returns a path including the directory and file portions of a + * URL. For example, the filePath of "http://host/foo/bar.html#baz" + * is "/foo/bar.html". + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String filePath; + + /** + * Returns the query portion (the part after the "?") of the URL. + * If there isn't one, an empty string is returned. + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String query; + + /** + * Returns if there is a query portion (the part after the "?") of the URI. + */ + readonly attribute boolean hasQuery; + + /** + * If the URI has a punycode encoded hostname, this will hold the UTF8 + * representation of that hostname (if that representation doesn't contain + * blacklisted characters, and the network.IDN_show_punycode pref is false) + * Otherwise, if the hostname is ASCII, it will return the same as .asciiHost + */ + readonly attribute AUTF8String displayHost; + + /** + * The displayHost:port (or simply the displayHost, if port == -1). + */ + readonly attribute AUTF8String displayHostPort; + + /** + * Returns the same as calling .spec, only with a UTF8 encoded hostname + * (if that hostname doesn't contain blacklisted characters, and + * the network.IDN_show_punycode pref is false) + */ + readonly attribute AUTF8String displaySpec; + + /** + * Returns the same as calling .prePath, only with a UTF8 encoded hostname + * (if that hostname doesn't contain blacklisted characters, and + * the network.IDN_show_punycode pref is false) + */ + readonly attribute AUTF8String displayPrePath; + + /** + * Returns an nsIURIMutator that can be used to make changes to the URI. + * After performing the setter operations on the mutator, one may call + * mutator.finalize() to get a new immutable URI with the desired + * properties. + */ + nsIURIMutator mutate(); + + /** + * Serializes a URI object to a URIParams data structure in order for being + * passed over IPC. For deserialization, see nsIURIMutator. + */ + [noscript, notxpcom] void serialize(in URIParams aParams); + + %{C++ + // MOZ_DBG support + friend std::ostream& operator<<(std::ostream& aOut, const nsIURI& aURI) { + nsIURI* uri = const_cast<nsIURI*>(&aURI); + return aOut << "nsIURI { " << uri->GetSpecOrDefault() << " }"; + } + %} +}; diff --git a/netwerk/base/nsIURIMutator.idl b/netwerk/base/nsIURIMutator.idl new file mode 100644 index 0000000000..c4d9293313 --- /dev/null +++ b/netwerk/base/nsIURIMutator.idl @@ -0,0 +1,540 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIURI; +interface nsIObjectInputStream; +interface nsIURIMutator; + +%{C++ +#include "nsString.h" +#include "nsCOMPtr.h" +#include <utility> + +#undef SetPort // XXX Windows! + +namespace mozilla { +class Encoding; +} + +namespace mozilla { +namespace ipc { +class URIParams; +} // namespace ipc +} // namespace mozilla + +template <class T> +class BaseURIMutator +{ +// This is the base class that can be extended by implementors of nsIURIMutator +// in order to avoid code duplication +// Class type T should be the type of the class that implements nsIURI +protected: + virtual T* Create() + { + return new T(); + } + + [[nodiscard]] nsresult InitFromURI(T* aURI) + { + nsCOMPtr<nsIURI> clone; + nsresult rv = aURI->Clone(getter_AddRefs(clone)); + if (NS_FAILED(rv)) { + return rv; + } + mURI = static_cast<T*>(clone.get()); + return NS_OK; + } + + [[nodiscard]] nsresult InitFromInputStream(nsIObjectInputStream* aStream) + { + RefPtr<T> uri = Create(); + nsresult rv = uri->ReadPrivate(aStream); + if (NS_FAILED(rv)) { + return rv; + } + mURI = std::move(uri); + return NS_OK; + } + + [[nodiscard]] nsresult InitFromIPCParams(const mozilla::ipc::URIParams& aParams) + { + RefPtr<T> uri = Create(); + bool ret = uri->Deserialize(aParams); + if (!ret) { + return NS_ERROR_FAILURE; + } + mURI = std::move(uri); + return NS_OK; + } + + [[nodiscard]] nsresult InitFromSpec(const nsACString& aSpec) + { + nsresult rv = NS_OK; + RefPtr<T> uri; + if (mURI) { + // This only works because all other Init methods create a new object + mURI.swap(uri); + } else { + uri = Create(); + } + + rv = uri->SetSpecInternal(aSpec); + if (NS_FAILED(rv)) { + return rv; + } + mURI = std::move(uri); + return NS_OK; + } + + RefPtr<T> mURI; +}; + +// Since most implementations of nsIURIMutator would extend BaseURIMutator, +// some methods would have the same implementation. We provide a useful macro +// to avoid code duplication. +#define NS_DEFINE_NSIMUTATOR_COMMON \ + [[nodiscard]] NS_IMETHOD \ + Deserialize(const mozilla::ipc::URIParams& aParams) override \ + { \ + return InitFromIPCParams(aParams); \ + } \ + \ + [[nodiscard]] NS_IMETHOD \ + Finalize(nsIURI** aURI) override \ + { \ + mURI.forget(aURI); return NS_OK; \ + } \ + \ + [[nodiscard]] NS_IMETHOD \ + SetSpec(const nsACString& aSpec, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return InitFromSpec(aSpec); \ + } \ + +// Implements AddRef, Release and QueryInterface for the mutator +#define NS_IMPL_NSIURIMUTATOR_ISUPPORTS(aClass, ...) \ + NS_IMPL_ADDREF(aClass) \ + NS_IMPL_RELEASE(aClass) \ + NS_IMPL_NSIURIMUTATOR_QUERY_INTERFACE(aClass, __VA_ARGS__) \ + +// The list of interfaces is queried and an AddRef-ed pointer is returned if +// there is a match. Otherwise, we call QueryInterface on mURI and return. +// The reason for this specialized QueryInterface implementation is that we +// we want to be able to instantiate the mutator for a given CID of a +// nsIURI implementation, call nsISerializable.Read() on the mutator to +// deserialize the URI then QueryInterface the mutator to an nsIURI interface. +// See bug 1442239. +// If you QueryInterface a mutator to an interface of the URI +// implementation this is similar to calling Finalize. +#define NS_IMPL_NSIURIMUTATOR_QUERY_INTERFACE(aClass, ...) \ + static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \ + "Need more arguments"); \ + NS_INTERFACE_MAP_BEGIN(aClass) \ + nsCOMPtr<nsIURI> uri; \ + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIMutator) \ + MOZ_FOR_EACH(NS_INTERFACE_MAP_ENTRY, (), (__VA_ARGS__)) \ + if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { \ + foundInterface = nullptr; \ + } else \ + if (mURI && \ + NS_SUCCEEDED(mURI->QueryInterface(aIID, getter_AddRefs(uri)))) { \ + mURI = nullptr; \ + foundInterface = uri.get(); \ + } else \ + NS_INTERFACE_MAP_END \ + +%} + +[ptr] native Encoding(const mozilla::Encoding); +[ref] native const_URIParams_ref(const mozilla::ipc::URIParams); + +[scriptable, builtinclass, uuid(1fc53257-898b-4c5e-b69c-05bc84b4cd8f)] +interface nsIURISetSpec : nsISupports +{ + /** + * This setter is different from all other setters because it may be used to + * initialize the object. We define it separately allowing mutator implementors + * to define it separately, while the rest of the setters may be simply + * forwarded to the mutable URI. + */ + [must_use] nsIURIMutator setSpec(in AUTF8String aSpec); +}; + +/** + * These methods allow the mutator to change various parts of the URI. + * They return the same nsIURIMutator so that we may chain setter operations: + * Example: + * let newURI = uri.mutate() + * .setSpec("http://example.com") + * .setQuery("hello") + * .finalize(); + */ +[scriptable, builtinclass, uuid(5403a6ec-99d7-405e-8b45-9f805bbdfcef)] +interface nsIURISetters : nsIURISetSpec +{ + /** + * Setting the scheme outside of a protocol handler implementation is highly + * discouraged since that will generally lead to incorrect results. + */ + [must_use] nsIURIMutator setScheme(in AUTF8String aScheme); + [must_use] nsIURIMutator setUserPass(in AUTF8String aUserPass); + [must_use] nsIURIMutator setUsername(in AUTF8String aUsername); + [must_use] nsIURIMutator setPassword(in AUTF8String aPassword); + + /** + * If you setHostPort to a value that only has a host part, the port + * will not be reset. To reset the port set it to -1 beforehand. + * If setting the host succeeds, this method will return NS_OK, even if + * setting the port fails (error in parsing the port, or value out of range) + */ + [must_use] nsIURIMutator setHostPort(in AUTF8String aHostPort); + [must_use] nsIURIMutator setHost(in AUTF8String aHost); + [must_use] nsIURIMutator setPort(in long aPort); + [must_use] nsIURIMutator setPathQueryRef(in AUTF8String aPathQueryRef); + [must_use] nsIURIMutator setRef(in AUTF8String aRef); + [must_use] nsIURIMutator setFilePath(in AUTF8String aFilePath); + [must_use] nsIURIMutator setQuery(in AUTF8String aQuery); + [must_use, noscript] nsIURIMutator setQueryWithEncoding(in AUTF8String query, in Encoding encoding); +}; + +%{C++ + +// Using this macro instead of NS_FORWARD_SAFE_NSIURISETTERS makes chaining +// setter operations possible. +#define NS_FORWARD_SAFE_NSIURISETTERS_RET(_to) \ + [[nodiscard]] NS_IMETHOD \ + SetScheme(const nsACString& aScheme, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetScheme(aScheme); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetUserPass(const nsACString& aUserPass, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetUserPass(aUserPass); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetUsername(const nsACString& aUsername, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetUsername(aUsername); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetPassword(const nsACString& aPassword, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetPassword(aPassword); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetHostPort(const nsACString& aHostPort, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetHostPort(aHostPort); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetHost(const nsACString& aHost, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetHost(aHost); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetPort(int32_t aPort, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetPort(aPort); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetPathQueryRef(const nsACString& aPathQueryRef, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetPathQueryRef(aPathQueryRef); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetRef(const nsACString& aRef, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetRef(aRef); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetFilePath(const nsACString& aFilePath, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetFilePath(aFilePath); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetQuery(const nsACString& aQuery, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetQuery(aQuery); \ + } \ + [[nodiscard]] NS_IMETHOD \ + SetQueryWithEncoding(const nsACString& query, const mozilla::Encoding *encoding, nsIURIMutator** aMutator) override \ + { \ + if (aMutator) NS_ADDREF(*aMutator = this); \ + return !_to ? NS_ERROR_NULL_POINTER : _to->SetQueryWithEncoding(query, encoding); \ + } \ + +%} + +[scriptable, builtinclass, uuid(4d1f3103-1c44-4dcd-b717-5d22a697a7d9)] +interface nsIURIMutator : nsIURISetters +{ + /** + * Initalizes the URI by reading IPC URIParams. + * See nsIURI. + */ + [noscript, notxpcom, must_use] + nsresult deserialize(in const_URIParams_ref aParams); + + /** + * Finishes changing or constructing the URI and returns an immutable URI. + */ + [must_use] + nsIURI finalize(); +}; + +%{C++ + +// This templated struct is used to extract the class type of the method +template <typename Method> +struct nsMethodTypeTraits; + +template <class C, typename R, typename... As> +struct nsMethodTypeTraits<R(C::*)(As...)> +{ + typedef C class_type; +}; + +#ifdef NS_HAVE_STDCALL +template <class C, typename R, typename... As> +struct nsMethodTypeTraits<R(__stdcall C::*)(As...)> +{ + typedef C class_type; +}; +#endif + + +// This class provides a useful helper that allows chaining of setter operations +class MOZ_STACK_CLASS NS_MutateURI +{ +public: + explicit NS_MutateURI(nsIURI* aURI); + explicit NS_MutateURI(const char * aContractID); + + explicit NS_MutateURI(nsIURIMutator* m) + { + mStatus = m ? NS_OK : NS_ERROR_NULL_POINTER; + mMutator = m; + NS_ENSURE_SUCCESS_VOID(mStatus); + } + + NS_MutateURI& SetSpec(const nsACString& aSpec) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetSpec(aSpec, nullptr); + return *this; + } + NS_MutateURI& SetScheme(const nsACString& aScheme) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetScheme(aScheme, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + NS_MutateURI& SetUserPass(const nsACString& aUserPass) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetUserPass(aUserPass, nullptr); + return *this; + } + NS_MutateURI& SetUsername(const nsACString& aUsername) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetUsername(aUsername, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + NS_MutateURI& SetPassword(const nsACString& aPassword) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetPassword(aPassword, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + NS_MutateURI& SetHostPort(const nsACString& aHostPort) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetHostPort(aHostPort, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + NS_MutateURI& SetHost(const nsACString& aHost) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetHost(aHost, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + NS_MutateURI& SetPort(int32_t aPort) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetPort(aPort, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + NS_MutateURI& SetPathQueryRef(const nsACString& aPathQueryRef) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetPathQueryRef(aPathQueryRef, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + NS_MutateURI& SetRef(const nsACString& aRef) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetRef(aRef, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + NS_MutateURI& SetFilePath(const nsACString& aFilePath) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetFilePath(aFilePath, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + NS_MutateURI& SetQuery(const nsACString& aQuery) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetQuery(aQuery, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + NS_MutateURI& SetQueryWithEncoding(const nsACString& query, const mozilla::Encoding *encoding) + { + if (NS_FAILED(mStatus)) { + return *this; + } + mStatus = mMutator->SetQueryWithEncoding(query, encoding, nullptr); + NS_ENSURE_SUCCESS(mStatus, *this); + return *this; + } + + /** + * This method allows consumers to call the methods declared in other + * interfaces implemented by the mutator object. + * + * Example: + * nsCOMPtr<nsIURI> uri; + * nsresult rv = NS_MutateURI(new URIClass::Mutator()) + * .SetSpec(aSpec) + * .Apply(&SomeInterface::Method, arg1, arg2) + * .Finalize(uri); + * + * If mMutator does not implement SomeInterface, do_QueryInterface will fail + * and the method will not be called. + * If aMethod does not exist, or if there is a mismatch between argument + * types, or the number of arguments, then there will be a compile error. + */ + template <typename Method, typename... Args> + NS_MutateURI& Apply(Method aMethod, Args&&... aArgs) + { + typedef typename nsMethodTypeTraits<Method>::class_type Interface; + NS_ENSURE_SUCCESS(mStatus, *this); + nsCOMPtr<Interface> target = do_QueryInterface(mMutator, &mStatus); + MOZ_ASSERT(NS_SUCCEEDED(mStatus), "URL object must implement interface"); + NS_ENSURE_SUCCESS(mStatus, *this); + mStatus = (target->*aMethod)(std::forward<Args>(aArgs)...); + return *this; + } + + template <class C> + [[nodiscard]] nsresult Finalize(nsCOMPtr<C>& aURI) + { + NS_ENSURE_SUCCESS(mStatus, mStatus); + + nsCOMPtr<nsIURI> uri; + mStatus = mMutator->Finalize(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(mStatus, mStatus); + + aURI = do_QueryInterface(uri, &mStatus); + NS_ENSURE_SUCCESS(mStatus, mStatus); + + mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail. + return NS_OK; + } + + // Overload for nsIURI to avoid query interface. + [[nodiscard]] nsresult Finalize(nsCOMPtr<nsIURI>& aURI) + { + if (NS_FAILED(mStatus)) return mStatus; + mStatus = mMutator->Finalize(getter_AddRefs(aURI)); + NS_ENSURE_SUCCESS(mStatus, mStatus); + + mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail. + return NS_OK; + } + + template <class C> + [[nodiscard]] nsresult Finalize(C** aURI) + { + NS_ENSURE_SUCCESS(mStatus, mStatus); + + nsCOMPtr<nsIURI> uri; + mStatus = mMutator->Finalize(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(mStatus, mStatus); + + nsCOMPtr<C> result = do_QueryInterface(uri, &mStatus); + NS_ENSURE_SUCCESS(mStatus, mStatus); + + result.forget(aURI); + mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail. + return NS_OK; + } + + [[nodiscard]] nsresult Finalize(nsIURI** aURI) + { + if (NS_FAILED(mStatus)) return mStatus; + mStatus = mMutator->Finalize(aURI); + NS_ENSURE_SUCCESS(mStatus, mStatus); + + mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail. + return NS_OK; + } + + nsresult GetStatus() { return mStatus; } +private: + nsresult mStatus; + nsCOMPtr<nsIURIMutator> mMutator; +}; + +%} diff --git a/netwerk/base/nsIURIMutatorUtils.cpp b/netwerk/base/nsIURIMutatorUtils.cpp new file mode 100644 index 0000000000..6c7fd7c92b --- /dev/null +++ b/netwerk/base/nsIURIMutatorUtils.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIURIMutator.h" +#include "nsIURI.h" +#include "nsComponentManagerUtils.h" + +static nsresult GetURIMutator(nsIURI* aURI, nsIURIMutator** aMutator) { + if (NS_WARN_IF(!aURI)) { + return NS_ERROR_INVALID_ARG; + } + return aURI->Mutate(aMutator); +} + +NS_MutateURI::NS_MutateURI(nsIURI* aURI) { + mStatus = GetURIMutator(aURI, getter_AddRefs(mMutator)); + NS_ENSURE_SUCCESS_VOID(mStatus); +} + +NS_MutateURI::NS_MutateURI(const char* aContractID) + : mStatus(NS_ERROR_NOT_INITIALIZED) { + mMutator = do_CreateInstance(aContractID, &mStatus); + MOZ_ASSERT(NS_SUCCEEDED(mStatus), "Called with wrong aContractID"); +} diff --git a/netwerk/base/nsIURIWithSpecialOrigin.idl b/netwerk/base/nsIURIWithSpecialOrigin.idl new file mode 100644 index 0000000000..3b8e2e0c97 --- /dev/null +++ b/netwerk/base/nsIURIWithSpecialOrigin.idl @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +/** + * nsIURIWithSpecialOrigin is implemented by URIs need to supply an origin that + * does not match the spec. This is exclusively used in comm-central's Mailnews module. + */ +[scriptable, builtinclass, uuid(4f65569b-d6fc-4580-94d9-21e775658a2a)] +interface nsIURIWithSpecialOrigin : nsISupports +{ + /** + * Special origin. + */ + readonly attribute nsIURI origin; +}; diff --git a/netwerk/base/nsIURL.idl b/netwerk/base/nsIURL.idl new file mode 100644 index 0000000000..11a9fa0f47 --- /dev/null +++ b/netwerk/base/nsIURL.idl @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIURI.idl" +interface nsIURIMutator; + +/** + * The nsIURL interface provides convenience methods that further + * break down the path portion of nsIURI: + * + * http://host/directory/fileBaseName.fileExtension?query + * http://host/directory/fileBaseName.fileExtension#ref + * \ \ / + * \ ----------------------- + * \ | / + * \ fileName / + * ---------------------------- + * | + * filePath + */ +[scriptable, builtinclass, uuid(86adcd89-0b70-47a2-b0fe-5bb2c5f37e31)] +interface nsIURL : nsIURI +{ + /************************************************************************* + * The URL path is broken down into the following principal components: + * + * attribute AUTF8String filePath; + * attribute AUTF8String query; + * + * These are inherited from nsIURI. + */ + + /************************************************************************* + * The URL filepath is broken down into the following sub-components: + */ + + /** + * Returns the directory portion of a URL. If the URL denotes a path to a + * directory and not a file, e.g. http://host/foo/bar/, then the Directory + * attribute accesses the complete /foo/bar/ portion, and the FileName is + * the empty string. If the trailing slash is omitted, then the Directory + * is /foo/ and the file is bar (i.e. this is a syntactic, not a semantic + * breakdown of the Path). And hence don't rely on this for something to + * be a definitely be a file. But you can get just the leading directory + * portion for sure. + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String directory; + + /** + * Returns the file name portion of a URL. If the URL denotes a path to a + * directory and not a file, e.g. http://host/foo/bar/, then the Directory + * attribute accesses the complete /foo/bar/ portion, and the FileName is + * the empty string. Note that this is purely based on searching for the + * last trailing slash. And hence don't rely on this to be a definite file. + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String fileName; + + /************************************************************************* + * The URL filename is broken down even further: + */ + + /** + * Returns the file basename portion of a filename in a url. + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String fileBaseName; + + /** + * Returns the file extension portion of a filename in a url. If a file + * extension does not exist, the empty string is returned. + * + * Some characters may be escaped. + */ + readonly attribute AUTF8String fileExtension; + + /** + * This method takes a uri and compares the two. The common uri portion + * is returned as a string. The minimum common uri portion is the + * protocol, and any of these if present: login, password, host and port + * If no commonality is found, "" is returned. If they are identical, the + * whole path with file/ref/etc. is returned. For file uris, it is + * expected that the common spec would be at least "file:///" since '/' is + * a shared common root. + * + * Examples: + * this.spec aURIToCompare.spec result + * 1) http://mozilla.org/ http://www.mozilla.org/ "" + * 2) http://foo.com/bar/ ftp://foo.com/bar/ "" + * 3) http://foo.com:8080/ http://foo.com/bar/ "" + * 4) ftp://user@foo.com/ ftp://user:pw@foo.com/ "" + * 5) ftp://foo.com/bar/ ftp://foo.com/bar ftp://foo.com/ + * 6) ftp://foo.com/bar/ ftp://foo.com/bar/b.html ftp://foo.com/bar/ + * 7) http://foo.com/a.htm#i http://foo.com/b.htm http://foo.com/ + * 8) ftp://foo.com/c.htm#i ftp://foo.com/c.htm ftp://foo.com/c.htm + * 9) file:///a/b/c.html file:///d/e/c.html file:/// + */ + AUTF8String getCommonBaseSpec(in nsIURI aURIToCompare); + + /** + * This method tries to create a string which specifies the location of the + * argument relative to |this|. If the argument and |this| are equal, the + * method returns "". If any of the URIs' scheme, host, userpass, or port + * don't match, the method returns the full spec of the argument. + * + * Examples: + * this.spec aURIToCompare.spec result + * 1) http://mozilla.org/ http://www.mozilla.org/ http://www.mozilla.org/ + * 2) http://mozilla.org/ http://www.mozilla.org http://www.mozilla.org/ + * 3) http://foo.com/bar/ http://foo.com:80/bar/ "" + * 4) http://foo.com/ http://foo.com/a.htm#b a.html#b + * 5) http://foo.com/a/b/ http://foo.com/c ../../c + */ + AUTF8String getRelativeSpec(in nsIURI aURIToCompare); + +}; + +[scriptable, builtinclass, uuid(25072eb8-f1e6-482f-9ca9-eddd3d65169a)] +interface nsIURLMutator : nsISupports +{ + [must_use] nsIURIMutator setFileName(in AUTF8String aFileName); + [must_use] nsIURIMutator setFileBaseName(in AUTF8String aFileBaseName); + [must_use] nsIURIMutator setFileExtension(in AUTF8String aFileExtension); +}; diff --git a/netwerk/base/nsIURLParser.idl b/netwerk/base/nsIURLParser.idl new file mode 100644 index 0000000000..3d6ac19b8c --- /dev/null +++ b/netwerk/base/nsIURLParser.idl @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsIURLParser specifies the interface to an URL parser that attempts to + * follow the definitions of RFC 2396. + */ +[scriptable, uuid(78c5d19f-f5d2-4732-8d3d-d5a7d7133bc0)] +interface nsIURLParser : nsISupports +{ + /** + * The string to parse in the following methods may be given as a null + * terminated string, in which case the length argument should be -1. + * + * Out parameters of the following methods are all optional (ie. the caller + * may pass-in a NULL value if the corresponding results are not needed). + * Signed out parameters may hold a value of -1 if the corresponding result + * is not part of the string being parsed. + * + * The parsing routines attempt to be as forgiving as possible. + */ + + /** + * ParseSpec breaks the URL string up into its 3 major components: a scheme, + * an authority section (hostname, etc.), and a path. + * + * spec = <scheme>://<authority><path> + */ + void parseURL (in string spec, in long specLen, + out unsigned long schemePos, out long schemeLen, + out unsigned long authorityPos, out long authorityLen, + out unsigned long pathPos, out long pathLen); + + /** + * ParseAuthority breaks the authority string up into its 4 components: + * username, password, hostname, and hostport. + * + * auth = <username>:<password>@<hostname>:<port> + */ + void parseAuthority (in string authority, in long authorityLen, + out unsigned long usernamePos, out long usernameLen, + out unsigned long passwordPos, out long passwordLen, + out unsigned long hostnamePos, out long hostnameLen, + out long port); + + /** + * userinfo = <username>:<password> + */ + void parseUserInfo (in string userinfo, in long userinfoLen, + out unsigned long usernamePos, out long usernameLen, + out unsigned long passwordPos, out long passwordLen); + + /** + * serverinfo = <hostname>:<port> + */ + void parseServerInfo (in string serverinfo, in long serverinfoLen, + out unsigned long hostnamePos, out long hostnameLen, + out long port); + + /** + * ParsePath breaks the path string up into its 3 major components: a file path, + * a query string, and a reference string. + * + * path = <filepath>?<query>#<ref> + */ + void parsePath (in string path, in long pathLen, + out unsigned long filepathPos, out long filepathLen, + out unsigned long queryPos, out long queryLen, + out unsigned long refPos, out long refLen); + + /** + * ParseFilePath breaks the file path string up into: the directory portion, + * file base name, and file extension. + * + * filepath = <directory><basename>.<extension> + */ + void parseFilePath (in string filepath, in long filepathLen, + out unsigned long directoryPos, out long directoryLen, + out unsigned long basenamePos, out long basenameLen, + out unsigned long extensionPos, out long extensionLen); + + /** + * filename = <basename>.<extension> + */ + void parseFileName (in string filename, in long filenameLen, + out unsigned long basenamePos, out long basenameLen, + out unsigned long extensionPos, out long extensionLen); +}; + +%{C++ +// url parser key for use with the category manager +// mapping from scheme to url parser. +#define NS_IURLPARSER_KEY "@mozilla.org/urlparser;1" +%} diff --git a/netwerk/base/nsIUploadChannel.idl b/netwerk/base/nsIUploadChannel.idl new file mode 100644 index 0000000000..1c059e8869 --- /dev/null +++ b/netwerk/base/nsIUploadChannel.idl @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; + +/** + * nsIUploadChannel + * + * A channel may optionally implement this interface if it supports the + * notion of uploading a data stream. The upload stream may only be set + * prior to the invocation of asyncOpen on the channel. + */ +[scriptable, uuid(5cfe15bd-5adb-4a7f-9e55-4f5a67d15794)] +interface nsIUploadChannel : nsISupports +{ + /** + * Sets a stream to be uploaded by this channel. + * + * Most implementations of this interface require that the stream: + * (1) implement threadsafe addRef and release + * (2) implement nsIInputStream::readSegments + * (3) implement nsISeekableStream::seek + * + * History here is that we need to support both streams that already have + * headers (e.g., Content-Type and Content-Length) information prepended to + * the stream (by plugins) as well as clients (composer, uploading + * application) that want to upload data streams without any knowledge of + * protocol specifications. For this reason, we have a special meaning + * for the aContentType parameter (see below). + * + * @param aStream + * The stream to be uploaded by this channel. + * @param aContentType + * If aContentType is empty, the protocol will assume that no + * content headers are to be added to the uploaded stream and that + * any required headers are already encoded in the stream. In the + * case of HTTP, if this parameter is non-empty, then its value will + * replace any existing Content-Type header on the HTTP request. + * In the case of FTP and FILE, this parameter is ignored. + * @param aContentLength + * A value of -1 indicates that the length of the stream should be + * determined by calling the stream's |available| method. + */ + void setUploadStream(in nsIInputStream aStream, + in ACString aContentType, + in long long aContentLength); + + /** + * Get the stream (to be) uploaded by this channel. + */ + readonly attribute nsIInputStream uploadStream; +}; diff --git a/netwerk/base/nsIUploadChannel2.idl b/netwerk/base/nsIUploadChannel2.idl new file mode 100644 index 0000000000..45f1d76682 --- /dev/null +++ b/netwerk/base/nsIUploadChannel2.idl @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; +interface nsIRunnable; + +[scriptable, uuid(2f712b52-19c5-4e0c-9e8f-b5c7c3b67049)] +interface nsIUploadChannel2 : nsISupports +{ + /** + * Sets a stream to be uploaded by this channel with the specified + * Content-Type and Content-Length header values. + * + * Most implementations of this interface require that the stream: + * (1) implement threadsafe addRef and release + * (2) implement nsIInputStream::readSegments + * (3) implement nsISeekableStream::seek + * + * @param aStream + * The stream to be uploaded by this channel. + * @param aContentType + * This value will replace any existing Content-Type + * header on the HTTP request, regardless of whether + * or not its empty. + * @param aContentLength + * A value of -1 indicates that the length of the stream should be + * determined by calling the stream's |available| method. + * @param aMethod + * The HTTP request method to set on the stream. + * @param aStreamHasHeaders + * True if the stream already contains headers for the HTTP request. + */ + void explicitSetUploadStream(in nsIInputStream aStream, + in ACString aContentType, + in long long aContentLength, + in ACString aMethod, + in boolean aStreamHasHeaders); + + /** + * Value of aStreamHasHeaders from the last successful call to + * explicitSetUploadStream. TRUE indicates the attached upload stream + * contains request headers. + */ + readonly attribute boolean uploadStreamHasHeaders; + + /** + * 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. + */ + [noscript] + nsIInputStream cloneUploadStream(out long long aContentLength); +}; diff --git a/netwerk/base/nsIncrementalDownload.cpp b/netwerk/base/nsIncrementalDownload.cpp new file mode 100644 index 0000000000..d7cb658092 --- /dev/null +++ b/netwerk/base/nsIncrementalDownload.cpp @@ -0,0 +1,878 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/UniquePtr.h" + +#include "nsIIncrementalDownload.h" +#include "nsIRequestObserver.h" +#include "nsIProgressEventSink.h" +#include "nsIChannelEventSink.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIInterfaceRequestor.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsIStreamListener.h" +#include "nsIFile.h" +#include "nsIHttpChannel.h" +#include "nsITimer.h" +#include "nsIURI.h" +#include "nsIInputStream.h" +#include "nsNetUtil.h" +#include "nsWeakReference.h" +#include "prio.h" +#include "prprf.h" +#include <algorithm> +#include "nsIContentPolicy.h" +#include "nsContentUtils.h" +#include "mozilla/Logging.h" +#include "mozilla/UniquePtr.h" + +// Default values used to initialize a nsIncrementalDownload object. +#define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes +#define DEFAULT_INTERVAL 60 // seconds + +#define UPDATE_PROGRESS_INTERVAL PRTime(100 * PR_USEC_PER_MSEC) // 100ms + +// Number of times to retry a failed byte-range request. +#define MAX_RETRY_COUNT 20 + +using namespace mozilla; +using namespace mozilla::net; + +static LazyLogModule gIDLog("IncrementalDownload"); +#undef LOG +#define LOG(args) MOZ_LOG(gIDLog, mozilla::LogLevel::Debug, args) + +//----------------------------------------------------------------------------- + +static nsresult WriteToFile(nsIFile* lf, const char* data, uint32_t len, + int32_t flags) { + PRFileDesc* fd; + int32_t mode = 0600; + nsresult rv; + rv = lf->OpenNSPRFileDesc(flags, mode, &fd); + if (NS_FAILED(rv)) return rv; + + if (len) { + rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE; + } + + PR_Close(fd); + return rv; +} + +static nsresult AppendToFile(nsIFile* lf, const char* data, uint32_t len) { + int32_t flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND; + return WriteToFile(lf, data, len, flags); +} + +// maxSize may be -1 if unknown +static void MakeRangeSpec(const int64_t& size, const int64_t& maxSize, + int32_t chunkSize, bool fetchRemaining, + nsCString& rangeSpec) { + rangeSpec.AssignLiteral("bytes="); + rangeSpec.AppendInt(int64_t(size)); + rangeSpec.Append('-'); + + if (fetchRemaining) return; + + int64_t end = size + int64_t(chunkSize); + if (maxSize != int64_t(-1) && end > maxSize) end = maxSize; + end -= 1; + + rangeSpec.AppendInt(int64_t(end)); +} + +//----------------------------------------------------------------------------- + +class nsIncrementalDownload final : public nsIIncrementalDownload, + public nsIStreamListener, + public nsIObserver, + public nsIInterfaceRequestor, + public nsIChannelEventSink, + public nsSupportsWeakReference, + public nsIAsyncVerifyRedirectCallback { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSIINCREMENTALDOWNLOAD + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIOBSERVER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + + nsIncrementalDownload() = default; + + private: + ~nsIncrementalDownload() = default; + nsresult FlushChunk(); + void UpdateProgress(); + nsresult CallOnStartRequest(); + void CallOnStopRequest(); + nsresult StartTimer(int32_t interval); + nsresult ProcessTimeout(); + nsresult ReadCurrentSize(); + nsresult ClearRequestHeader(nsIHttpChannel* channel); + + nsCOMPtr<nsIRequestObserver> mObserver; + nsCOMPtr<nsIProgressEventSink> mProgressSink; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIURI> mFinalURI; + nsCOMPtr<nsIFile> mDest; + nsCOMPtr<nsIChannel> mChannel; + nsCOMPtr<nsITimer> mTimer; + mozilla::UniquePtr<char[]> mChunk; + int32_t mChunkLen{0}; + int32_t mChunkSize{DEFAULT_CHUNK_SIZE}; + int32_t mInterval{DEFAULT_INTERVAL}; + int64_t mTotalSize{-1}; + int64_t mCurrentSize{-1}; + uint32_t mLoadFlags{LOAD_NORMAL}; + int32_t mNonPartialCount{0}; + nsresult mStatus{NS_OK}; + bool mIsPending{false}; + bool mDidOnStartRequest{false}; + PRTime mLastProgressUpdate{0}; + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; + nsCOMPtr<nsIChannel> mNewRedirectChannel; + nsCString mPartialValidator; + bool mCacheBust{false}; + + // nsITimerCallback is implemented on a subclass so that the name attribute + // doesn't conflict with the name attribute of the nsIRequest interface. + class TimerCallback final : public nsITimerCallback, public nsINamed { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + explicit TimerCallback(nsIncrementalDownload* aIncrementalDownload); + + private: + ~TimerCallback() = default; + + RefPtr<nsIncrementalDownload> mIncrementalDownload; + }; +}; + +nsresult nsIncrementalDownload::FlushChunk() { + NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known"); + + if (mChunkLen == 0) return NS_OK; + + nsresult rv = AppendToFile(mDest, mChunk.get(), mChunkLen); + if (NS_FAILED(rv)) return rv; + + mCurrentSize += int64_t(mChunkLen); + mChunkLen = 0; + + return NS_OK; +} + +void nsIncrementalDownload::UpdateProgress() { + mLastProgressUpdate = PR_Now(); + + if (mProgressSink) { + mProgressSink->OnProgress(this, mCurrentSize + mChunkLen, mTotalSize); + } +} + +nsresult nsIncrementalDownload::CallOnStartRequest() { + if (!mObserver || mDidOnStartRequest) return NS_OK; + + mDidOnStartRequest = true; + return mObserver->OnStartRequest(this); +} + +void nsIncrementalDownload::CallOnStopRequest() { + if (!mObserver) return; + + // Ensure that OnStartRequest is always called once before OnStopRequest. + nsresult rv = CallOnStartRequest(); + if (NS_SUCCEEDED(mStatus)) mStatus = rv; + + mIsPending = false; + + mObserver->OnStopRequest(this, mStatus); + mObserver = nullptr; +} + +nsresult nsIncrementalDownload::StartTimer(int32_t interval) { + auto callback = MakeRefPtr<TimerCallback>(this); + return NS_NewTimerWithCallback(getter_AddRefs(mTimer), callback, + interval * 1000, nsITimer::TYPE_ONE_SHOT); +} + +nsresult nsIncrementalDownload::ProcessTimeout() { + NS_ASSERTION(!mChannel, "how can we have a channel?"); + + // Handle existing error conditions + if (NS_FAILED(mStatus)) { + CallOnStopRequest(); + return NS_OK; + } + + // Fetch next chunk + + nsCOMPtr<nsIChannel> channel; + nsresult rv = NS_NewChannel( + getter_AddRefs(channel), mFinalURI, nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // nsICookieJarSettings + nullptr, // PerformanceStorage + nullptr, // loadGroup + this, // aCallbacks + mLoadFlags); + + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv); + if (NS_FAILED(rv)) return rv; + + NS_ASSERTION(mCurrentSize != int64_t(-1), + "we should know the current file size by now"); + + rv = ClearRequestHeader(http); + if (NS_FAILED(rv)) return rv; + + // Don't bother making a range request if we are just going to fetch the + // entire document. + if (mInterval || mCurrentSize != int64_t(0)) { + nsAutoCString range; + MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range); + + rv = http->SetRequestHeader("Range"_ns, range, false); + if (NS_FAILED(rv)) return rv; + + if (!mPartialValidator.IsEmpty()) { + rv = http->SetRequestHeader("If-Range"_ns, mPartialValidator, false); + if (NS_FAILED(rv)) { + LOG( + ("nsIncrementalDownload::ProcessTimeout\n" + " failed to set request header: If-Range\n")); + } + } + + if (mCacheBust) { + rv = http->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns, false); + if (NS_FAILED(rv)) { + LOG( + ("nsIncrementalDownload::ProcessTimeout\n" + " failed to set request header: If-Range\n")); + } + rv = http->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false); + if (NS_FAILED(rv)) { + LOG( + ("nsIncrementalDownload::ProcessTimeout\n" + " failed to set request header: If-Range\n")); + } + } + } + + rv = channel->AsyncOpen(this); + if (NS_FAILED(rv)) return rv; + + // Wait to assign mChannel when we know we are going to succeed. This is + // important because we don't want to introduce a reference cycle between + // mChannel and this until we know for a fact that AsyncOpen has succeeded, + // thus ensuring that our stream listener methods will be invoked. + mChannel = channel; + return NS_OK; +} + +// Reads the current file size and validates it. +nsresult nsIncrementalDownload::ReadCurrentSize() { + int64_t size; + nsresult rv = mDest->GetFileSize((int64_t*)&size); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + mCurrentSize = 0; + return NS_OK; + } + if (NS_FAILED(rv)) return rv; + + mCurrentSize = size; + return NS_OK; +} + +// nsISupports + +NS_IMPL_ISUPPORTS(nsIncrementalDownload, nsIIncrementalDownload, nsIRequest, + nsIStreamListener, nsIRequestObserver, nsIObserver, + nsIInterfaceRequestor, nsIChannelEventSink, + nsISupportsWeakReference, nsIAsyncVerifyRedirectCallback) + +// nsIRequest + +NS_IMETHODIMP +nsIncrementalDownload::GetName(nsACString& name) { + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); + + return mURI->GetSpec(name); +} + +NS_IMETHODIMP +nsIncrementalDownload::IsPending(bool* isPending) { + *isPending = mIsPending; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetStatus(nsresult* status) { + *status = mStatus; + return NS_OK; +} + +NS_IMETHODIMP nsIncrementalDownload::SetCanceledReason( + const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsIncrementalDownload::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsIncrementalDownload::CancelWithReason( + nsresult aStatus, const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +nsIncrementalDownload::Cancel(nsresult status) { + NS_ENSURE_ARG(NS_FAILED(status)); + + // Ignore this cancelation if we're already canceled. + if (NS_FAILED(mStatus)) return NS_OK; + + mStatus = status; + + // Nothing more to do if callbacks aren't pending. + if (!mIsPending) return NS_OK; + + if (mChannel) { + mChannel->Cancel(mStatus); + NS_ASSERTION(!mTimer, "what is this timer object doing here?"); + } else { + // dispatch a timer callback event to drive invoking our listener's + // OnStopRequest. + if (mTimer) mTimer->Cancel(); + StartTimer(0); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsIncrementalDownload::Resume() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsIncrementalDownload::GetLoadFlags(nsLoadFlags* loadFlags) { + *loadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags) { + mLoadFlags = loadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsIncrementalDownload::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsIncrementalDownload::GetLoadGroup(nsILoadGroup** loadGroup) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsIncrementalDownload::SetLoadGroup(nsILoadGroup* loadGroup) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +// nsIIncrementalDownload + +NS_IMETHODIMP +nsIncrementalDownload::Init(nsIURI* uri, nsIFile* dest, int32_t chunkSize, + int32_t interval) { + // Keep it simple: only allow initialization once + NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED); + + mDest = dest; + NS_ENSURE_ARG(mDest); + + mURI = uri; + mFinalURI = uri; + + if (chunkSize > 0) mChunkSize = chunkSize; + if (interval >= 0) mInterval = interval; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetURI(nsIURI** result) { + nsCOMPtr<nsIURI> uri = mURI; + uri.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetFinalURI(nsIURI** result) { + nsCOMPtr<nsIURI> uri = mFinalURI; + uri.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetDestination(nsIFile** result) { + if (!mDest) { + *result = nullptr; + return NS_OK; + } + // Return a clone of mDest so that callers may modify the resulting nsIFile + // without corrupting our internal object. This also works around the fact + // that some nsIFile impls may cache the result of stat'ing the filesystem. + return mDest->Clone(result); +} + +NS_IMETHODIMP +nsIncrementalDownload::GetTotalSize(int64_t* result) { + *result = mTotalSize; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetCurrentSize(int64_t* result) { + *result = mCurrentSize; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::Start(nsIRequestObserver* observer, + nsISupports* context) { + NS_ENSURE_ARG(observer); + NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS); + + // Observe system shutdown so we can be sure to release any reference held + // between ourselves and the timer. We have the observer service hold a weak + // reference to us, so that we don't have to worry about calling + // RemoveObserver. XXX(darin): The timer code should do this for us. + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + + nsresult rv = ReadCurrentSize(); + if (NS_FAILED(rv)) return rv; + + rv = StartTimer(0); + if (NS_FAILED(rv)) return rv; + + mObserver = observer; + mProgressSink = do_QueryInterface(observer); // ok if null + + mIsPending = true; + return NS_OK; +} + +// nsIRequestObserver + +NS_IMETHODIMP +nsIncrementalDownload::OnStartRequest(nsIRequest* request) { + nsresult rv; + + nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv); + if (NS_FAILED(rv)) return rv; + + // Ensure that we are receiving a 206 response. + uint32_t code; + rv = http->GetResponseStatus(&code); + if (NS_FAILED(rv)) return rv; + if (code != 206) { + // We may already have the entire file downloaded, in which case + // our request for a range beyond the end of the file would have + // been met with an error response code. + if (code == 416 && mTotalSize == int64_t(-1)) { + mTotalSize = mCurrentSize; + // Return an error code here to suppress OnDataAvailable. + return NS_ERROR_DOWNLOAD_COMPLETE; + } + // The server may have decided to give us all of the data in one chunk. If + // we requested a partial range, then we don't want to download all of the + // data at once. So, we'll just try again, but if this keeps happening then + // we'll eventually give up. + if (code == 200) { + if (mInterval) { + mChannel = nullptr; + if (++mNonPartialCount > MAX_RETRY_COUNT) { + NS_WARNING("unable to fetch a byte range; giving up"); + return NS_ERROR_FAILURE; + } + // Increase delay with each failure. + StartTimer(mInterval * mNonPartialCount); + return NS_ERROR_DOWNLOAD_NOT_PARTIAL; + } + // Since we have been asked to download the rest of the file, we can deal + // with a 200 response. This may result in downloading the beginning of + // the file again, but that can't really be helped. + } else { + NS_WARNING("server response was unexpected"); + return NS_ERROR_UNEXPECTED; + } + } else { + // We got a partial response, so clear this counter in case the next chunk + // results in a 200 response. + mNonPartialCount = 0; + + // confirm that the content-range response header is consistent with + // expectations on each 206. If it is not then drop this response and + // retry with no-cache set. + if (!mCacheBust) { + nsAutoCString buf; + int64_t startByte = 0; + bool confirmedOK = false; + + rv = http->GetResponseHeader("Content-Range"_ns, buf); + if (NS_FAILED(rv)) { + return rv; // it isn't a useful 206 without a CONTENT-RANGE of some + } + // sort + + // Content-Range: bytes 0-299999/25604694 + int32_t p = buf.Find("bytes "); + + // first look for the starting point of the content-range + // to make sure it is what we expect + if (p != -1) { + char* endptr = nullptr; + const char* s = buf.get() + p + 6; + while (*s && *s == ' ') s++; + startByte = strtol(s, &endptr, 10); + + if (*s && endptr && (endptr != s) && (mCurrentSize == startByte)) { + // ok the starting point is confirmed. We still need to check the + // total size of the range for consistency if this isn't + // the first chunk + if (mTotalSize == int64_t(-1)) { + // first chunk + confirmedOK = true; + } else { + int32_t slash = buf.FindChar('/'); + int64_t rangeSize = 0; + if (slash != kNotFound && + (PR_sscanf(buf.get() + slash + 1, "%lld", + (int64_t*)&rangeSize) == 1) && + rangeSize == mTotalSize) { + confirmedOK = true; + } + } + } + } + + if (!confirmedOK) { + NS_WARNING("unexpected content-range"); + mCacheBust = true; + mChannel = nullptr; + if (++mNonPartialCount > MAX_RETRY_COUNT) { + NS_WARNING("unable to fetch a byte range; giving up"); + return NS_ERROR_FAILURE; + } + // Increase delay with each failure. + StartTimer(mInterval * mNonPartialCount); + return NS_ERROR_DOWNLOAD_NOT_PARTIAL; + } + } + } + + // Do special processing after the first response. + if (mTotalSize == int64_t(-1)) { + // Update knowledge of mFinalURI + rv = http->GetURI(getter_AddRefs(mFinalURI)); + if (NS_FAILED(rv)) return rv; + Unused << http->GetResponseHeader("Etag"_ns, mPartialValidator); + if (StringBeginsWith(mPartialValidator, "W/"_ns)) { + mPartialValidator.Truncate(); // don't use weak validators + } + if (mPartialValidator.IsEmpty()) { + rv = http->GetResponseHeader("Last-Modified"_ns, mPartialValidator); + if (NS_FAILED(rv)) { + LOG( + ("nsIncrementalDownload::OnStartRequest\n" + " empty validator\n")); + } + } + + if (code == 206) { + // OK, read the Content-Range header to determine the total size of this + // download file. + nsAutoCString buf; + rv = http->GetResponseHeader("Content-Range"_ns, buf); + if (NS_FAILED(rv)) return rv; + int32_t slash = buf.FindChar('/'); + if (slash == kNotFound) { + NS_WARNING("server returned invalid Content-Range header!"); + return NS_ERROR_UNEXPECTED; + } + if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t*)&mTotalSize) != + 1) { + return NS_ERROR_UNEXPECTED; + } + } else { + rv = http->GetContentLength(&mTotalSize); + if (NS_FAILED(rv)) return rv; + // We need to know the total size of the thing we're trying to download. + if (mTotalSize == int64_t(-1)) { + NS_WARNING("server returned no content-length header!"); + return NS_ERROR_UNEXPECTED; + } + // Need to truncate (or create, if it doesn't exist) the file since we + // are downloading the whole thing. + WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE); + mCurrentSize = 0; + } + + // Notify observer that we are starting... + rv = CallOnStartRequest(); + if (NS_FAILED(rv)) return rv; + } + + // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize. + int64_t diff = mTotalSize - mCurrentSize; + if (diff <= int64_t(0)) { + NS_WARNING("about to set a bogus chunk size; giving up"); + return NS_ERROR_UNEXPECTED; + } + + if (diff < int64_t(mChunkSize)) mChunkSize = uint32_t(diff); + + mChunk = mozilla::MakeUniqueFallible<char[]>(mChunkSize); + if (!mChunk) rv = NS_ERROR_OUT_OF_MEMORY; + + return rv; +} + +NS_IMETHODIMP +nsIncrementalDownload::OnStopRequest(nsIRequest* request, nsresult status) { + // Not a real error; just a trick to kill off the channel without our + // listener having to care. + if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL) return NS_OK; + + // Not a real error; just a trick used to suppress OnDataAvailable calls. + if (status == NS_ERROR_DOWNLOAD_COMPLETE) status = NS_OK; + + if (NS_SUCCEEDED(mStatus)) mStatus = status; + + if (mChunk) { + if (NS_SUCCEEDED(mStatus)) mStatus = FlushChunk(); + + mChunk = nullptr; // deletes memory + mChunkLen = 0; + UpdateProgress(); + } + + mChannel = nullptr; + + // Notify listener if we hit an error or finished + if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) { + CallOnStopRequest(); + return NS_OK; + } + + return StartTimer(mInterval); // Do next chunk +} + +// nsIStreamListener + +NS_IMETHODIMP +nsIncrementalDownload::OnDataAvailable(nsIRequest* request, + nsIInputStream* input, uint64_t offset, + uint32_t count) { + while (count) { + uint32_t space = mChunkSize - mChunkLen; + uint32_t n, len = std::min(space, count); + + nsresult rv = input->Read(&mChunk[mChunkLen], len, &n); + if (NS_FAILED(rv)) return rv; + if (n != len) return NS_ERROR_UNEXPECTED; + + count -= n; + mChunkLen += n; + + if (mChunkLen == mChunkSize) { + rv = FlushChunk(); + if (NS_FAILED(rv)) return rv; + } + } + + if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL) { + UpdateProgress(); + } + + return NS_OK; +} + +// nsIObserver + +NS_IMETHODIMP +nsIncrementalDownload::Observe(nsISupports* subject, const char* topic, + const char16_t* data) { + if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + Cancel(NS_ERROR_ABORT); + + // Since the app is shutting down, we need to go ahead and notify our + // observer here. Otherwise, we would notify them after XPCOM has been + // shutdown or not at all. + CallOnStopRequest(); + } + return NS_OK; +} + +// nsITimerCallback + +nsIncrementalDownload::TimerCallback::TimerCallback( + nsIncrementalDownload* aIncrementalDownload) + : mIncrementalDownload(aIncrementalDownload) {} + +NS_IMPL_ISUPPORTS(nsIncrementalDownload::TimerCallback, nsITimerCallback, + nsINamed) + +NS_IMETHODIMP +nsIncrementalDownload::TimerCallback::Notify(nsITimer* aTimer) { + mIncrementalDownload->mTimer = nullptr; + + nsresult rv = mIncrementalDownload->ProcessTimeout(); + if (NS_FAILED(rv)) mIncrementalDownload->Cancel(rv); + + return NS_OK; +} + +// nsINamed + +NS_IMETHODIMP +nsIncrementalDownload::TimerCallback::GetName(nsACString& aName) { + aName.AssignLiteral("nsIncrementalDownload"); + return NS_OK; +} + +// nsIInterfaceRequestor + +NS_IMETHODIMP +nsIncrementalDownload::GetInterface(const nsIID& iid, void** result) { + if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) { + NS_ADDREF_THIS(); + *result = static_cast<nsIChannelEventSink*>(this); + return NS_OK; + } + + nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver); + if (ir) return ir->GetInterface(iid, result); + + return NS_ERROR_NO_INTERFACE; +} + +nsresult nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel* channel) { + NS_ENSURE_ARG(channel); + + // We don't support encodings -- they make the Content-Length not equal + // to the actual size of the data. + return channel->SetRequestHeader("Accept-Encoding"_ns, ""_ns, false); +} + +// nsIChannelEventSink + +NS_IMETHODIMP +nsIncrementalDownload::AsyncOnChannelRedirect( + nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags, + nsIAsyncVerifyRedirectCallback* cb) { + // In response to a redirect, we need to propagate the Range header. See bug + // 311595. Any failure code returned from this function aborts the redirect. + + nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel); + NS_ENSURE_STATE(http); + + nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel); + NS_ENSURE_STATE(newHttpChannel); + + constexpr auto rangeHdr = "Range"_ns; + + nsresult rv = ClearRequestHeader(newHttpChannel); + if (NS_FAILED(rv)) return rv; + + // If we didn't have a Range header, then we must be doing a full download. + nsAutoCString rangeVal; + Unused << http->GetRequestHeader(rangeHdr, rangeVal); + if (!rangeVal.IsEmpty()) { + rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // A redirection changes the validator + mPartialValidator.Truncate(); + + if (mCacheBust) { + rv = newHttpChannel->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns, + false); + if (NS_FAILED(rv)) { + LOG( + ("nsIncrementalDownload::AsyncOnChannelRedirect\n" + " failed to set request header: Cache-Control\n")); + } + rv = newHttpChannel->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false); + if (NS_FAILED(rv)) { + LOG( + ("nsIncrementalDownload::AsyncOnChannelRedirect\n" + " failed to set request header: Pragma\n")); + } + } + + // Prepare to receive callback + mRedirectCallback = cb; + mNewRedirectChannel = newChannel; + + // Give the observer a chance to see this redirect notification. + nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver); + if (sink) { + rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); + if (NS_FAILED(rv)) { + mRedirectCallback = nullptr; + mNewRedirectChannel = nullptr; + } + return rv; + } + (void)OnRedirectVerifyCallback(NS_OK); + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result) { + NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); + NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); + + // Update mChannel, so we can Cancel the new channel. + if (NS_SUCCEEDED(result)) mChannel = mNewRedirectChannel; + + mRedirectCallback->OnRedirectVerifyCallback(result); + mRedirectCallback = nullptr; + mNewRedirectChannel = nullptr; + return NS_OK; +} + +extern nsresult net_NewIncrementalDownload(const nsIID& iid, void** result) { + RefPtr<nsIncrementalDownload> d = new nsIncrementalDownload(); + return d->QueryInterface(iid, result); +} diff --git a/netwerk/base/nsIncrementalStreamLoader.cpp b/netwerk/base/nsIncrementalStreamLoader.cpp new file mode 100644 index 0000000000..f4437b5269 --- /dev/null +++ b/netwerk/base/nsIncrementalStreamLoader.cpp @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIncrementalStreamLoader.h" +#include "nsIInputStream.h" +#include "nsIChannel.h" +#include "nsError.h" +#include "mozilla/ProfilerLabels.h" + +#include <limits> + +nsIncrementalStreamLoader::nsIncrementalStreamLoader() = default; + +NS_IMETHODIMP +nsIncrementalStreamLoader::Init(nsIIncrementalStreamLoaderObserver* observer) { + NS_ENSURE_ARG_POINTER(observer); + mObserver = observer; + return NS_OK; +} + +nsresult nsIncrementalStreamLoader::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsIncrementalStreamLoader> it = new nsIncrementalStreamLoader(); + return it->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsIncrementalStreamLoader, nsIIncrementalStreamLoader, + nsIRequestObserver, nsIStreamListener, + nsIThreadRetargetableStreamListener) + +NS_IMETHODIMP +nsIncrementalStreamLoader::GetNumBytesRead(uint32_t* aNumBytes) { + *aNumBytes = mBytesRead; + return NS_OK; +} + +/* readonly attribute nsIRequest request; */ +NS_IMETHODIMP +nsIncrementalStreamLoader::GetRequest(nsIRequest** aRequest) { + *aRequest = do_AddRef(mRequest).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalStreamLoader::OnStartRequest(nsIRequest* request) { + nsCOMPtr<nsIChannel> chan(do_QueryInterface(request)); + if (chan) { + int64_t contentLength = -1; + chan->GetContentLength(&contentLength); + if (contentLength >= 0) { + // On 64bit platforms size of uint64_t coincides with the size of size_t, + // so we want to compare with the minimum from size_t and int64_t. + if (static_cast<uint64_t>(contentLength) > + std::min(std::numeric_limits<size_t>::max(), + static_cast<size_t>(std::numeric_limits<int64_t>::max()))) { + // Too big to fit into size_t, so let's bail. + return NS_ERROR_OUT_OF_MEMORY; + } + + // preallocate buffer + if (!mData.initCapacity(contentLength)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalStreamLoader::OnStopRequest(nsIRequest* request, + nsresult aStatus) { + AUTO_PROFILER_LABEL("nsIncrementalStreamLoader::OnStopRequest", NETWORK); + + if (mObserver) { + // provide nsIIncrementalStreamLoader::request during call to + // OnStreamComplete + mRequest = request; + size_t length = mData.length(); + uint8_t* elems = mData.extractOrCopyRawBuffer(); + nsresult rv = + mObserver->OnStreamComplete(this, mContext, aStatus, length, elems); + if (rv != NS_SUCCESS_ADOPTED_DATA) { + // The observer didn't take ownership of the extracted data buffer, so + // put it back into mData. + mData.replaceRawBuffer(elems, length); + } + // done.. cleanup + ReleaseData(); + mRequest = nullptr; + mObserver = nullptr; + } + return NS_OK; +} + +nsresult nsIncrementalStreamLoader::WriteSegmentFun( + nsIInputStream* inStr, void* closure, const char* fromSegment, + uint32_t toOffset, uint32_t count, uint32_t* writeCount) { + nsIncrementalStreamLoader* self = (nsIncrementalStreamLoader*)closure; + + const uint8_t* data = reinterpret_cast<const uint8_t*>(fromSegment); + uint32_t consumedCount = 0; + nsresult rv; + if (self->mData.empty()) { + // Shortcut when observer wants to keep the listener's buffer empty. + rv = self->mObserver->OnIncrementalData(self, self->mContext, count, data, + &consumedCount); + + if (rv != NS_OK) { + return rv; + } + + if (consumedCount > count) { + return NS_ERROR_INVALID_ARG; + } + + if (consumedCount < count) { + if (!self->mData.append(fromSegment + consumedCount, + count - consumedCount)) { + self->mData.clearAndFree(); + return NS_ERROR_OUT_OF_MEMORY; + } + } + } else { + // We have some non-consumed data from previous OnIncrementalData call, + // appending new data and reporting combined data. + if (!self->mData.append(fromSegment, count)) { + self->mData.clearAndFree(); + return NS_ERROR_OUT_OF_MEMORY; + } + size_t length = self->mData.length(); + uint32_t reportCount = length > UINT32_MAX ? UINT32_MAX : (uint32_t)length; + uint8_t* elems = self->mData.extractOrCopyRawBuffer(); + + rv = self->mObserver->OnIncrementalData(self, self->mContext, reportCount, + elems, &consumedCount); + + // We still own elems, freeing its memory when exiting scope. + if (rv != NS_OK) { + free(elems); + return rv; + } + + if (consumedCount > reportCount) { + free(elems); + return NS_ERROR_INVALID_ARG; + } + + if (consumedCount == length) { + free(elems); // good case -- fully consumed data + } else { + // Adopting elems back (at least its portion). + self->mData.replaceRawBuffer(elems, length); + if (consumedCount > 0) { + self->mData.erase(self->mData.begin() + consumedCount); + } + } + } + + *writeCount = count; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalStreamLoader::OnDataAvailable(nsIRequest* request, + nsIInputStream* inStr, + uint64_t sourceOffset, + uint32_t count) { + if (mObserver) { + // provide nsIIncrementalStreamLoader::request during call to + // OnStreamComplete + mRequest = request; + } + uint32_t countRead; + nsresult rv = inStr->ReadSegments(WriteSegmentFun, this, count, &countRead); + mRequest = nullptr; + NS_ENSURE_SUCCESS(rv, rv); + mBytesRead += countRead; + return rv; +} + +void nsIncrementalStreamLoader::ReleaseData() { mData.clearAndFree(); } + +NS_IMETHODIMP +nsIncrementalStreamLoader::CheckListenerChain() { return NS_OK; } + +NS_IMETHODIMP +nsIncrementalStreamLoader::OnDataFinished(nsresult aStatus) { return NS_OK; } diff --git a/netwerk/base/nsIncrementalStreamLoader.h b/netwerk/base/nsIncrementalStreamLoader.h new file mode 100644 index 0000000000..97d4277c28 --- /dev/null +++ b/netwerk/base/nsIncrementalStreamLoader.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsIncrementalStreamLoader_h__ +#define nsIncrementalStreamLoader_h__ + +#include "nsIThreadRetargetableStreamListener.h" +#include "nsIIncrementalStreamLoader.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/Vector.h" + +class nsIRequest; + +class nsIncrementalStreamLoader final : public nsIIncrementalStreamLoader { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINCREMENTALSTREAMLOADER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + + nsIncrementalStreamLoader(); + + static nsresult Create(REFNSIID aIID, void** aResult); + + protected: + ~nsIncrementalStreamLoader() = default; + + static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*, uint32_t, + uint32_t, uint32_t*); + + // Utility method to free mData, if present, and update other state to + // reflect that no data has been allocated. + void ReleaseData(); + + nsCOMPtr<nsIIncrementalStreamLoaderObserver> mObserver; + nsCOMPtr<nsISupports> mContext; // the observer's context + nsCOMPtr<nsIRequest> mRequest; + + // Buffer to accumulate incoming data. We preallocate if contentSize is + // available. + mozilla::Vector<uint8_t, 0> mData; + + // Number of bytes read, which may not match the number of bytes in mData at + // all, as we incrementally remove from there. + mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed> mBytesRead; +}; + +#endif // nsIncrementalStreamLoader_h__ diff --git a/netwerk/base/nsInputStreamChannel.cpp b/netwerk/base/nsInputStreamChannel.cpp new file mode 100644 index 0000000000..e00bc85ad5 --- /dev/null +++ b/netwerk/base/nsInputStreamChannel.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsInputStreamChannel.h" + +//----------------------------------------------------------------------------- +// nsInputStreamChannel + +namespace mozilla { +namespace net { + +nsresult nsInputStreamChannel::OpenContentStream(bool async, + nsIInputStream** result, + nsIChannel** channel) { + NS_ENSURE_TRUE(mContentStream, NS_ERROR_NOT_INITIALIZED); + + // If content length is unknown, then we must guess. In this case, we assume + // the stream can tell us. If the stream is a pipe, then this will not work. + + if (mContentLength < 0) { + uint64_t avail; + nsresult rv = mContentStream->Available(&avail); + if (rv == NS_BASE_STREAM_CLOSED) { + // This just means there's nothing in the stream + avail = 0; + } else if (NS_FAILED(rv)) { + return rv; + } + mContentLength = avail; + } + + EnableSynthesizedProgressEvents(true); + + *result = do_AddRef(mContentStream).take(); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsInputStreamChannel::nsISupports + +NS_IMPL_ISUPPORTS_INHERITED(nsInputStreamChannel, nsBaseChannel, + nsIInputStreamChannel) + +//----------------------------------------------------------------------------- +// nsInputStreamChannel::nsIInputStreamChannel + +NS_IMETHODIMP +nsInputStreamChannel::SetURI(nsIURI* uri) { + NS_ENSURE_TRUE(!URI(), NS_ERROR_ALREADY_INITIALIZED); + nsBaseChannel::SetURI(uri); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamChannel::GetContentStream(nsIInputStream** stream) { + *stream = do_AddRef(mContentStream).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamChannel::SetContentStream(nsIInputStream* stream) { + NS_ENSURE_TRUE(!mContentStream, NS_ERROR_ALREADY_INITIALIZED); + mContentStream = stream; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamChannel::GetSrcdocData(nsAString& aSrcdocData) { + aSrcdocData = mSrcdocData; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamChannel::SetSrcdocData(const nsAString& aSrcdocData) { + mSrcdocData = aSrcdocData; + mIsSrcdocChannel = true; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamChannel::GetIsSrcdocChannel(bool* aIsSrcdocChannel) { + *aIsSrcdocChannel = mIsSrcdocChannel; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamChannel::GetBaseURI(nsIURI** aBaseURI) { + *aBaseURI = do_AddRef(mBaseURI).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamChannel::SetBaseURI(nsIURI* aBaseURI) { + mBaseURI = aBaseURI; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsInputStreamChannel.h b/netwerk/base/nsInputStreamChannel.h new file mode 100644 index 0000000000..c8c926020b --- /dev/null +++ b/netwerk/base/nsInputStreamChannel.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsInputStreamChannel_h__ +#define nsInputStreamChannel_h__ + +#include "nsBaseChannel.h" +#include "nsIInputStreamChannel.h" + +//----------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +class nsInputStreamChannel : public nsBaseChannel, + public nsIInputStreamChannel { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINPUTSTREAMCHANNEL + + nsInputStreamChannel() = default; + + protected: + virtual ~nsInputStreamChannel() = default; + + virtual nsresult OpenContentStream(bool async, nsIInputStream** result, + nsIChannel** channel) override; + + virtual void OnChannelDone() override { mContentStream = nullptr; } + + private: + nsCOMPtr<nsIInputStream> mContentStream; + nsCOMPtr<nsIURI> mBaseURI; + nsString mSrcdocData; + bool mIsSrcdocChannel{false}; +}; + +} // namespace net +} // namespace mozilla + +#endif // !nsInputStreamChannel_h__ diff --git a/netwerk/base/nsInputStreamPump.cpp b/netwerk/base/nsInputStreamPump.cpp new file mode 100644 index 0000000000..dd74aed23a --- /dev/null +++ b/netwerk/base/nsInputStreamPump.cpp @@ -0,0 +1,795 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIOService.h" +#include "nsInputStreamPump.h" +#include "nsIStreamTransportService.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsThreadUtils.h" +#include "nsCOMPtr.h" +#include "mozilla/Logging.h" +#include "mozilla/NonBlockingAsyncInputStream.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/SlicedInputStream.h" +#include "mozilla/StaticPrefs_network.h" +#include "nsIStreamListener.h" +#include "nsILoadGroup.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" +#include <algorithm> + +// +// MOZ_LOG=nsStreamPump:5 +// +static mozilla::LazyLogModule gStreamPumpLog("nsStreamPump"); +#undef LOG +#define LOG(args) MOZ_LOG(gStreamPumpLog, mozilla::LogLevel::Debug, args) + +//----------------------------------------------------------------------------- +// nsInputStreamPump methods +//----------------------------------------------------------------------------- + +nsInputStreamPump::nsInputStreamPump() : mOffMainThread(!NS_IsMainThread()) {} + +nsresult nsInputStreamPump::Create(nsInputStreamPump** result, + nsIInputStream* stream, uint32_t segsize, + uint32_t segcount, bool closeWhenDone, + nsISerialEventTarget* mainThreadTarget) { + nsresult rv = NS_ERROR_OUT_OF_MEMORY; + RefPtr<nsInputStreamPump> pump = new nsInputStreamPump(); + if (pump) { + rv = pump->Init(stream, segsize, segcount, closeWhenDone, mainThreadTarget); + if (NS_SUCCEEDED(rv)) { + pump.forget(result); + } + } + return rv; +} + +struct PeekData { + PeekData(nsInputStreamPump::PeekSegmentFun fun, void* closure) + : mFunc(fun), mClosure(closure) {} + + nsInputStreamPump::PeekSegmentFun mFunc; + void* mClosure; +}; + +static nsresult CallPeekFunc(nsIInputStream* aInStream, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount) { + NS_ASSERTION(aToOffset == 0, "Called more than once?"); + NS_ASSERTION(aCount > 0, "Called without data?"); + + PeekData* data = static_cast<PeekData*>(aClosure); + data->mFunc(data->mClosure, reinterpret_cast<const uint8_t*>(aFromSegment), + aCount); + return NS_BINDING_ABORTED; +} + +nsresult nsInputStreamPump::PeekStream(PeekSegmentFun callback, void* closure) { + RecursiveMutexAutoLock lock(mMutex); + + MOZ_ASSERT(mAsyncStream, "PeekStream called without stream"); + + nsresult rv = CreateBufferedStreamIfNeeded(); + NS_ENSURE_SUCCESS(rv, rv); + + // See if the pipe is closed by checking the return of Available. + uint64_t dummy64; + rv = mAsyncStream->Available(&dummy64); + if (NS_FAILED(rv)) return rv; + uint32_t dummy = (uint32_t)std::min(dummy64, (uint64_t)UINT32_MAX); + + PeekData data(callback, closure); + return mAsyncStream->ReadSegments( + CallPeekFunc, &data, mozilla::net::nsIOService::gDefaultSegmentSize, + &dummy); +} + +nsresult nsInputStreamPump::EnsureWaiting() { + mMutex.AssertCurrentThreadIn(); + + // no need to worry about multiple threads... an input stream pump lives + // on only one thread at a time. + MOZ_ASSERT(mAsyncStream); + if (!mWaitingForInputStreamReady && !mProcessingCallbacks) { + // Ensure OnStateStop is called on the main thread only when this pump is + // created on main thread. + if (mState == STATE_STOP && !mOffMainThread) { + nsCOMPtr<nsISerialEventTarget> mainThread = + mLabeledMainThreadTarget + ? mLabeledMainThreadTarget + : do_AddRef(mozilla::GetMainThreadSerialEventTarget()); + if (mTargetThread != mainThread) { + mTargetThread = mainThread; + } + } + MOZ_ASSERT(mTargetThread); + nsresult rv = mAsyncStream->AsyncWait(this, 0, 0, mTargetThread); + if (NS_FAILED(rv)) { + NS_ERROR("AsyncWait failed"); + return rv; + } + // Any retargeting during STATE_START or START_TRANSFER is complete + // after the call to AsyncWait; next callback will be on mTargetThread. + mRetargeting = false; + mWaitingForInputStreamReady = true; + } + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsInputStreamPump::nsISupports +//----------------------------------------------------------------------------- + +// although this class can only be accessed from one thread at a time, we do +// allow its ownership to move from thread to thread, assuming the consumer +// understands the limitations of this. +NS_IMPL_ADDREF(nsInputStreamPump) +NS_IMPL_RELEASE(nsInputStreamPump) +NS_INTERFACE_MAP_BEGIN(nsInputStreamPump) + NS_INTERFACE_MAP_ENTRY(nsIRequest) + NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamPump) + NS_INTERFACE_MAP_ENTRY_CONCRETE(nsInputStreamPump) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamPump) +NS_INTERFACE_MAP_END + +//----------------------------------------------------------------------------- +// nsInputStreamPump::nsIRequest +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsInputStreamPump::GetName(nsACString& result) { + RecursiveMutexAutoLock lock(mMutex); + + result.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::IsPending(bool* result) { + RecursiveMutexAutoLock lock(mMutex); + + *result = (mState != STATE_IDLE && mState != STATE_DEAD); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::GetStatus(nsresult* status) { + RecursiveMutexAutoLock lock(mMutex); + + *status = mStatus; + return NS_OK; +} + +NS_IMETHODIMP nsInputStreamPump::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsInputStreamPump::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsInputStreamPump::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +nsInputStreamPump::Cancel(nsresult status) { + RecursiveMutexAutoLock lock(mMutex); + + AssertOnThread(); + + LOG(("nsInputStreamPump::Cancel [this=%p status=%" PRIx32 "]\n", this, + static_cast<uint32_t>(status))); + + if (NS_FAILED(mStatus)) { + LOG((" already canceled\n")); + return NS_OK; + } + + NS_ASSERTION(NS_FAILED(status), "cancel with non-failure status code"); + mStatus = status; + + // close input stream + if (mAsyncStream) { + // If mSuspendCount != 0, EnsureWaiting will be called by Resume(). + // Note that while suspended, OnInputStreamReady will + // not do anything, and also note that calling asyncWait + // on a closed stream works and will dispatch an event immediately. + + nsCOMPtr<nsIEventTarget> currentTarget = NS_GetCurrentThread(); + if (mTargetThread && currentTarget != mTargetThread) { + nsresult rv = mTargetThread->Dispatch(NS_NewRunnableFunction( + "nsInputStreamPump::Cancel", [self = RefPtr{this}, status] { + RecursiveMutexAutoLock lock(self->mMutex); + if (!self->mAsyncStream) { + return; + } + self->mAsyncStream->CloseWithStatus(status); + if (self->mSuspendCount == 0) { + self->EnsureWaiting(); + } + })); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mAsyncStream->CloseWithStatus(status); + if (mSuspendCount == 0) { + EnsureWaiting(); + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::Suspend() { + RecursiveMutexAutoLock lock(mMutex); + + LOG(("nsInputStreamPump::Suspend [this=%p]\n", this)); + NS_ENSURE_TRUE(mState != STATE_IDLE && mState != STATE_DEAD, + NS_ERROR_UNEXPECTED); + ++mSuspendCount; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::Resume() { + RecursiveMutexAutoLock lock(mMutex); + + LOG(("nsInputStreamPump::Resume [this=%p]\n", this)); + NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED); + NS_ENSURE_TRUE(mState != STATE_IDLE && mState != STATE_DEAD, + NS_ERROR_UNEXPECTED); + + // There is a brief in-between state when we null out mAsyncStream in + // OnStateStop() before calling OnStopRequest, and only afterwards set + // STATE_DEAD, which we need to handle gracefully. + if (--mSuspendCount == 0 && mAsyncStream) { + EnsureWaiting(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::GetLoadFlags(nsLoadFlags* aLoadFlags) { + RecursiveMutexAutoLock lock(mMutex); + + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::SetLoadFlags(nsLoadFlags aLoadFlags) { + RecursiveMutexAutoLock lock(mMutex); + + mLoadFlags = aLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsInputStreamPump::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsInputStreamPump::GetLoadGroup(nsILoadGroup** aLoadGroup) { + RecursiveMutexAutoLock lock(mMutex); + + *aLoadGroup = do_AddRef(mLoadGroup).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::SetLoadGroup(nsILoadGroup* aLoadGroup) { + RecursiveMutexAutoLock lock(mMutex); + + mLoadGroup = aLoadGroup; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsInputStreamPump::nsIInputStreamPump implementation +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsInputStreamPump::Init(nsIInputStream* stream, uint32_t segsize, + uint32_t segcount, bool closeWhenDone, + nsISerialEventTarget* mainThreadTarget) { + // probably we can't be multithread-accessed yet + RecursiveMutexAutoLock lock(mMutex); + NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS); + + mStream = stream; + mSegSize = segsize; + mSegCount = segcount; + mCloseWhenDone = closeWhenDone; + mLabeledMainThreadTarget = mainThreadTarget; + if (mOffMainThread && mLabeledMainThreadTarget) { + MOZ_ASSERT( + false, + "Init stream pump off main thread with a main thread event target."); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::AsyncRead(nsIStreamListener* listener) { + RecursiveMutexAutoLock lock(mMutex); + + // This ensures only one thread can interact with a pump at a time + NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS); + NS_ENSURE_ARG_POINTER(listener); + MOZ_ASSERT(NS_IsMainThread() || mOffMainThread, + "nsInputStreamPump should be read from the " + "main thread only."); + + nsresult rv = NS_MakeAsyncNonBlockingInputStream( + mStream.forget(), getter_AddRefs(mAsyncStream), mCloseWhenDone, mSegSize, + mSegCount); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(mAsyncStream); + + // mStreamOffset now holds the number of bytes currently read. + mStreamOffset = 0; + + // grab event queue (we must do this here by contract, since all notifications + // must go to the thread which called AsyncRead) + if (NS_IsMainThread() && mLabeledMainThreadTarget) { + mTargetThread = mLabeledMainThreadTarget; + } else { + mTargetThread = mozilla::GetCurrentSerialEventTarget(); + } + NS_ENSURE_STATE(mTargetThread); + + rv = EnsureWaiting(); + if (NS_FAILED(rv)) return rv; + + if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr); + + mState = STATE_START; + mListener = listener; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsInputStreamPump::nsIInputStreamCallback implementation +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream* stream) { + LOG(("nsInputStreamPump::OnInputStreamReady [this=%p]\n", this)); + + AUTO_PROFILER_LABEL("nsInputStreamPump::OnInputStreamReady", NETWORK); + + // this function has been called from a PLEvent, so we can safely call + // any listener or progress sink methods directly from here. + + for (;;) { + // There should only be one iteration of this loop happening at a time. + // To prevent AsyncWait() (called during callbacks or on other threads) + // from creating a parallel OnInputStreamReady(), we use: + // -- a mutex; and + // -- a boolean mProcessingCallbacks to detect parallel loops + // when exiting the mutex for callbacks. + RecursiveMutexAutoLock lock(mMutex); + + // Prevent parallel execution during callbacks, while out of mutex. + if (mProcessingCallbacks) { + MOZ_ASSERT(!mProcessingCallbacks); + break; + } + mProcessingCallbacks = true; + if (mSuspendCount || mState == STATE_IDLE || mState == STATE_DEAD) { + mWaitingForInputStreamReady = false; + mProcessingCallbacks = false; + break; + } + + uint32_t nextState; + switch (mState) { + case STATE_START: + nextState = OnStateStart(); + break; + case STATE_TRANSFER: + nextState = OnStateTransfer(); + break; + case STATE_STOP: + mRetargeting = false; + nextState = OnStateStop(); + break; + default: + nextState = 0; + MOZ_ASSERT_UNREACHABLE("Unknown enum value."); + return NS_ERROR_UNEXPECTED; + } + + bool stillTransferring = + (mState == STATE_TRANSFER && nextState == STATE_TRANSFER); + if (stillTransferring) { + NS_ASSERTION(NS_SUCCEEDED(mStatus), + "Should not have failed status for ongoing transfer"); + } else { + NS_ASSERTION(mState != nextState, + "Only OnStateTransfer can be called more than once."); + } + if (mRetargeting) { + NS_ASSERTION(mState != STATE_STOP, + "Retargeting should not happen during OnStateStop."); + } + + // Set mRetargeting so EnsureWaiting will be called. It ensures that + // OnStateStop is called on the main thread. + if (nextState == STATE_STOP && !NS_IsMainThread() && !mOffMainThread) { + mRetargeting = true; + } + + // Unset mProcessingCallbacks here (while we have lock) so our own call to + // EnsureWaiting isn't blocked by it. + mProcessingCallbacks = false; + + // We must break the loop if suspended during one of the previous + // operation. + if (mSuspendCount) { + mState = nextState; + mWaitingForInputStreamReady = false; + break; + } + + // Wait asynchronously if there is still data to transfer, or we're + // switching event delivery to another thread. + if (stillTransferring || mRetargeting) { + mState = nextState; + mWaitingForInputStreamReady = false; + nsresult rv = EnsureWaiting(); + if (NS_SUCCEEDED(rv)) break; + + // Failure to start asynchronous wait: stop transfer. + // Do not set mStatus if it was previously set to report a failure. + if (NS_SUCCEEDED(mStatus)) { + mStatus = rv; + } + nextState = STATE_STOP; + } + + mState = nextState; + } + return NS_OK; +} + +uint32_t nsInputStreamPump::OnStateStart() { + mMutex.AssertCurrentThreadIn(); + + AUTO_PROFILER_LABEL("nsInputStreamPump::OnStateStart", NETWORK); + + LOG((" OnStateStart [this=%p]\n", this)); + + nsresult rv; + + // need to check the reason why the stream is ready. this is required + // so our listener can check our status from OnStartRequest. + // XXX async streams should have a GetStatus method! + if (NS_SUCCEEDED(mStatus)) { + uint64_t avail; + rv = mAsyncStream->Available(&avail); + if (NS_FAILED(rv) && rv != NS_BASE_STREAM_CLOSED) mStatus = rv; + } + + { + nsCOMPtr<nsIStreamListener> listener = mListener; + // We're on the writing thread + AssertOnThread(); + + // Note: Must exit mutex for call to OnStartRequest to avoid + // deadlocks when calls to RetargetDeliveryTo for multiple + // nsInputStreamPumps are needed (e.g. nsHttpChannel). + RecursiveMutexAutoUnlock unlock(mMutex); + rv = listener->OnStartRequest(this); + } + + // an error returned from OnStartRequest should cause us to abort; however, + // we must not stomp on mStatus if already canceled. + if (NS_FAILED(rv) && NS_SUCCEEDED(mStatus)) mStatus = rv; + + return NS_SUCCEEDED(mStatus) ? STATE_TRANSFER : STATE_STOP; +} + +uint32_t nsInputStreamPump::OnStateTransfer() { + mMutex.AssertCurrentThreadIn(); + + AUTO_PROFILER_LABEL("nsInputStreamPump::OnStateTransfer", NETWORK); + + LOG((" OnStateTransfer [this=%p]\n", this)); + + // if canceled, go directly to STATE_STOP... + if (NS_FAILED(mStatus)) return STATE_STOP; + + nsresult rv = CreateBufferedStreamIfNeeded(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return STATE_STOP; + } + + uint64_t avail; + rv = mAsyncStream->Available(&avail); + LOG((" Available returned [stream=%p rv=%" PRIx32 " avail=%" PRIu64 "]\n", + mAsyncStream.get(), static_cast<uint32_t>(rv), avail)); + + if (rv == NS_BASE_STREAM_CLOSED) { + rv = NS_OK; + avail = 0; + } else if (NS_SUCCEEDED(rv) && avail) { + // we used to limit avail to 16K - we were afraid some ODA handlers + // might assume they wouldn't get more than 16K at once + // we're removing that limit since it speeds up local file access. + // Now there's an implicit 64K limit of 4 16K segments + // NOTE: ok, so the story is as follows. OnDataAvailable impls + // are by contract supposed to consume exactly |avail| bytes. + // however, many do not... mailnews... stream converters... + // cough, cough. the input stream pump is fairly tolerant + // in this regard; however, if an ODA does not consume any + // data from the stream, then we could potentially end up in + // an infinite loop. we do our best here to try to catch + // such an error. (see bug 189672) + + // in most cases this QI will succeed (mAsyncStream is almost always + // a nsPipeInputStream, which implements nsITellableStream::Tell). + int64_t offsetBefore; + nsCOMPtr<nsITellableStream> tellable = do_QueryInterface(mAsyncStream); + if (tellable && NS_FAILED(tellable->Tell(&offsetBefore))) { + MOZ_ASSERT_UNREACHABLE("Tell failed on readable stream"); + offsetBefore = 0; + } + + uint32_t odaAvail = avail > UINT32_MAX ? UINT32_MAX : uint32_t(avail); + + LOG((" calling OnDataAvailable [offset=%" PRIu64 " count=%" PRIu64 + "(%u)]\n", + mStreamOffset, avail, odaAvail)); + + { + // We may be called on non-MainThread even if mOffMainThread is + // false, due to RetargetDeliveryTo(), so don't use AssertOnThread() + if (mTargetThread) { + MOZ_ASSERT(mTargetThread->IsOnCurrentThread()); + } else { + MOZ_ASSERT(NS_IsMainThread()); + } + + nsCOMPtr<nsIStreamListener> listener = mListener; + // Note: Must exit mutex for call to OnStartRequest to avoid + // deadlocks when calls to RetargetDeliveryTo for multiple + // nsInputStreamPumps are needed (e.g. nsHttpChannel). + RecursiveMutexAutoUnlock unlock(mMutex); + // We're on the writing thread for mListener and mAsyncStream. + // mStreamOffset is only touched in OnStateTransfer, and AsyncRead + // shouldn't be called during OnDataAvailable() + + MOZ_PUSH_IGNORE_THREAD_SAFETY + rv = listener->OnDataAvailable(this, mAsyncStream, mStreamOffset, + odaAvail); + MOZ_POP_THREAD_SAFETY + } + + // don't enter this code if ODA failed or called Cancel + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(mStatus)) { + // test to see if this ODA failed to consume data + if (tellable) { + // NOTE: if Tell fails, which can happen if the stream is + // now closed, then we assume that everything was read. + int64_t offsetAfter; + if (NS_FAILED(tellable->Tell(&offsetAfter))) { + offsetAfter = offsetBefore + odaAvail; + } + if (offsetAfter > offsetBefore) { + mStreamOffset += (offsetAfter - offsetBefore); + } else if (mSuspendCount == 0) { + // + // possible infinite loop if we continue pumping data! + // + // NOTE: although not allowed by nsIStreamListener, we + // will allow the ODA impl to Suspend the pump. IMAP + // does this :-( + // + NS_ERROR("OnDataAvailable implementation consumed no data"); + mStatus = NS_ERROR_UNEXPECTED; + } + } else { + mStreamOffset += odaAvail; // assume ODA behaved well + } + } + } + + // an error returned from Available or OnDataAvailable should cause us to + // abort; however, we must not stop on mStatus if already canceled. + + if (NS_SUCCEEDED(mStatus)) { + if (NS_FAILED(rv)) { + mStatus = rv; + } else if (avail) { + // if stream is now closed, advance to STATE_STOP right away. + // Available may return 0 bytes available at the moment; that + // would not mean that we are done. + // XXX async streams should have a GetStatus method! + rv = mAsyncStream->Available(&avail); + if (NS_SUCCEEDED(rv)) return STATE_TRANSFER; + if (rv != NS_BASE_STREAM_CLOSED) mStatus = rv; + } + } + return STATE_STOP; +} + +nsresult nsInputStreamPump::CallOnStateStop() { + RecursiveMutexAutoLock lock(mMutex); + + MOZ_ASSERT(NS_IsMainThread(), + "CallOnStateStop should only be called on the main thread."); + + mState = OnStateStop(); + return NS_OK; +} + +uint32_t nsInputStreamPump::OnStateStop() { + mMutex.AssertCurrentThreadIn(); + + if (!NS_IsMainThread() && !mOffMainThread) { + // This method can be called on a different thread if nsInputStreamPump + // is used off the main-thread. + if (NS_SUCCEEDED(mStatus) && mListener && + mozilla::StaticPrefs::network_send_OnDataFinished_nsInputStreamPump()) { + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(mListener); + if (retargetableListener) { + retargetableListener->OnDataFinished(mStatus); + } + } + nsresult rv = mLabeledMainThreadTarget->Dispatch( + mozilla::NewRunnableMethod("nsInputStreamPump::CallOnStateStop", this, + &nsInputStreamPump::CallOnStateStop)); + NS_ENSURE_SUCCESS(rv, STATE_DEAD); + return STATE_DEAD; + } + + AUTO_PROFILER_LABEL("nsInputStreamPump::OnStateStop", NETWORK); + + LOG((" OnStateStop [this=%p status=%" PRIx32 "]\n", this, + static_cast<uint32_t>(mStatus))); + + // if an error occurred, we must be sure to pass the error onto the async + // stream. in some cases, this is redundant, but since close is idempotent, + // this is OK. otherwise, be sure to honor the "close-when-done" option. + + if (!mAsyncStream || !mListener) { + MOZ_ASSERT(mAsyncStream, "null mAsyncStream: OnStateStop called twice?"); + MOZ_ASSERT(mListener, "null mListener: OnStateStop called twice?"); + return STATE_DEAD; + } + + if (NS_FAILED(mStatus)) { + mAsyncStream->CloseWithStatus(mStatus); + } else if (mCloseWhenDone) { + mAsyncStream->Close(); + } + + mAsyncStream = nullptr; + mIsPending = false; + { + // We're on the writing thread. + // We believe that mStatus can't be changed on us here. + AssertOnThread(); + + nsCOMPtr<nsIStreamListener> listener = mListener; + nsresult status = mStatus; + // Note: Must exit mutex for call to OnStartRequest to avoid + // deadlocks when calls to RetargetDeliveryTo for multiple + // nsInputStreamPumps are needed (e.g. nsHttpChannel). + RecursiveMutexAutoUnlock unlock(mMutex); + + listener->OnStopRequest(this, status); + } + mTargetThread = nullptr; + mListener = nullptr; + + if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus); + + return STATE_DEAD; +} + +nsresult nsInputStreamPump::CreateBufferedStreamIfNeeded() { + if (mAsyncStreamIsBuffered) { + return NS_OK; + } + + // ReadSegments is not available for any nsIAsyncInputStream. In order to use + // it, we wrap a nsIBufferedInputStream around it, if needed. + + if (NS_InputStreamIsBuffered(mAsyncStream)) { + mAsyncStreamIsBuffered = true; + return NS_OK; + } + + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(stream), + mAsyncStream.forget(), 4096); + NS_ENSURE_SUCCESS(rv, rv); + + // A buffered inputStream must implement nsIAsyncInputStream. + mAsyncStream = do_QueryInterface(stream); + MOZ_DIAGNOSTIC_ASSERT(mAsyncStream); + mAsyncStreamIsBuffered = true; + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIThreadRetargetableRequest +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsInputStreamPump::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) { + RecursiveMutexAutoLock lock(mMutex); + + NS_ENSURE_ARG(aNewTarget); + NS_ENSURE_TRUE(mState == STATE_START || mState == STATE_TRANSFER, + NS_ERROR_UNEXPECTED); + + // If canceled, do not retarget. Return with canceled status. + if (NS_FAILED(mStatus)) { + return mStatus; + } + + if (aNewTarget == mTargetThread) { + NS_WARNING("Retargeting delivery to same thread"); + return NS_OK; + } + + if (mOffMainThread) { + // Don't support retargeting if this pump is already used off the main + // thread. + return NS_ERROR_FAILURE; + } + + // Ensure that |mListener| and any subsequent listeners can be retargeted + // to another thread. + nsresult rv = NS_OK; + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(mListener, &rv); + if (NS_SUCCEEDED(rv) && retargetableListener) { + rv = retargetableListener->CheckListenerChain(); + if (NS_SUCCEEDED(rv)) { + mTargetThread = aNewTarget; + mRetargeting = true; + } + } + LOG( + ("nsInputStreamPump::RetargetDeliveryTo [this=%p aNewTarget=%p] " + "%s listener [%p] rv[%" PRIx32 "]", + this, aNewTarget, (mTargetThread == aNewTarget ? "success" : "failure"), + (nsIStreamListener*)mListener, static_cast<uint32_t>(rv))); + return rv; +} + +NS_IMETHODIMP +nsInputStreamPump::GetDeliveryTarget(nsISerialEventTarget** aNewTarget) { + RecursiveMutexAutoLock lock(mMutex); + + nsCOMPtr<nsISerialEventTarget> target = mTargetThread; + target.forget(aNewTarget); + return NS_OK; +} diff --git a/netwerk/base/nsInputStreamPump.h b/netwerk/base/nsInputStreamPump.h new file mode 100644 index 0000000000..21e75c0276 --- /dev/null +++ b/netwerk/base/nsInputStreamPump.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsInputStreamPump_h__ +#define nsInputStreamPump_h__ + +#include "nsIInputStreamPump.h" +#include "nsIAsyncInputStream.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/RecursiveMutex.h" + +#ifdef DEBUG +# include "MainThreadUtils.h" +# include "nsISerialEventTarget.h" +#endif + +class nsIInputStream; +class nsILoadGroup; +class nsIStreamListener; + +#define NS_INPUT_STREAM_PUMP_IID \ + { \ + 0x42f1cc9b, 0xdf5f, 0x4c9b, { \ + 0xbd, 0x71, 0x8d, 0x4a, 0xe2, 0x27, 0xc1, 0x8a \ + } \ + } + +class nsInputStreamPump final : public nsIInputStreamPump, + public nsIInputStreamCallback, + public nsIThreadRetargetableRequest { + ~nsInputStreamPump() = default; + + public: + using RecursiveMutexAutoLock = mozilla::RecursiveMutexAutoLock; + using RecursiveMutexAutoUnlock = mozilla::RecursiveMutexAutoUnlock; + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSIINPUTSTREAMPUMP + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSITHREADRETARGETABLEREQUEST + NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_STREAM_PUMP_IID) + + nsInputStreamPump(); + + static nsresult Create(nsInputStreamPump** result, nsIInputStream* stream, + uint32_t segsize = 0, uint32_t segcount = 0, + bool closeWhenDone = false, + nsISerialEventTarget* mainThreadTarget = nullptr); + + using PeekSegmentFun = void (*)(void*, const uint8_t*, uint32_t); + /** + * Peek into the first chunk of data that's in the stream. Note that this + * method will not call the callback when there is no data in the stream. + * The callback will be called at most once. + * + * The data from the stream will not be consumed, i.e. the pump's listener + * can still read all the data + * + * Do not call before asyncRead. Do not call after onStopRequest. + */ + nsresult PeekStream(PeekSegmentFun callback, void* closure); + + /** + * Dispatched (to the main thread) by OnStateStop if it's called off main + * thread. Updates mState based on return value of OnStateStop. + */ + nsresult CallOnStateStop(); + + protected: + enum { STATE_IDLE, STATE_START, STATE_TRANSFER, STATE_STOP, STATE_DEAD }; + + nsresult EnsureWaiting(); + uint32_t OnStateStart(); + uint32_t OnStateTransfer(); + uint32_t OnStateStop(); + nsresult CreateBufferedStreamIfNeeded() MOZ_REQUIRES(mMutex); + + // This should optimize away in non-DEBUG builds + MOZ_ALWAYS_INLINE void AssertOnThread() const MOZ_REQUIRES(mMutex) { + if (mOffMainThread) { + MOZ_ASSERT(mTargetThread->IsOnCurrentThread()); + } else { + MOZ_ASSERT(NS_IsMainThread()); + } + } + + uint32_t mState MOZ_GUARDED_BY(mMutex){STATE_IDLE}; + nsCOMPtr<nsILoadGroup> mLoadGroup MOZ_GUARDED_BY(mMutex); + // mListener is written on a single thread (either MainThread or an + // off-MainThread thread), read from that thread and perhaps others (in + // RetargetDeliveryTo) + nsCOMPtr<nsIStreamListener> mListener MOZ_GUARDED_BY(mMutex); + nsCOMPtr<nsISerialEventTarget> mTargetThread MOZ_GUARDED_BY(mMutex); + nsCOMPtr<nsISerialEventTarget> mLabeledMainThreadTarget + MOZ_GUARDED_BY(mMutex); + nsCOMPtr<nsIInputStream> mStream MOZ_GUARDED_BY(mMutex); + // mAsyncStream is written on a single thread (either MainThread or an + // off-MainThread thread), and lives from AsyncRead() to OnStateStop(). + nsCOMPtr<nsIAsyncInputStream> mAsyncStream MOZ_GUARDED_BY(mMutex); + uint64_t mStreamOffset MOZ_GUARDED_BY(mMutex){0}; + uint64_t mStreamLength MOZ_GUARDED_BY(mMutex){0}; + uint32_t mSegSize MOZ_GUARDED_BY(mMutex){0}; + uint32_t mSegCount MOZ_GUARDED_BY(mMutex){0}; + nsresult mStatus MOZ_GUARDED_BY(mMutex){NS_OK}; + uint32_t mSuspendCount MOZ_GUARDED_BY(mMutex){0}; + uint32_t mLoadFlags MOZ_GUARDED_BY(mMutex){LOAD_NORMAL}; + bool mIsPending MOZ_GUARDED_BY(mMutex){false}; + // True while in OnInputStreamReady, calling OnStateStart, OnStateTransfer + // and OnStateStop. Used to prevent calls to AsyncWait during callbacks. + bool mProcessingCallbacks MOZ_GUARDED_BY(mMutex){false}; + // True if waiting on the "input stream ready" callback. + bool mWaitingForInputStreamReady MOZ_GUARDED_BY(mMutex){false}; + bool mCloseWhenDone MOZ_GUARDED_BY(mMutex){false}; + bool mRetargeting MOZ_GUARDED_BY(mMutex){false}; + bool mAsyncStreamIsBuffered MOZ_GUARDED_BY(mMutex){false}; + // Indicate whether nsInputStreamPump is used completely off main thread. + // If true, OnStateStop() is executed off main thread. Set at creation. + const bool mOffMainThread; + // Protects state/member var accesses across multiple threads. + mozilla::RecursiveMutex mMutex{"nsInputStreamPump"}; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsInputStreamPump, NS_INPUT_STREAM_PUMP_IID) + +#endif // !nsInputStreamChannel_h__ diff --git a/netwerk/base/nsLoadGroup.cpp b/netwerk/base/nsLoadGroup.cpp new file mode 100644 index 0000000000..3e2445b6ae --- /dev/null +++ b/netwerk/base/nsLoadGroup.cpp @@ -0,0 +1,1158 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=4 sts=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" + +#include "nsLoadGroup.h" + +#include "nsArrayEnumerator.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "mozilla/Logging.h" +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/Telemetry.h" +#include "nsIHttpChannelInternal.h" +#include "nsITimedChannel.h" +#include "nsIInterfaceRequestor.h" +#include "nsIRequestObserver.h" +#include "CacheObserver.h" +#include "MainThreadUtils.h" +#include "RequestContextService.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/StoragePrincipalHelper.h" +#include "mozilla/Unused.h" +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/StaticPrefs_network.h" + +namespace mozilla { +namespace net { + +// +// Log module for nsILoadGroup logging... +// +// To enable logging (see prlog.h for full details): +// +// set MOZ_LOG=LoadGroup:5 +// set MOZ_LOG_FILE=network.log +// +// This enables LogLevel::Debug level information and places all output in +// the file network.log. +// +static LazyLogModule gLoadGroupLog("LoadGroup"); +#undef LOG +#define LOG(args) MOZ_LOG(gLoadGroupLog, mozilla::LogLevel::Debug, args) + +//////////////////////////////////////////////////////////////////////////////// + +class RequestMapEntry : public PLDHashEntryHdr { + public: + explicit RequestMapEntry(nsIRequest* aRequest) : mKey(aRequest) {} + + nsCOMPtr<nsIRequest> mKey; +}; + +static bool RequestHashMatchEntry(const PLDHashEntryHdr* entry, + const void* key) { + const RequestMapEntry* e = static_cast<const RequestMapEntry*>(entry); + const nsIRequest* request = static_cast<const nsIRequest*>(key); + + return e->mKey == request; +} + +static void RequestHashClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) { + RequestMapEntry* e = static_cast<RequestMapEntry*>(entry); + + // An entry is being cleared, let the entry do its own cleanup. + e->~RequestMapEntry(); +} + +static void RequestHashInitEntry(PLDHashEntryHdr* entry, const void* key) { + const nsIRequest* const_request = static_cast<const nsIRequest*>(key); + nsIRequest* request = const_cast<nsIRequest*>(const_request); + + // Initialize the entry with placement new + new (entry) RequestMapEntry(request); +} + +static const PLDHashTableOps sRequestHashOps = { + PLDHashTable::HashVoidPtrKeyStub, RequestHashMatchEntry, + PLDHashTable::MoveEntryStub, RequestHashClearEntry, RequestHashInitEntry}; + +static void RescheduleRequest(nsIRequest* aRequest, int32_t delta) { + nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(aRequest); + if (p) p->AdjustPriority(delta); +} + +nsLoadGroup::nsLoadGroup() + : mRequests(&sRequestHashOps, sizeof(RequestMapEntry)) { + LOG(("LOADGROUP [%p]: Created.\n", this)); +} + +nsLoadGroup::~nsLoadGroup() { + DebugOnly<nsresult> rv = + CancelWithReason(NS_BINDING_ABORTED, "nsLoadGroup::~nsLoadGroup"_ns); + NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed"); + + mDefaultLoadRequest = nullptr; + + if (mRequestContext && !mExternalRequestContext) { + mRequestContextService->RemoveRequestContext(mRequestContext->GetID()); + if (IsNeckoChild() && gNeckoChild) { + gNeckoChild->SendRemoveRequestContext(mRequestContext->GetID()); + } + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + Unused << os->RemoveObserver(this, "last-pb-context-exited"); + } + + LOG(("LOADGROUP [%p]: Destroyed.\n", this)); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsISupports methods: + +NS_IMPL_ISUPPORTS(nsLoadGroup, nsILoadGroup, nsILoadGroupChild, nsIRequest, + nsISupportsPriority, nsISupportsWeakReference, nsIObserver) + +//////////////////////////////////////////////////////////////////////////////// +// nsIRequest methods: + +NS_IMETHODIMP +nsLoadGroup::GetName(nsACString& result) { + // XXX is this the right "name" for a load group? + + if (!mDefaultLoadRequest) { + result.Truncate(); + return NS_OK; + } + + return mDefaultLoadRequest->GetName(result); +} + +NS_IMETHODIMP +nsLoadGroup::IsPending(bool* aResult) { + *aResult = mForegroundCount > 0; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetStatus(nsresult* status) { + if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest) { + return mDefaultLoadRequest->GetStatus(status); + } + + *status = mStatus; + return NS_OK; +} + +static bool AppendRequestsToArray(PLDHashTable* aTable, + nsTArray<nsIRequest*>* aArray) { + for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) { + auto* e = static_cast<RequestMapEntry*>(iter.Get()); + nsIRequest* request = e->mKey; + MOZ_DIAGNOSTIC_ASSERT(request, "Null key in mRequests PLDHashTable entry"); + + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + aArray->AppendElement(request); + NS_ADDREF(request); + } + + if (aArray->Length() != aTable->EntryCount()) { + for (uint32_t i = 0, len = aArray->Length(); i < len; ++i) { + NS_RELEASE((*aArray)[i]); + } + return false; + } + return true; +} + +NS_IMETHODIMP nsLoadGroup::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsLoadGroup::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsLoadGroup::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +nsLoadGroup::Cancel(nsresult status) { + MOZ_ASSERT(NS_IsMainThread()); + + NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code"); + nsresult rv; + uint32_t count = mRequests.EntryCount(); + + AutoTArray<nsIRequest*, 8> requests; + + if (!AppendRequestsToArray(&mRequests, &requests)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // set the load group status to our cancel status while we cancel + // all our requests...once the cancel is done, we'll reset it... + // + mStatus = status; + + // Set the flag indicating that the loadgroup is being canceled... This + // prevents any new channels from being added during the operation. + // + mIsCanceling = true; + + nsresult firstError = NS_OK; + while (count > 0) { + nsCOMPtr<nsIRequest> request = requests.ElementAt(--count); + + NS_ASSERTION(request, "NULL request found in list."); + + if (!mRequests.Search(request)) { + // |request| was removed already + // We need to null out the entry in the request array so we don't try + // to notify the observers for this request. + nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(count)); + requests.ElementAt(count) = nullptr; + + continue; + } + + if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { + nsAutoCString nameStr; + request->GetName(nameStr); + LOG(("LOADGROUP [%p]: Canceling request %p %s.\n", this, request.get(), + nameStr.get())); + } + + // Cancel the request... + rv = request->CancelWithReason(status, mCanceledReason); + + // Remember the first failure and return it... + if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv; + + if (NS_FAILED(RemoveRequestFromHashtable(request, status))) { + // It's possible that request->Cancel causes the request to be removed + // from the loadgroup causing RemoveRequestFromHashtable to fail. + // In that case we shouldn't call NotifyRemovalObservers or decrement + // mForegroundCount since that has already happened. + nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(count)); + requests.ElementAt(count) = nullptr; + + continue; + } + } + + for (count = requests.Length(); count > 0;) { + nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count)); + (void)NotifyRemovalObservers(request, status); + } + + if (mRequestContext) { + Unused << mRequestContext->CancelTailPendingRequests(status); + } + +#if defined(DEBUG) + NS_ASSERTION(mRequests.EntryCount() == 0, "Request list is not empty."); + NS_ASSERTION(mForegroundCount == 0, "Foreground URLs are active."); +#endif + + mStatus = NS_OK; + mIsCanceling = false; + + return firstError; +} + +NS_IMETHODIMP +nsLoadGroup::Suspend() { + nsresult rv, firstError; + uint32_t count = mRequests.EntryCount(); + + AutoTArray<nsIRequest*, 8> requests; + + if (!AppendRequestsToArray(&mRequests, &requests)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + firstError = NS_OK; + // + // Operate the elements from back to front so that if items get + // get removed from the list it won't affect our iteration + // + while (count > 0) { + nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count)); + + NS_ASSERTION(request, "NULL request found in list."); + if (!request) continue; + + if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { + nsAutoCString nameStr; + request->GetName(nameStr); + LOG(("LOADGROUP [%p]: Suspending request %p %s.\n", this, request.get(), + nameStr.get())); + } + + // Suspend the request... + rv = request->Suspend(); + + // Remember the first failure and return it... + if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv; + } + + return firstError; +} + +NS_IMETHODIMP +nsLoadGroup::Resume() { + nsresult rv, firstError; + uint32_t count = mRequests.EntryCount(); + + AutoTArray<nsIRequest*, 8> requests; + + if (!AppendRequestsToArray(&mRequests, &requests)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + firstError = NS_OK; + // + // Operate the elements from back to front so that if items get + // get removed from the list it won't affect our iteration + // + while (count > 0) { + nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count)); + + NS_ASSERTION(request, "NULL request found in list."); + if (!request) continue; + + if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { + nsAutoCString nameStr; + request->GetName(nameStr); + LOG(("LOADGROUP [%p]: Resuming request %p %s.\n", this, request.get(), + nameStr.get())); + } + + // Resume the request... + rv = request->Resume(); + + // Remember the first failure and return it... + if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv; + } + + return firstError; +} + +NS_IMETHODIMP +nsLoadGroup::GetLoadFlags(uint32_t* aLoadFlags) { + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetLoadFlags(uint32_t aLoadFlags) { + mLoadFlags = aLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsLoadGroup::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +nsLoadGroup::GetLoadGroup(nsILoadGroup** loadGroup) { + nsCOMPtr<nsILoadGroup> result = mLoadGroup; + result.forget(loadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetLoadGroup(nsILoadGroup* loadGroup) { + mLoadGroup = loadGroup; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsILoadGroup methods: + +NS_IMETHODIMP +nsLoadGroup::GetDefaultLoadRequest(nsIRequest** aRequest) { + nsCOMPtr<nsIRequest> result = mDefaultLoadRequest; + result.forget(aRequest); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetDefaultLoadRequest(nsIRequest* aRequest) { + LOG(("nsLoadGroup::SetDefaultLoadRequest this=%p default-request=%p", this, + aRequest)); + + mDefaultLoadRequest = aRequest; + // Inherit the group load flags from the default load request + if (mDefaultLoadRequest) { + mDefaultLoadRequest->GetLoadFlags(&mLoadFlags); + // + // Mask off any bits that are not part of the nsIRequest flags. + // in particular, nsIChannel::LOAD_DOCUMENT_URI... + // + mLoadFlags &= nsIRequest::LOAD_REQUESTMASK; + + nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(aRequest); + mDefaultLoadIsTimed = timedChannel != nullptr; + if (mDefaultLoadIsTimed) { + timedChannel->GetChannelCreation(&mDefaultRequestCreationTime); + timedChannel->SetTimingEnabled(true); + } + } + // Else, do not change the group's load flags (see bug 95981) + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::AddRequest(nsIRequest* request, nsISupports* ctxt) { + nsresult rv; + + if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { + nsAutoCString nameStr; + request->GetName(nameStr); + LOG(("LOADGROUP [%p]: Adding request %p %s (count=%d).\n", this, request, + nameStr.get(), mRequests.EntryCount())); + } + + NS_ASSERTION(!mRequests.Search(request), + "Entry added to loadgroup twice, don't do that"); + + // + // Do not add the channel, if the loadgroup is being canceled... + // + if (mIsCanceling) { + LOG( + ("LOADGROUP [%p]: AddChannel() ABORTED because LoadGroup is" + " being canceled!!\n", + this)); + + return NS_BINDING_ABORTED; + } + + nsLoadFlags flags; + // if the request is the default load request or if the default load + // request is null, then the load group should inherit its load flags from + // the request, but also we need to enforce defaultLoadFlags. + if (mDefaultLoadRequest == request || !mDefaultLoadRequest) { + rv = MergeDefaultLoadFlags(request, flags); + } else { + rv = MergeLoadFlags(request, flags); + } + if (NS_FAILED(rv)) return rv; + + // + // Add the request to the list of active requests... + // + + auto* entry = static_cast<RequestMapEntry*>(mRequests.Add(request, fallible)); + if (!entry) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (mPriority != 0) RescheduleRequest(request, mPriority); + + nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request); + if (timedChannel) timedChannel->SetTimingEnabled(true); + + bool foreground = !(flags & nsIRequest::LOAD_BACKGROUND); + if (foreground) { + // Update the count of foreground URIs.. + mForegroundCount += 1; + } + + if (foreground || mNotifyObserverAboutBackgroundRequests) { + // + // Fire the OnStartRequest notification out to the observer... + // + // If the notification fails then DO NOT add the request to + // the load group. + // + nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver); + if (observer) { + LOG( + ("LOADGROUP [%p]: Firing OnStartRequest for request %p." + "(foreground count=%d).\n", + this, request, mForegroundCount)); + + rv = observer->OnStartRequest(request); + if (NS_FAILED(rv)) { + LOG(("LOADGROUP [%p]: OnStartRequest for request %p FAILED.\n", this, + request)); + // + // The URI load has been canceled by the observer. Clean up + // the damage... + // + + mRequests.Remove(request); + + rv = NS_OK; + + if (foreground) { + mForegroundCount -= 1; + } + } + } + + // Ensure that we're part of our loadgroup while pending + if (foreground && mForegroundCount == 1 && mLoadGroup) { + mLoadGroup->AddRequest(this, nullptr); + } + } + + return rv; +} + +NS_IMETHODIMP +nsLoadGroup::RemoveRequest(nsIRequest* request, nsISupports* ctxt, + nsresult aStatus) { + // Make sure we have a owning reference to the request we're about + // to remove. + nsCOMPtr<nsIRequest> kungFuDeathGrip(request); + + nsresult rv = RemoveRequestFromHashtable(request, aStatus); + if (NS_FAILED(rv)) { + return rv; + } + + return NotifyRemovalObservers(request, aStatus); +} + +nsresult nsLoadGroup::RemoveRequestFromHashtable(nsIRequest* request, + nsresult aStatus) { + NS_ENSURE_ARG_POINTER(request); + nsresult rv; + + if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { + nsAutoCString nameStr; + request->GetName(nameStr); + LOG(("LOADGROUP [%p]: Removing request %p %s status %" PRIx32 + " (count=%d).\n", + this, request, nameStr.get(), static_cast<uint32_t>(aStatus), + mRequests.EntryCount() - 1)); + } + + // + // Remove the request from the group. If this fails, it means that + // the request was *not* in the group so do not update the foreground + // count or it will get messed up... + // + auto* entry = static_cast<RequestMapEntry*>(mRequests.Search(request)); + + if (!entry) { + LOG(("LOADGROUP [%p]: Unable to remove request %p. Not in group!\n", this, + request)); + + return NS_ERROR_FAILURE; + } + + mRequests.RemoveEntry(entry); + + // Collect telemetry stats only when default request is a timed channel. + // Don't include failed requests in the timing statistics. + if (mDefaultLoadIsTimed && NS_SUCCEEDED(aStatus)) { + nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request); + if (timedChannel) { + // Figure out if this request was served from the cache + ++mTimedRequests; + TimeStamp timeStamp; + rv = timedChannel->GetCacheReadStart(&timeStamp); + if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) { + ++mCachedRequests; + } + + rv = timedChannel->GetAsyncOpen(&timeStamp); + if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) { + Telemetry::AccumulateTimeDelta( + Telemetry::HTTP_SUBITEM_OPEN_LATENCY_TIME, + mDefaultRequestCreationTime, timeStamp); + } + + rv = timedChannel->GetResponseStart(&timeStamp); + if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) { + Telemetry::AccumulateTimeDelta( + Telemetry::HTTP_SUBITEM_FIRST_BYTE_LATENCY_TIME, + mDefaultRequestCreationTime, timeStamp); + } + + TelemetryReportChannel(timedChannel, false); + } + } + + if (mRequests.EntryCount() == 0) { + TelemetryReport(); + } + + return NS_OK; +} + +nsresult nsLoadGroup::NotifyRemovalObservers(nsIRequest* request, + nsresult aStatus) { + NS_ENSURE_ARG_POINTER(request); + // Undo any group priority delta... + if (mPriority != 0) RescheduleRequest(request, -mPriority); + + nsLoadFlags flags; + nsresult rv = request->GetLoadFlags(&flags); + if (NS_FAILED(rv)) return rv; + + bool foreground = !(flags & nsIRequest::LOAD_BACKGROUND); + if (foreground) { + NS_ASSERTION(mForegroundCount > 0, "ForegroundCount messed up"); + mForegroundCount -= 1; + } + + if (foreground || mNotifyObserverAboutBackgroundRequests) { + // Fire the OnStopRequest out to the observer... + nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver); + if (observer) { + LOG( + ("LOADGROUP [%p]: Firing OnStopRequest for request %p." + "(foreground count=%d).\n", + this, request, mForegroundCount)); + + rv = observer->OnStopRequest(request, aStatus); + + if (NS_FAILED(rv)) { + LOG(("LOADGROUP [%p]: OnStopRequest for request %p FAILED.\n", this, + request)); + } + } + + // If that was the last request -> remove ourselves from loadgroup + if (foreground && mForegroundCount == 0 && mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, aStatus); + } + } + + return rv; +} + +NS_IMETHODIMP +nsLoadGroup::GetRequests(nsISimpleEnumerator** aRequests) { + nsCOMArray<nsIRequest> requests; + requests.SetCapacity(mRequests.EntryCount()); + + for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) { + auto* e = static_cast<RequestMapEntry*>(iter.Get()); + requests.AppendObject(e->mKey); + } + + return NS_NewArrayEnumerator(aRequests, requests, NS_GET_IID(nsIRequest)); +} + +NS_IMETHODIMP +nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver) { + SetGroupObserver(aObserver, false); + return NS_OK; +} + +void nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver, + bool aIncludeBackgroundRequests) { + mObserver = do_GetWeakReference(aObserver); + mNotifyObserverAboutBackgroundRequests = aIncludeBackgroundRequests; +} + +NS_IMETHODIMP +nsLoadGroup::GetGroupObserver(nsIRequestObserver** aResult) { + nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver); + observer.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetActiveCount(uint32_t* aResult) { + *aResult = mForegroundCount; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) { + NS_ENSURE_ARG_POINTER(aCallbacks); + nsCOMPtr<nsIInterfaceRequestor> callbacks = mCallbacks; + callbacks.forget(aCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) { + mCallbacks = aCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetRequestContextID(uint64_t* aRCID) { + if (!mRequestContext) { + return NS_ERROR_NOT_AVAILABLE; + } + *aRCID = mRequestContext->GetID(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsILoadGroupChild methods: + +NS_IMETHODIMP +nsLoadGroup::GetParentLoadGroup(nsILoadGroup** aParentLoadGroup) { + *aParentLoadGroup = nullptr; + nsCOMPtr<nsILoadGroup> parent = do_QueryReferent(mParentLoadGroup); + if (!parent) return NS_OK; + parent.forget(aParentLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetParentLoadGroup(nsILoadGroup* aParentLoadGroup) { + mParentLoadGroup = do_GetWeakReference(aParentLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetChildLoadGroup(nsILoadGroup** aChildLoadGroup) { + *aChildLoadGroup = do_AddRef(this).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetRootLoadGroup(nsILoadGroup** aRootLoadGroup) { + // first recursively try the root load group of our parent + nsCOMPtr<nsILoadGroupChild> ancestor = do_QueryReferent(mParentLoadGroup); + if (ancestor) return ancestor->GetRootLoadGroup(aRootLoadGroup); + + // next recursively try the root load group of our own load grop + ancestor = do_QueryInterface(mLoadGroup); + if (ancestor) return ancestor->GetRootLoadGroup(aRootLoadGroup); + + // finally just return this + *aRootLoadGroup = do_AddRef(this).take(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsISupportsPriority methods: + +NS_IMETHODIMP +nsLoadGroup::GetPriority(int32_t* aValue) { + *aValue = mPriority; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetPriority(int32_t aValue) { + return AdjustPriority(aValue - mPriority); +} + +NS_IMETHODIMP +nsLoadGroup::AdjustPriority(int32_t aDelta) { + // Update the priority for each request that supports nsISupportsPriority + if (aDelta != 0) { + mPriority += aDelta; + for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) { + auto* e = static_cast<RequestMapEntry*>(iter.Get()); + RescheduleRequest(e->mKey, aDelta); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetDefaultLoadFlags(uint32_t* aFlags) { + *aFlags = mDefaultLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetDefaultLoadFlags(uint32_t aFlags) { + mDefaultLoadFlags = aFlags; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +void nsLoadGroup::TelemetryReport() { + nsresult defaultStatus = NS_ERROR_INVALID_ARG; + // We should only report HTTP_PAGE_* telemetry if the defaultRequest was + // actually successful. + if (mDefaultLoadRequest) { + mDefaultLoadRequest->GetStatus(&defaultStatus); + } + if (mDefaultLoadIsTimed && NS_SUCCEEDED(defaultStatus)) { + Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE, mTimedRequests); + if (mTimedRequests) { + Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE_FROM_CACHE, + mCachedRequests * 100 / mTimedRequests); + } + + nsCOMPtr<nsITimedChannel> timedChannel = + do_QueryInterface(mDefaultLoadRequest); + if (timedChannel) TelemetryReportChannel(timedChannel, true); + } + + mTimedRequests = 0; + mCachedRequests = 0; + mDefaultLoadIsTimed = false; +} + +void nsLoadGroup::TelemetryReportChannel(nsITimedChannel* aTimedChannel, + bool aDefaultRequest) { + nsresult rv; + bool timingEnabled; + rv = aTimedChannel->GetTimingEnabled(&timingEnabled); + if (NS_FAILED(rv) || !timingEnabled) return; + + TimeStamp asyncOpen; + rv = aTimedChannel->GetAsyncOpen(&asyncOpen); + // We do not check !asyncOpen.IsNull() bellow, prevent ASSERTIONs this way + if (NS_FAILED(rv) || asyncOpen.IsNull()) return; + + TimeStamp cacheReadStart; + rv = aTimedChannel->GetCacheReadStart(&cacheReadStart); + if (NS_FAILED(rv)) return; + + TimeStamp cacheReadEnd; + rv = aTimedChannel->GetCacheReadEnd(&cacheReadEnd); + if (NS_FAILED(rv)) return; + + TimeStamp domainLookupStart; + rv = aTimedChannel->GetDomainLookupStart(&domainLookupStart); + if (NS_FAILED(rv)) return; + + TimeStamp domainLookupEnd; + rv = aTimedChannel->GetDomainLookupEnd(&domainLookupEnd); + if (NS_FAILED(rv)) return; + + TimeStamp connectStart; + rv = aTimedChannel->GetConnectStart(&connectStart); + if (NS_FAILED(rv)) return; + + TimeStamp secureConnectionStart; + rv = aTimedChannel->GetSecureConnectionStart(&secureConnectionStart); + if (NS_FAILED(rv)) return; + + TimeStamp connectEnd; + rv = aTimedChannel->GetConnectEnd(&connectEnd); + if (NS_FAILED(rv)) return; + + TimeStamp requestStart; + rv = aTimedChannel->GetRequestStart(&requestStart); + if (NS_FAILED(rv)) return; + + TimeStamp responseStart; + rv = aTimedChannel->GetResponseStart(&responseStart); + if (NS_FAILED(rv)) return; + + TimeStamp responseEnd; + rv = aTimedChannel->GetResponseEnd(&responseEnd); + if (NS_FAILED(rv)) return; + + bool useHttp3 = false; + bool supportHttp3 = false; + nsCOMPtr<nsIHttpChannelInternal> httpChannel = + do_QueryInterface(aTimedChannel); + if (httpChannel) { + uint32_t major; + uint32_t minor; + if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) { + useHttp3 = major == 3; + if (major == 2) { + if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) { + supportHttp3 = false; + } + } + } + } + +#define HTTP_REQUEST_HISTOGRAMS(prefix) \ + if (!domainLookupStart.IsNull()) { \ + Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_DNS_ISSUE_TIME, \ + asyncOpen, domainLookupStart); \ + } \ + \ + if (!domainLookupStart.IsNull() && !domainLookupEnd.IsNull()) { \ + Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_DNS_LOOKUP_TIME, \ + domainLookupStart, domainLookupEnd); \ + } \ + \ + if (!secureConnectionStart.IsNull() && !connectEnd.IsNull()) { \ + Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_TLS_HANDSHAKE, \ + secureConnectionStart, connectEnd); \ + } \ + \ + if (!connectStart.IsNull() && !connectEnd.IsNull()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_TCP_CONNECTION_2, connectStart, \ + connectEnd); \ + } \ + \ + if (!requestStart.IsNull() && !responseEnd.IsNull()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_SENT, asyncOpen, \ + requestStart); \ + \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_FIRST_SENT_TO_LAST_RECEIVED, requestStart, \ + responseEnd); \ + \ + if (cacheReadStart.IsNull() && !responseStart.IsNull()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_RECEIVED, asyncOpen, \ + responseStart); \ + } \ + } \ + \ + if (!cacheReadStart.IsNull() && !cacheReadEnd.IsNull()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE_V2, asyncOpen, \ + cacheReadStart); \ + \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_CACHE_READ_TIME_V2, cacheReadStart, \ + cacheReadEnd); \ + \ + if (!requestStart.IsNull() && !responseEnd.IsNull()) { \ + Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_REVALIDATION, \ + requestStart, responseEnd); \ + } \ + } \ + \ + if (!cacheReadEnd.IsNull()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, asyncOpen, cacheReadEnd); \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED_V2, asyncOpen, \ + cacheReadEnd); \ + } else if (!responseEnd.IsNull()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, asyncOpen, responseEnd); \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET_V2, asyncOpen, \ + responseEnd); \ + } + + // Glean instrumentation of metrics previously collected via Geckoview + // Streaming. + if (aDefaultRequest) { + if (!cacheReadStart.IsNull() && !cacheReadEnd.IsNull()) { + mozilla::glean::network::first_from_cache.AccumulateRawDuration( + cacheReadStart - asyncOpen); + } + if (!connectEnd.IsNull()) { + if (!connectStart.IsNull()) { + mozilla::glean::network::tcp_connection.AccumulateRawDuration( + connectEnd - connectStart); + } + if (!secureConnectionStart.IsNull()) { + mozilla::glean::network::tls_handshake.AccumulateRawDuration( + connectEnd - secureConnectionStart); + } + } + if (!domainLookupStart.IsNull()) { + mozilla::glean::network::dns_start.AccumulateRawDuration( + domainLookupStart - asyncOpen); + if (!domainLookupEnd.IsNull()) { + mozilla::glean::network::dns_end.AccumulateRawDuration( + domainLookupEnd - domainLookupStart); + } + } + } + + if (aDefaultRequest) { + HTTP_REQUEST_HISTOGRAMS(PAGE) + } else { + HTTP_REQUEST_HISTOGRAMS(SUB) + } + + if ((useHttp3 || supportHttp3) && cacheReadStart.IsNull() && + cacheReadEnd.IsNull()) { + nsCString key = (useHttp3) ? ((aDefaultRequest) ? "uses_http3_page"_ns + : "uses_http3_sub"_ns) + : ((aDefaultRequest) ? "supports_http3_page"_ns + : "supports_http3_sub"_ns); + + if (!secureConnectionStart.IsNull() && !connectEnd.IsNull()) { + Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_TLS_HANDSHAKE, key, + secureConnectionStart, connectEnd); + } + + if (supportHttp3 && !connectStart.IsNull() && !connectEnd.IsNull()) { + Telemetry::AccumulateTimeDelta(Telemetry::SUP_HTTP3_TCP_CONNECTION, key, + connectStart, connectEnd); + } + + if (!requestStart.IsNull() && !responseEnd.IsNull()) { + Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_OPEN_TO_FIRST_SENT, key, + asyncOpen, requestStart); + + Telemetry::AccumulateTimeDelta( + Telemetry::HTTP3_FIRST_SENT_TO_LAST_RECEIVED, key, requestStart, + responseEnd); + + if (!responseStart.IsNull()) { + Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_OPEN_TO_FIRST_RECEIVED, + key, asyncOpen, responseStart); + } + + if (!responseEnd.IsNull()) { + Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_COMPLETE_LOAD, key, + asyncOpen, responseEnd); + } + } + } + + bool hasHTTPSRR = false; + if (httpChannel && NS_SUCCEEDED(httpChannel->GetHasHTTPSRR(&hasHTTPSRR)) && + cacheReadStart.IsNull() && cacheReadEnd.IsNull() && + !requestStart.IsNull()) { + nsCString key = (hasHTTPSRR) ? ((aDefaultRequest) ? "uses_https_rr_page"_ns + : "uses_https_rr_sub"_ns) + : ((aDefaultRequest) ? "no_https_rr_page"_ns + : "no_https_rr_sub"_ns); + Telemetry::AccumulateTimeDelta(Telemetry::HTTPS_RR_OPEN_TO_FIRST_SENT, key, + asyncOpen, requestStart); + TimeDuration elapsed = requestStart - asyncOpen; + if (hasHTTPSRR) { + if (aDefaultRequest) { + glean::networking::http_channel_page_open_to_first_sent_https_rr + .AccumulateRawDuration(elapsed); + } else { + glean::networking::http_channel_sub_open_to_first_sent_https_rr + .AccumulateRawDuration(elapsed); + } + } else { + if (aDefaultRequest) { + glean::networking::http_channel_page_open_to_first_sent + .AccumulateRawDuration(elapsed); + } else { + glean::networking::http_channel_sub_open_to_first_sent + .AccumulateRawDuration(elapsed); + } + } + } + +#undef HTTP_REQUEST_HISTOGRAMS +} + +nsresult nsLoadGroup::MergeLoadFlags(nsIRequest* aRequest, + nsLoadFlags& outFlags) { + nsresult rv; + nsLoadFlags flags, oldFlags; + + rv = aRequest->GetLoadFlags(&flags); + if (NS_FAILED(rv)) { + return rv; + } + + oldFlags = flags; + + // Inherit the following bits... + flags |= (mLoadFlags & + (LOAD_BACKGROUND | LOAD_BYPASS_CACHE | LOAD_FROM_CACHE | + VALIDATE_ALWAYS | VALIDATE_ONCE_PER_SESSION | VALIDATE_NEVER)); + + // ... and force the default flags. + flags |= mDefaultLoadFlags; + + if (flags != oldFlags) { + rv = aRequest->SetLoadFlags(flags); + } + + outFlags = flags; + return rv; +} + +nsresult nsLoadGroup::MergeDefaultLoadFlags(nsIRequest* aRequest, + nsLoadFlags& outFlags) { + nsresult rv; + nsLoadFlags flags, oldFlags; + + rv = aRequest->GetLoadFlags(&flags); + if (NS_FAILED(rv)) { + return rv; + } + + oldFlags = flags; + // ... and force the default flags. + flags |= mDefaultLoadFlags; + + if (flags != oldFlags) { + rv = aRequest->SetLoadFlags(flags); + } + outFlags = flags; + return rv; +} + +nsresult nsLoadGroup::Init() { + mRequestContextService = RequestContextService::GetOrCreate(); + if (mRequestContextService) { + Unused << mRequestContextService->NewRequestContext( + getter_AddRefs(mRequestContext)); + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + NS_ENSURE_STATE(os); + + Unused << os->AddObserver(this, "last-pb-context-exited", true); + + return NS_OK; +} + +nsresult nsLoadGroup::InitWithRequestContextId( + const uint64_t& aRequestContextId) { + mRequestContextService = RequestContextService::GetOrCreate(); + if (mRequestContextService) { + Unused << mRequestContextService->GetRequestContext( + aRequestContextId, getter_AddRefs(mRequestContext)); + } + mExternalRequestContext = true; + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + NS_ENSURE_STATE(os); + + Unused << os->AddObserver(this, "last-pb-context-exited", true); + + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(!strcmp(aTopic, "last-pb-context-exited")); + + OriginAttributes attrs; + StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs); + if (attrs.mPrivateBrowsingId == 0) { + return NS_OK; + } + + mBrowsingContextDiscarded = true; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetIsBrowsingContextDiscarded(bool* aIsBrowsingContextDiscarded) { + *aIsBrowsingContextDiscarded = mBrowsingContextDiscarded; + return NS_OK; +} + +} // namespace net +} // namespace mozilla + +#undef LOG diff --git a/netwerk/base/nsLoadGroup.h b/netwerk/base/nsLoadGroup.h new file mode 100644 index 0000000000..055e1804d6 --- /dev/null +++ b/netwerk/base/nsLoadGroup.h @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsLoadGroup_h__ +#define nsLoadGroup_h__ + +#include "nsILoadGroup.h" +#include "nsILoadGroupChild.h" +#include "nsIObserver.h" +#include "nsCOMPtr.h" +#include "nsWeakReference.h" +#include "nsISupportsPriority.h" +#include "PLDHashTable.h" +#include "mozilla/TimeStamp.h" + +class nsIRequestContext; +class nsIRequestContextService; +class nsITimedChannel; + +namespace mozilla { +namespace net { + +class nsLoadGroup : public nsILoadGroup, + public nsILoadGroupChild, + public nsIObserver, + public nsISupportsPriority, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////////// + // nsIRequest methods: + NS_DECL_NSIREQUEST + + //////////////////////////////////////////////////////////////////////////// + // nsILoadGroup methods: + NS_DECL_NSILOADGROUP + + //////////////////////////////////////////////////////////////////////////// + // nsILoadGroupChild methods: + NS_DECL_NSILOADGROUPCHILD + + //////////////////////////////////////////////////////////////////////////// + // nsISupportsPriority methods: + NS_DECL_NSISUPPORTSPRIORITY + + //////////////////////////////////////////////////////////////////////////// + // nsIObserver methods: + NS_DECL_NSIOBSERVER + + //////////////////////////////////////////////////////////////////////////// + // nsLoadGroup methods: + + nsLoadGroup(); + + nsresult Init(); + nsresult InitWithRequestContextId(const uint64_t& aRequestContextId); + + void SetGroupObserver(nsIRequestObserver* aObserver, + bool aIncludeBackgroundRequests); + + protected: + virtual ~nsLoadGroup(); + + nsresult MergeLoadFlags(nsIRequest* aRequest, nsLoadFlags& flags); + nsresult MergeDefaultLoadFlags(nsIRequest* aRequest, nsLoadFlags& flags); + + private: + void TelemetryReport(); + void TelemetryReportChannel(nsITimedChannel* timedChannel, + bool defaultRequest); + + nsresult RemoveRequestFromHashtable(nsIRequest* aRequest, nsresult aStatus); + nsresult NotifyRemovalObservers(nsIRequest* aRequest, nsresult aStatus); + + protected: + uint32_t mForegroundCount{0}; + uint32_t mLoadFlags{LOAD_NORMAL}; + uint32_t mDefaultLoadFlags{0}; + int32_t mPriority{PRIORITY_NORMAL}; + + nsCOMPtr<nsILoadGroup> mLoadGroup; // load groups can contain load groups + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; + nsCOMPtr<nsIRequestContext> mRequestContext; + nsCOMPtr<nsIRequestContextService> mRequestContextService; + + nsCOMPtr<nsIRequest> mDefaultLoadRequest; + PLDHashTable mRequests; + + nsWeakPtr mObserver; + nsWeakPtr mParentLoadGroup; + + nsresult mStatus{NS_OK}; + bool mIsCanceling{false}; + bool mDefaultLoadIsTimed{false}; + bool mBrowsingContextDiscarded{false}; + bool mExternalRequestContext{false}; + bool mNotifyObserverAboutBackgroundRequests{false}; + + /* Telemetry */ + mozilla::TimeStamp mDefaultRequestCreationTime; + uint32_t mTimedRequests{0}; + uint32_t mCachedRequests{0}; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsLoadGroup_h__ diff --git a/netwerk/base/nsMIMEInputStream.cpp b/netwerk/base/nsMIMEInputStream.cpp new file mode 100644 index 0000000000..d0d9fe09a5 --- /dev/null +++ b/netwerk/base/nsMIMEInputStream.cpp @@ -0,0 +1,500 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * The MIME stream separates headers and a datastream. It also allows + * automatic creation of the content-length header. + */ + +#include "nsMIMEInputStream.h" + +#include <utility> + +#include "ipc/IPCMessageUtils.h" +#include "mozilla/Mutex.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIAsyncInputStream.h" +#include "nsIClassInfoImpl.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsIInputStreamLength.h" +#include "nsIMIMEInputStream.h" +#include "nsISeekableStream.h" +#include "nsString.h" + +using namespace mozilla::ipc; +using mozilla::Maybe; + +class nsMIMEInputStream : public nsIMIMEInputStream, + public nsISeekableStream, + public nsIIPCSerializableInputStream, + public nsIAsyncInputStream, + public nsIInputStreamCallback, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength, + public nsIInputStreamLengthCallback, + public nsICloneableInputStream { + virtual ~nsMIMEInputStream() = default; + + public: + nsMIMEInputStream() = default; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIMIMEINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIINPUTSTREAMLENGTH + NS_DECL_NSIASYNCINPUTSTREAMLENGTH + NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK + NS_DECL_NSICLONEABLEINPUTSTREAM + + private: + struct MOZ_STACK_CLASS ReadSegmentsState { + nsCOMPtr<nsIInputStream> mThisStream; + nsWriteSegmentFun mWriter{nullptr}; + void* mClosure{nullptr}; + }; + static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + + bool IsSeekableInputStream() const; + bool IsAsyncInputStream() const; + bool IsInputStreamLength() const; + bool IsAsyncInputStreamLength() const; + bool IsCloneableInputStream() const; + + nsTArray<HeaderEntry> mHeaders; + + nsCOMPtr<nsIInputStream> mStream; + mozilla::Atomic<bool, mozilla::Relaxed> mStartedReading{false}; + + mozilla::Mutex mMutex{"nsMIMEInputStream::mMutex"}; + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback MOZ_GUARDED_BY(mMutex); + + // This is protected by mutex. + nsCOMPtr<nsIInputStreamLengthCallback> mAsyncInputStreamLengthCallback; +}; + +NS_IMPL_ADDREF(nsMIMEInputStream) +NS_IMPL_RELEASE(nsMIMEInputStream) + +NS_IMPL_CLASSINFO(nsMIMEInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_MIMEINPUTSTREAM_CID) + +NS_INTERFACE_MAP_BEGIN(nsMIMEInputStream) + NS_INTERFACE_MAP_ENTRY(nsIMIMEInputStream) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIInputStream, nsIMIMEInputStream) + NS_INTERFACE_MAP_ENTRY(nsITellableStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, IsSeekableInputStream()) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, IsAsyncInputStream()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, + IsAsyncInputStream()) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMIMEInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, + IsInputStreamLength()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, + IsAsyncInputStreamLength()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback, + IsAsyncInputStreamLength()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, + IsCloneableInputStream()) + NS_IMPL_QUERY_CLASSINFO(nsMIMEInputStream) +NS_INTERFACE_MAP_END + +NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream, nsIMIMEInputStream, + nsIAsyncInputStream, nsIInputStream, + nsISeekableStream, nsITellableStream) + +NS_IMETHODIMP +nsMIMEInputStream::AddHeader(const char* aName, const char* aValue) { + NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE); + + HeaderEntry* entry = mHeaders.AppendElement(); + entry->name().Append(aName); + entry->value().Append(aValue); + + return NS_OK; +} + +NS_IMETHODIMP +nsMIMEInputStream::VisitHeaders(nsIHttpHeaderVisitor* visitor) { + nsresult rv; + + for (auto& header : mHeaders) { + rv = visitor->VisitHeader(header.name(), header.value()); + if (NS_FAILED(rv)) { + return rv; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMIMEInputStream::SetData(nsIInputStream* aStream) { + NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE); + + mStream = aStream; + return NS_OK; +} + +NS_IMETHODIMP +nsMIMEInputStream::GetData(nsIInputStream** aStream) { + NS_ENSURE_ARG_POINTER(aStream); + *aStream = do_AddRef(mStream).take(); + return NS_OK; +} + +#define INITSTREAMS \ + if (!mStartedReading) { \ + NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); \ + mStartedReading = true; \ + } + +// Reset mStartedReading when Seek-ing to start +NS_IMETHODIMP +nsMIMEInputStream::Seek(int32_t whence, int64_t offset) { + NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); + + nsresult rv; + nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream); + + if (whence == NS_SEEK_SET && offset == 0) { + rv = stream->Seek(whence, offset); + if (NS_SUCCEEDED(rv)) mStartedReading = false; + } else { + INITSTREAMS; + rv = stream->Seek(whence, offset); + } + + return rv; +} + +// Proxy ReadSegments since we need to be a good little nsIInputStream +NS_IMETHODIMP nsMIMEInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* _retval) { + INITSTREAMS; + ReadSegmentsState state; + // Disambiguate ambiguous nsIInputStream. + state.mThisStream = + static_cast<nsIInputStream*>(static_cast<nsIMIMEInputStream*>(this)); + state.mWriter = aWriter; + state.mClosure = aClosure; + return mStream->ReadSegments(ReadSegCb, &state, aCount, _retval); +} + +nsresult nsMIMEInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) { + ReadSegmentsState* state = (ReadSegmentsState*)aClosure; + return (state->mWriter)(state->mThisStream, state->mClosure, aFromRawSegment, + aToOffset, aCount, aWriteCount); +} + +/** + * Forward everything else to the mStream after calling INITSTREAMS + */ + +// nsIInputStream +NS_IMETHODIMP nsMIMEInputStream::Close(void) { + INITSTREAMS; + return mStream->Close(); +} +NS_IMETHODIMP nsMIMEInputStream::Available(uint64_t* _retval) { + INITSTREAMS; + return mStream->Available(_retval); +} +NS_IMETHODIMP nsMIMEInputStream::StreamStatus() { + INITSTREAMS; + return mStream->StreamStatus(); +} +NS_IMETHODIMP nsMIMEInputStream::Read(char* buf, uint32_t count, + uint32_t* _retval) { + INITSTREAMS; + return mStream->Read(buf, count, _retval); +} +NS_IMETHODIMP nsMIMEInputStream::IsNonBlocking(bool* aNonBlocking) { + INITSTREAMS; + return mStream->IsNonBlocking(aNonBlocking); +} + +// nsIAsyncInputStream +NS_IMETHODIMP +nsMIMEInputStream::CloseWithStatus(nsresult aStatus) { + INITSTREAMS; + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream); + return asyncStream->CloseWithStatus(aStatus); +} + +NS_IMETHODIMP +nsMIMEInputStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + INITSTREAMS; + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream); + if (NS_WARN_IF(!asyncStream)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr; + { + mozilla::MutexAutoLock lock(mMutex); + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + } + + return asyncStream->AsyncWait(callback, aFlags, aRequestedCount, + aEventTarget); +} + +// nsIInputStreamCallback + +NS_IMETHODIMP +nsMIMEInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { + nsCOMPtr<nsIInputStreamCallback> callback; + + { + mozilla::MutexAutoLock lock(mMutex); + + // We have been canceled in the meanwhile. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + callback.swap(mAsyncWaitCallback); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamReady(this); +} + +// nsITellableStream +NS_IMETHODIMP nsMIMEInputStream::Tell(int64_t* _retval) { + INITSTREAMS; + nsCOMPtr<nsITellableStream> stream = do_QueryInterface(mStream); + return stream->Tell(_retval); +} + +// nsISeekableStream +NS_IMETHODIMP nsMIMEInputStream::SetEOF(void) { + INITSTREAMS; + nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream); + return stream->SetEOF(); +} + +/** + * Factory method used by do_CreateInstance + */ + +nsresult nsMIMEInputStreamConstructor(REFNSIID iid, void** result) { + *result = nullptr; + + RefPtr<nsMIMEInputStream> inst = new nsMIMEInputStream(); + if (!inst) return NS_ERROR_OUT_OF_MEMORY; + + return inst->QueryInterface(iid, result); +} + +void nsMIMEInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + if (nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(mStream)) { + InputStreamHelper::SerializedComplexity(mStream, aMaxSize, aSizeUsed, + aPipes, aTransferables); + } else { + *aPipes = 1; + } +} + +void nsMIMEInputStream::Serialize(InputStreamParams& aParams, uint32_t aMaxSize, + uint32_t* aSizeUsed) { + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + MIMEInputStreamParams params; + params.headers() = mHeaders.Clone(); + params.startedReading() = mStartedReading; + + if (!mStream) { + aParams = params; + return; + } + + InputStreamParams wrappedParams; + + if (nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(mStream)) { + InputStreamHelper::SerializeInputStream(mStream, wrappedParams, aMaxSize, + aSizeUsed); + } else { + // Falling back to sending the underlying stream over a pipe when + // sending an nsMIMEInputStream over IPC is potentially wasteful + // if it is sent several times. This can possibly happen with + // fission. There are two ways to improve this, see bug 1648369 + // and bug 1648370. + InputStreamHelper::SerializeInputStreamAsPipe(mStream, wrappedParams); + } + + NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None, + "Wrapped stream failed to serialize!"); + + params.optionalStream().emplace(wrappedParams); + aParams = params; +} + +bool nsMIMEInputStream::Deserialize(const InputStreamParams& aParams) { + if (aParams.type() != InputStreamParams::TMIMEInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const MIMEInputStreamParams& params = aParams.get_MIMEInputStreamParams(); + const Maybe<InputStreamParams>& wrappedParams = params.optionalStream(); + + if (wrappedParams.isSome()) { + nsCOMPtr<nsIInputStream> stream; + stream = InputStreamHelper::DeserializeInputStream(wrappedParams.ref()); + if (!stream) { + NS_WARNING("Failed to deserialize wrapped stream!"); + return false; + } + + MOZ_ALWAYS_SUCCEEDS(SetData(stream)); + } + + mHeaders = params.headers().Clone(); + mStartedReading = params.startedReading(); + + return true; +} + +NS_IMETHODIMP +nsMIMEInputStream::Length(int64_t* aLength) { + nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream); + if (NS_WARN_IF(!stream)) { + return NS_ERROR_FAILURE; + } + + return stream->Length(aLength); +} + +NS_IMETHODIMP +nsMIMEInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) { + nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream); + if (NS_WARN_IF(!stream)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback ? this : nullptr; + { + mozilla::MutexAutoLock lock(mMutex); + mAsyncInputStreamLengthCallback = aCallback; + } + + return stream->AsyncLengthWait(callback, aEventTarget); +} + +NS_IMETHODIMP +nsMIMEInputStream::OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream, + int64_t aLength) { + nsCOMPtr<nsIInputStreamLengthCallback> callback; + { + mozilla::MutexAutoLock lock(mMutex); + // We have been canceled in the meanwhile. + if (!mAsyncInputStreamLengthCallback) { + return NS_OK; + } + + callback.swap(mAsyncInputStreamLengthCallback); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamLengthReady(this, aLength); +} + +bool nsMIMEInputStream::IsSeekableInputStream() const { + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream); + return !!seekable; +} + +bool nsMIMEInputStream::IsAsyncInputStream() const { + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream); + return !!asyncStream; +} + +bool nsMIMEInputStream::IsInputStreamLength() const { + nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream); + return !!stream; +} + +bool nsMIMEInputStream::IsAsyncInputStreamLength() const { + nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream); + return !!stream; +} + +bool nsMIMEInputStream::IsCloneableInputStream() const { + nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream); + return !!stream; +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +nsMIMEInputStream::GetCloneable(bool* aCloneable) { + nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream); + if (!mStream) { + return NS_ERROR_FAILURE; + } + + return stream->GetCloneable(aCloneable); +} + +NS_IMETHODIMP +nsMIMEInputStream::Clone(nsIInputStream** aResult) { + nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream); + if (!mStream) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIInputStream> clonedStream; + nsresult rv = stream->Clone(getter_AddRefs(clonedStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIMIMEInputStream> mimeStream = new nsMIMEInputStream(); + + rv = mimeStream->SetData(clonedStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (const HeaderEntry& entry : mHeaders) { + rv = mimeStream->AddHeader(entry.name().get(), entry.value().get()); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + static_cast<nsMIMEInputStream*>(mimeStream.get())->mStartedReading = + static_cast<bool>(mStartedReading); + + mimeStream.forget(aResult); + return NS_OK; +} diff --git a/netwerk/base/nsMIMEInputStream.h b/netwerk/base/nsMIMEInputStream.h new file mode 100644 index 0000000000..adfc5532b1 --- /dev/null +++ b/netwerk/base/nsMIMEInputStream.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * The MIME stream separates headers and a datastream. It also allows + * automatic creation of the content-length header. + */ + +#ifndef _nsMIMEInputStream_h_ +#define _nsMIMEInputStream_h_ + +#include "ErrorList.h" +#include "nsID.h" + +#define NS_MIMEINPUTSTREAM_CONTRACTID "@mozilla.org/network/mime-input-stream;1" +#define NS_MIMEINPUTSTREAM_CID \ + { /* 58a1c31c-1dd2-11b2-a3f6-d36949d48268 */ \ + 0x58a1c31c, 0x1dd2, 0x11b2, { \ + 0xa3, 0xf6, 0xd3, 0x69, 0x49, 0xd4, 0x82, 0x68 \ + } \ + } + +extern nsresult nsMIMEInputStreamConstructor(REFNSIID iid, void** result); + +#endif // _nsMIMEInputStream_h_ diff --git a/netwerk/base/nsMediaFragmentURIParser.cpp b/netwerk/base/nsMediaFragmentURIParser.cpp new file mode 100644 index 0000000000..bfa82530c6 --- /dev/null +++ b/netwerk/base/nsMediaFragmentURIParser.cpp @@ -0,0 +1,354 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTArray.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsEscape.h" +#include "nsIURI.h" +#include <utility> + +#include "nsMediaFragmentURIParser.h" + +using std::make_pair; +using std::pair; + +namespace mozilla { +namespace net { + +nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsIURI* aURI) + : mClipUnit(eClipUnit_Pixel) { + nsAutoCString ref; + aURI->GetRef(ref); + Parse(ref); +} + +nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsCString& aRef) + : mClipUnit(eClipUnit_Pixel) { + Parse(aRef); +} + +bool nsMediaFragmentURIParser::ParseNPT(nsDependentSubstring aString) { + nsDependentSubstring original(aString); + if (aString.Length() > 4 && aString[0] == 'n' && aString[1] == 'p' && + aString[2] == 't' && aString[3] == ':') { + aString.Rebind(aString, 4); + } + + if (aString.Length() == 0) { + return false; + } + + double start = -1.0; + double end = -1.0; + + ParseNPTTime(aString, start); + + if (aString.Length() == 0) { + mStart.emplace(start); + return true; + } + + if (aString[0] != ',') { + aString.Rebind(original, 0); + return false; + } + + aString.Rebind(aString, 1); + + if (aString.Length() == 0) { + aString.Rebind(original, 0); + return false; + } + + ParseNPTTime(aString, end); + + if (end <= start || aString.Length() != 0) { + aString.Rebind(original, 0); + return false; + } + + mStart.emplace(start); + mEnd.emplace(end); + return true; +} + +bool nsMediaFragmentURIParser::ParseNPTTime(nsDependentSubstring& aString, + double& aTime) { + if (aString.Length() == 0) { + return false; + } + + return ParseNPTHHMMSS(aString, aTime) || ParseNPTMMSS(aString, aTime) || + ParseNPTSec(aString, aTime); +} + +// Return true if the given character is a numeric character +static bool IsDigit(nsDependentSubstring::char_type aChar) { + return (aChar >= '0' && aChar <= '9'); +} + +// Return the index of the first character in the string that is not +// a numerical digit, starting from 'aStart'. +static uint32_t FirstNonDigit(nsDependentSubstring& aString, uint32_t aStart) { + while (aStart < aString.Length() && IsDigit(aString[aStart])) { + ++aStart; + } + return aStart; +} + +bool nsMediaFragmentURIParser::ParseNPTSec(nsDependentSubstring& aString, + double& aSec) { + nsDependentSubstring original(aString); + if (aString.Length() == 0) { + return false; + } + + uint32_t index = FirstNonDigit(aString, 0); + if (index == 0) { + return false; + } + + nsDependentSubstring n(aString, 0, index); + nsresult ec; + int32_t s = n.ToInteger(&ec); + if (NS_FAILED(ec)) { + return false; + } + + aString.Rebind(aString, index); + double fraction = 0.0; + if (!ParseNPTFraction(aString, fraction)) { + aString.Rebind(original, 0); + return false; + } + + aSec = s + fraction; + return true; +} + +bool nsMediaFragmentURIParser::ParseNPTMMSS(nsDependentSubstring& aString, + double& aTime) { + nsDependentSubstring original(aString); + uint32_t mm = 0; + uint32_t ss = 0; + double fraction = 0.0; + if (!ParseNPTMM(aString, mm)) { + aString.Rebind(original, 0); + return false; + } + + if (aString.Length() < 2 || aString[0] != ':') { + aString.Rebind(original, 0); + return false; + } + + aString.Rebind(aString, 1); + if (!ParseNPTSS(aString, ss)) { + aString.Rebind(original, 0); + return false; + } + + if (!ParseNPTFraction(aString, fraction)) { + aString.Rebind(original, 0); + return false; + } + aTime = mm * 60 + ss + fraction; + return true; +} + +bool nsMediaFragmentURIParser::ParseNPTFraction(nsDependentSubstring& aString, + double& aFraction) { + double fraction = 0.0; + + if (aString.Length() > 0 && aString[0] == '.') { + uint32_t index = FirstNonDigit(aString, 1); + + if (index > 1) { + nsDependentSubstring number(aString, 0, index); + nsresult ec; + fraction = PromiseFlatString(number).ToDouble(&ec); + if (NS_FAILED(ec)) { + return false; + } + } + aString.Rebind(aString, index); + } + + aFraction = fraction; + return true; +} + +bool nsMediaFragmentURIParser::ParseNPTHHMMSS(nsDependentSubstring& aString, + double& aTime) { + nsDependentSubstring original(aString); + uint32_t hh = 0; + double seconds = 0.0; + if (!ParseNPTHH(aString, hh)) { + return false; + } + + if (aString.Length() < 2 || aString[0] != ':') { + aString.Rebind(original, 0); + return false; + } + + aString.Rebind(aString, 1); + if (!ParseNPTMMSS(aString, seconds)) { + aString.Rebind(original, 0); + return false; + } + + aTime = hh * 3600 + seconds; + return true; +} + +bool nsMediaFragmentURIParser::ParseNPTHH(nsDependentSubstring& aString, + uint32_t& aHour) { + if (aString.Length() == 0) { + return false; + } + + uint32_t index = FirstNonDigit(aString, 0); + if (index == 0) { + return false; + } + + nsDependentSubstring n(aString, 0, index); + nsresult ec; + int32_t u = n.ToInteger(&ec); + if (NS_FAILED(ec)) { + return false; + } + + aString.Rebind(aString, index); + aHour = u; + return true; +} + +bool nsMediaFragmentURIParser::ParseNPTMM(nsDependentSubstring& aString, + uint32_t& aMinute) { + return ParseNPTSS(aString, aMinute); +} + +bool nsMediaFragmentURIParser::ParseNPTSS(nsDependentSubstring& aString, + uint32_t& aSecond) { + if (aString.Length() < 2) { + return false; + } + + if (IsDigit(aString[0]) && IsDigit(aString[1])) { + nsDependentSubstring n(aString, 0, 2); + nsresult ec; + int32_t u = n.ToInteger(&ec); + if (NS_FAILED(ec)) { + return false; + } + + aString.Rebind(aString, 2); + if (u >= 60) return false; + + aSecond = u; + return true; + } + + return false; +} + +static bool ParseInteger(nsDependentSubstring& aString, int32_t& aResult) { + uint32_t index = FirstNonDigit(aString, 0); + if (index == 0) { + return false; + } + + nsDependentSubstring n(aString, 0, index); + nsresult ec; + int32_t s = n.ToInteger(&ec); + if (NS_FAILED(ec)) { + return false; + } + + aString.Rebind(aString, index); + aResult = s; + return true; +} + +static bool ParseCommaSeparator(nsDependentSubstring& aString) { + if (aString.Length() > 1 && aString[0] == ',') { + aString.Rebind(aString, 1); + return true; + } + + return false; +} + +bool nsMediaFragmentURIParser::ParseXYWH(nsDependentSubstring aString) { + int32_t x, y, w, h; + ClipUnit clipUnit; + + // Determine units. + if (StringBeginsWith(aString, u"pixel:"_ns)) { + clipUnit = eClipUnit_Pixel; + aString.Rebind(aString, 6); + } else if (StringBeginsWith(aString, u"percent:"_ns)) { + clipUnit = eClipUnit_Percent; + aString.Rebind(aString, 8); + } else { + clipUnit = eClipUnit_Pixel; + } + + // Read and validate coordinates. + if (ParseInteger(aString, x) && x >= 0 && ParseCommaSeparator(aString) && + ParseInteger(aString, y) && y >= 0 && ParseCommaSeparator(aString) && + ParseInteger(aString, w) && w > 0 && ParseCommaSeparator(aString) && + ParseInteger(aString, h) && h > 0 && aString.Length() == 0) { + // Reject invalid percentage coordinates. + if (clipUnit == eClipUnit_Percent && (x + w > 100 || y + h > 100)) { + return false; + } + + mClip.emplace(x, y, w, h); + mClipUnit = clipUnit; + return true; + } + + return false; +} + +void nsMediaFragmentURIParser::Parse(nsACString& aRef) { + // Create an array of possibly-invalid media fragments. + nsTArray<std::pair<nsCString, nsCString> > fragments; + + for (const nsACString& nv : nsCCharSeparatedTokenizer(aRef, '&').ToRange()) { + int32_t index = nv.FindChar('='); + if (index >= 0) { + nsAutoCString name; + nsAutoCString value; + NS_UnescapeURL(StringHead(nv, index), esc_Ref | esc_AlwaysCopy, name); + NS_UnescapeURL(Substring(nv, index + 1, nv.Length()), + esc_Ref | esc_AlwaysCopy, value); + fragments.AppendElement(make_pair(name, value)); + } + } + + // Parse the media fragment values. + bool gotTemporal = false, gotSpatial = false; + for (int i = fragments.Length() - 1; i >= 0; --i) { + if (gotTemporal && gotSpatial) { + // We've got one of each possible type. No need to look at the rest. + break; + } + if (!gotTemporal && fragments[i].first.EqualsLiteral("t")) { + nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second); + gotTemporal = ParseNPT(nsDependentSubstring(value, 0)); + } else if (!gotSpatial && fragments[i].first.EqualsLiteral("xywh")) { + nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second); + gotSpatial = ParseXYWH(nsDependentSubstring(value, 0)); + } + } +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsMediaFragmentURIParser.h b/netwerk/base/nsMediaFragmentURIParser.h new file mode 100644 index 0000000000..4c277fead8 --- /dev/null +++ b/netwerk/base/nsMediaFragmentURIParser.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(nsMediaFragmentURIParser_h__) +# define nsMediaFragmentURIParser_h__ + +# include "mozilla/Maybe.h" +# include "nsStringFwd.h" +# include "nsRect.h" + +class nsIURI; + +// Class to handle parsing of a W3C media fragment URI as per +// spec at: http://www.w3.org/TR/media-frags/ +// Only the temporaral URI portion of the spec is implemented. +// To use: +// a) Construct an instance with the URI containing the fragment +// b) Check for the validity of the values you are interested in +// using e.g. HasStartTime(). +// c) If the values are valid, obtain them using e.g. GetStartTime(). + +namespace mozilla { +namespace net { + +enum ClipUnit { + eClipUnit_Pixel, + eClipUnit_Percent, +}; + +class nsMediaFragmentURIParser { + public: + // Create a parser with the provided URI. + explicit nsMediaFragmentURIParser(nsIURI* aURI); + + // Create a parser with the provided URI reference portion. + explicit nsMediaFragmentURIParser(nsCString& aRef); + + // True if a valid temporal media fragment indicated a start time. + bool HasStartTime() const { return mStart.isSome(); } + + // If a valid temporal media fragment indicated a start time, returns + // it in units of seconds. If not, defaults to 0. + double GetStartTime() const { return *mStart; } + + // True if a valid temporal media fragment indicated an end time. + bool HasEndTime() const { return mEnd.isSome(); } + + // If a valid temporal media fragment indicated an end time, returns + // it in units of seconds. If not, defaults to -1. + double GetEndTime() const { return *mEnd; } + + // True if a valid spatial media fragment indicated a clipping region. + bool HasClip() const { return mClip.isSome(); } + + // If a valid spatial media fragment indicated a clipping region, + // returns the region. If not, returns an empty region. The unit + // used depends on the value returned by GetClipUnit(). + nsIntRect GetClip() const { return *mClip; } + + // If a valid spatial media fragment indicated a clipping region, + // returns the unit used. + ClipUnit GetClipUnit() const { return mClipUnit; } + + private: + // Parse the URI ref provided, looking for media fragments. This is + // the top-level parser the invokes the others below. + void Parse(nsACString& aRef); + + // The following methods parse the fragment as per the media + // fragments specification. 'aString' contains the remaining + // fragment data to be parsed. The method returns true + // if the parse was successful and leaves the remaining unparsed + // data in 'aString'. If the parse fails then false is returned + // and 'aString' is left as it was when called. + bool ParseNPT(nsDependentSubstring aString); + bool ParseNPTTime(nsDependentSubstring& aString, double& aTime); + bool ParseNPTSec(nsDependentSubstring& aString, double& aSec); + bool ParseNPTFraction(nsDependentSubstring& aString, double& aFraction); + bool ParseNPTMMSS(nsDependentSubstring& aString, double& aTime); + bool ParseNPTHHMMSS(nsDependentSubstring& aString, double& aTime); + bool ParseNPTHH(nsDependentSubstring& aString, uint32_t& aHour); + bool ParseNPTMM(nsDependentSubstring& aString, uint32_t& aMinute); + bool ParseNPTSS(nsDependentSubstring& aString, uint32_t& aSecond); + bool ParseXYWH(nsDependentSubstring aString); + bool ParseMozResolution(nsDependentSubstring aString); + + // Media fragment information. + Maybe<double> mStart; + Maybe<double> mEnd; + Maybe<nsIntRect> mClip; + ClipUnit mClipUnit; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/nsNetAddr.cpp b/netwerk/base/nsNetAddr.cpp new file mode 100644 index 0000000000..d0cbe69bae --- /dev/null +++ b/netwerk/base/nsNetAddr.cpp @@ -0,0 +1,138 @@ +/* vim: et ts=2 sw=2 tw=80 + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNetAddr.h" +#include "nsString.h" +#include "mozilla/net/DNS.h" + +using namespace mozilla::net; + +NS_IMPL_ISUPPORTS(nsNetAddr, nsINetAddr) + +NS_IMETHODIMP nsNetAddr::GetFamily(uint16_t* aFamily) { + switch (mAddr.raw.family) { + case AF_INET: + *aFamily = nsINetAddr::FAMILY_INET; + break; + case AF_INET6: + *aFamily = nsINetAddr::FAMILY_INET6; + break; +#if defined(XP_UNIX) + case AF_LOCAL: + *aFamily = nsINetAddr::FAMILY_LOCAL; + break; +#endif + default: + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP nsNetAddr::GetAddress(nsACString& aAddress) { + switch (mAddr.raw.family) { + /* PR_NetAddrToString can handle INET and INET6, but not LOCAL. */ + case AF_INET: + aAddress.SetLength(kIPv4CStrBufSize); + mAddr.ToStringBuffer(aAddress.BeginWriting(), kIPv4CStrBufSize); + aAddress.SetLength(strlen(aAddress.BeginReading())); + break; + case AF_INET6: + aAddress.SetLength(kIPv6CStrBufSize); + mAddr.ToStringBuffer(aAddress.BeginWriting(), kIPv6CStrBufSize); + aAddress.SetLength(strlen(aAddress.BeginReading())); + break; +#if defined(XP_UNIX) + case AF_LOCAL: + aAddress.Assign(mAddr.local.path); + break; +#endif + // PR_AF_LOCAL falls through to default when not XP_UNIX + default: + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP nsNetAddr::GetPort(uint16_t* aPort) { + switch (mAddr.raw.family) { + case AF_INET: + *aPort = ntohs(mAddr.inet.port); + break; + case AF_INET6: + *aPort = ntohs(mAddr.inet6.port); + break; +#if defined(XP_UNIX) + case AF_LOCAL: + // There is no port number for local / connections. + return NS_ERROR_NOT_AVAILABLE; +#endif + default: + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP nsNetAddr::GetFlow(uint32_t* aFlow) { + switch (mAddr.raw.family) { + case AF_INET6: + *aFlow = ntohl(mAddr.inet6.flowinfo); + break; + case AF_INET: +#if defined(XP_UNIX) + case AF_LOCAL: +#endif + // only for IPv6 + return NS_ERROR_NOT_AVAILABLE; + default: + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP nsNetAddr::GetScope(uint32_t* aScope) { + switch (mAddr.raw.family) { + case AF_INET6: + *aScope = ntohl(mAddr.inet6.scope_id); + break; + case AF_INET: +#if defined(XP_UNIX) + case AF_LOCAL: +#endif + // only for IPv6 + return NS_ERROR_NOT_AVAILABLE; + default: + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP nsNetAddr::GetIsV4Mapped(bool* aIsV4Mapped) { + switch (mAddr.raw.family) { + case AF_INET6: + *aIsV4Mapped = IPv6ADDR_IS_V4MAPPED(&mAddr.inet6.ip); + break; + case AF_INET: +#if defined(XP_UNIX) + case AF_LOCAL: +#endif + // only for IPv6 + return NS_ERROR_NOT_AVAILABLE; + default: + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP nsNetAddr::GetNetAddr(NetAddr* aResult) { + memcpy(aResult, &mAddr, sizeof(mAddr)); + return NS_OK; +} diff --git a/netwerk/base/nsNetAddr.h b/netwerk/base/nsNetAddr.h new file mode 100644 index 0000000000..49d43ccaee --- /dev/null +++ b/netwerk/base/nsNetAddr.h @@ -0,0 +1,30 @@ +/* vim: et ts=2 sw=2 tw=80 + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsNetAddr_h__ +#define nsNetAddr_h__ + +#include "nsINetAddr.h" +#include "mozilla/net/DNS.h" +#include "mozilla/Attributes.h" + +class nsNetAddr final : public nsINetAddr { + ~nsNetAddr() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSINETADDR + + explicit nsNetAddr(const mozilla::net::NetAddr* addr) : mAddr(*addr) {} + + private: + mozilla::net::NetAddr mAddr; + + protected: + /* additional members */ +}; + +#endif // !nsNetAddr_h__ diff --git a/netwerk/base/nsNetSegmentUtils.h b/netwerk/base/nsNetSegmentUtils.h new file mode 100644 index 0000000000..a65defdb50 --- /dev/null +++ b/netwerk/base/nsNetSegmentUtils.h @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsNetSegmentUtils_h__ +#define nsNetSegmentUtils_h__ + +#include "nsIOService.h" + +/** + * applies defaults to segment params in a consistent way. + */ +static inline void net_ResolveSegmentParams(uint32_t& segsize, + uint32_t& segcount) { + if (!segsize) segsize = mozilla::net::nsIOService::gDefaultSegmentSize; + + if (!segcount) segcount = mozilla::net::nsIOService::gDefaultSegmentCount; +} + +#endif // !nsNetSegmentUtils_h__ diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp new file mode 100644 index 0000000000..4c03cf63c3 --- /dev/null +++ b/netwerk/base/nsNetUtil.cpp @@ -0,0 +1,4088 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 sts=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// HttpLog.h should generally be included first +#include "DecoderDoctorDiagnostics.h" +#include "HttpLog.h" + +#include "nsNetUtil.h" + +#include "mozilla/Atomics.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Components.h" +#include "mozilla/Encoding.h" +#include "mozilla/LoadContext.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Monitor.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StoragePrincipalHelper.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Telemetry.h" +#include "nsBufferedStreams.h" +#include "nsCategoryCache.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsEscape.h" +#include "nsFileStreams.h" +#include "nsHashKeys.h" +#include "nsHttp.h" +#include "nsMimeTypes.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsIAuthPromptAdapterFactory.h" +#include "nsIBufferedStreams.h" +#include "nsBufferedStreams.h" +#include "nsIChannelEventSink.h" +#include "nsIContentSniffer.h" +#include "mozilla/dom/Document.h" +#include "nsIDownloader.h" +#include "nsIFileProtocolHandler.h" +#include "nsIFileStreams.h" +#include "nsIFileURL.h" +#include "nsIIDNService.h" +#include "nsIInputStreamChannel.h" +#include "nsIInputStreamPump.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILoadContext.h" +#include "nsIMIMEHeaderParam.h" +#include "nsINode.h" +#include "nsIObjectLoadingContent.h" +#include "nsPersistentProperties.h" +#include "nsIPrivateBrowsingChannel.h" +#include "nsIPropertyBag2.h" +#include "nsIProtocolProxyService.h" +#include "mozilla/net/RedirectChannelRegistrar.h" +#include "nsRequestObserverProxy.h" +#include "nsISensitiveInfoHiddenURI.h" +#include "nsISimpleStreamListener.h" +#include "nsISocketProvider.h" +#include "nsIStandardURL.h" +#include "nsIStreamLoader.h" +#include "nsIIncrementalStreamLoader.h" +#include "nsStringStream.h" +#include "nsSyncStreamListener.h" +#include "nsITextToSubURI.h" +#include "nsIURIWithSpecialOrigin.h" +#include "nsIViewSourceChannel.h" +#include "nsInterfaceRequestorAgg.h" +#include "nsINestedURI.h" +#include "mozilla/dom/nsCSPUtils.h" +#include "mozilla/dom/nsHTTPSOnlyUtils.h" +#include "mozilla/dom/nsMixedContentBlocker.h" +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/net/HttpBaseChannel.h" +#include "nsIScriptError.h" +#include "nsISiteSecurityService.h" +#include "nsHttpHandler.h" +#include "nsNSSComponent.h" +#include "nsIRedirectHistoryEntry.h" +#include "nsICertStorage.h" +#include "nsICertOverrideService.h" +#include "nsQueryObject.h" +#include "mozIThirdPartyUtil.h" +#include "../mime/nsMIMEHeaderParamImpl.h" +#include "nsStandardURL.h" +#include "DefaultURI.h" +#include "nsChromeProtocolHandler.h" +#include "nsJSProtocolHandler.h" +#include "nsDataHandler.h" +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "nsStreamUtils.h" +#include "nsSocketTransportService2.h" +#include "nsViewSourceHandler.h" +#include "nsJARURI.h" +#include "nsIconURI.h" +#include "nsAboutProtocolHandler.h" +#include "nsResProtocolHandler.h" +#include "mozilla/net/ExtensionProtocolHandler.h" +#include "mozilla/net/PageThumbProtocolHandler.h" +#include "mozilla/net/SFVService.h" +#include <limits> +#include "nsIXPConnect.h" +#include "nsParserConstants.h" +#include "nsCRT.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/dom/MediaList.h" +#include "MediaContainerType.h" +#include "DecoderTraits.h" +#include "imgLoader.h" + +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) +# include "nsNewMailnewsURI.h" +#endif + +using namespace mozilla; +using namespace mozilla::net; +using mozilla::dom::BlobURLProtocolHandler; +using mozilla::dom::ClientInfo; +using mozilla::dom::PerformanceStorage; +using mozilla::dom::ServiceWorkerDescriptor; + +#define MAX_RECURSION_COUNT 50 + +already_AddRefed<nsIIOService> do_GetIOService(nsresult* error /* = 0 */) { + nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service(); + if (error) *error = io ? NS_OK : NS_ERROR_FAILURE; + return io.forget(); +} + +nsresult NS_NewLocalFileInputStream(nsIInputStream** result, nsIFile* file, + int32_t ioFlags /* = -1 */, + int32_t perm /* = -1 */, + int32_t behaviorFlags /* = 0 */) { + nsresult rv; + nsCOMPtr<nsIFileInputStream> in = + do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = in->Init(file, ioFlags, perm, behaviorFlags); + if (NS_SUCCEEDED(rv)) in.forget(result); + } + return rv; +} + +Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewLocalFileInputStream( + nsIFile* file, int32_t ioFlags /* = -1 */, int32_t perm /* = -1 */, + int32_t behaviorFlags /* = 0 */) { + nsCOMPtr<nsIInputStream> stream; + const nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, + ioFlags, perm, behaviorFlags); + if (NS_SUCCEEDED(rv)) { + return stream; + } + return Err(rv); +} + +nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result, nsIFile* file, + int32_t ioFlags /* = -1 */, + int32_t perm /* = -1 */, + int32_t behaviorFlags /* = 0 */) { + nsresult rv; + nsCOMPtr<nsIFileOutputStream> out = + do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = out->Init(file, ioFlags, perm, behaviorFlags); + if (NS_SUCCEEDED(rv)) out.forget(result); + } + return rv; +} + +Result<nsCOMPtr<nsIOutputStream>, nsresult> NS_NewLocalFileOutputStream( + nsIFile* file, int32_t ioFlags /* = -1 */, int32_t perm /* = -1 */, + int32_t behaviorFlags /* = 0 */) { + nsCOMPtr<nsIOutputStream> stream; + const nsresult rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), file, + ioFlags, perm, behaviorFlags); + if (NS_SUCCEEDED(rv)) { + return stream; + } + return Err(rv); +} + +nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result, + const mozilla::ipc::FileDescriptor& fd) { + nsCOMPtr<nsIFileOutputStream> out; + nsFileOutputStream::Create(NS_GET_IID(nsIFileOutputStream), + getter_AddRefs(out)); + + nsresult rv = + static_cast<nsFileOutputStream*>(out.get())->InitWithFileDescriptor(fd); + if (NS_FAILED(rv)) { + return rv; + } + + out.forget(result); + return NS_OK; +} + +nsresult net_EnsureIOService(nsIIOService** ios, nsCOMPtr<nsIIOService>& grip) { + nsresult rv = NS_OK; + if (!*ios) { + grip = do_GetIOService(&rv); + *ios = grip; + } + return rv; +} + +nsresult NS_NewFileURI( + nsIURI** result, nsIFile* spec, + nsIIOService* + ioService /* = nullptr */) // pass in nsIIOService to optimize callers +{ + nsresult rv; + nsCOMPtr<nsIIOService> grip; + rv = net_EnsureIOService(&ioService, grip); + if (ioService) rv = ioService->NewFileURI(spec, result); + return rv; +} + +nsresult NS_GetURIWithNewRef(nsIURI* aInput, const nsACString& aRef, + nsIURI** aOutput) { + MOZ_DIAGNOSTIC_ASSERT(aRef.IsEmpty() || aRef[0] == '#'); + + if (NS_WARN_IF(!aInput || !aOutput)) { + return NS_ERROR_INVALID_ARG; + } + + bool hasRef; + nsresult rv = aInput->GetHasRef(&hasRef); + + nsAutoCString ref; + if (NS_SUCCEEDED(rv)) { + rv = aInput->GetRef(ref); + } + + // If the ref is already equal to the new ref, we do not need to do anything. + // Also, if the GetRef failed (it could return NS_ERROR_NOT_IMPLEMENTED) + // we can assume SetRef would fail as well, so returning the original + // URI is OK. + // + // Note that aRef contains the hash, but ref doesn't, so need to account for + // that in the equality check. + if (NS_FAILED(rv) || (!hasRef && aRef.IsEmpty()) || + (!aRef.IsEmpty() && hasRef && + Substring(aRef.Data() + 1, aRef.Length() - 1) == ref)) { + nsCOMPtr<nsIURI> uri = aInput; + uri.forget(aOutput); + return NS_OK; + } + + return NS_MutateURI(aInput).SetRef(aRef).Finalize(aOutput); +} + +nsresult NS_GetURIWithoutRef(nsIURI* aInput, nsIURI** aOutput) { + return NS_GetURIWithNewRef(aInput, ""_ns, aOutput); +} + +nsresult NS_NewChannelInternal( + nsIChannel** outChannel, nsIURI* aUri, nsILoadInfo* aLoadInfo, + PerformanceStorage* aPerformanceStorage /* = nullptr */, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService* aIoService /* = nullptr */) { + // NS_NewChannelInternal is mostly called for channel redirects. We should + // allow the creation of a channel even if the original channel did not have a + // loadinfo attached. + NS_ENSURE_ARG_POINTER(outChannel); + + nsCOMPtr<nsIIOService> grip; + nsresult rv = net_EnsureIOService(&aIoService, grip); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel; + rv = aIoService->NewChannelFromURIWithLoadInfo(aUri, aLoadInfo, + getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aLoadGroup) { + rv = channel->SetLoadGroup(aLoadGroup); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aCallbacks) { + rv = channel->SetNotificationCallbacks(aCallbacks); + NS_ENSURE_SUCCESS(rv, rv); + } + +#ifdef DEBUG + nsLoadFlags channelLoadFlags = 0; + channel->GetLoadFlags(&channelLoadFlags); + // Will be removed when we remove LOAD_REPLACE altogether + // This check is trying to catch protocol handlers that still + // try to set the LOAD_REPLACE flag. + MOZ_DIAGNOSTIC_ASSERT(!(channelLoadFlags & nsIChannel::LOAD_REPLACE)); +#endif + + if (aLoadFlags != nsIRequest::LOAD_NORMAL) { + rv = channel->SetLoadFlags(aLoadFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aPerformanceStorage) { + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + loadInfo->SetPerformanceStorage(aPerformanceStorage); + } + + channel.forget(outChannel); + return NS_OK; +} + +namespace { + +void AssertLoadingPrincipalAndClientInfoMatch( + nsIPrincipal* aLoadingPrincipal, const ClientInfo& aLoadingClientInfo, + nsContentPolicyType aType) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // Verify that the provided loading ClientInfo matches the loading + // principal. Unfortunately we can't just use nsIPrincipal::Equals() here + // because of some corner cases: + // + // 1. Worker debugger scripts want to use a system loading principal for + // worker scripts with a content principal. We exempt these from this + // check. + // 2. Null principals currently require exact object identity for + // nsIPrincipal::Equals() to return true. This doesn't work here because + // ClientInfo::GetPrincipal() uses PrincipalInfoToPrincipal() to allocate + // a new object. To work around this we compare the principal origin + // string itself. If bug 1431771 is fixed then we could switch to + // Equals(). + + // Allow worker debugger to load with a system principal. + if (aLoadingPrincipal->IsSystemPrincipal() && + (aType == nsIContentPolicy::TYPE_INTERNAL_WORKER || + aType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER || + aType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER || + aType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS || + aType == nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE)) { + return; + } + + // Perform a fast comparison for most principal checks. + auto clientPrincipalOrErr(aLoadingClientInfo.GetPrincipal()); + if (clientPrincipalOrErr.isOk()) { + nsCOMPtr<nsIPrincipal> clientPrincipal = clientPrincipalOrErr.unwrap(); + if (aLoadingPrincipal->Equals(clientPrincipal)) { + return; + } + // Fall back to a slower origin equality test to support null principals. + nsAutoCString loadingOriginNoSuffix; + MOZ_ALWAYS_SUCCEEDS( + aLoadingPrincipal->GetOriginNoSuffix(loadingOriginNoSuffix)); + + nsAutoCString clientOriginNoSuffix; + MOZ_ALWAYS_SUCCEEDS( + clientPrincipal->GetOriginNoSuffix(clientOriginNoSuffix)); + + // The client principal will have the partitionKey set if it's in a third + // party context, but the loading principal won't. So, we ignore he + // partitionKey when doing the verification here. + MOZ_DIAGNOSTIC_ASSERT(loadingOriginNoSuffix == clientOriginNoSuffix); + MOZ_DIAGNOSTIC_ASSERT( + aLoadingPrincipal->OriginAttributesRef().EqualsIgnoringPartitionKey( + clientPrincipal->OriginAttributesRef())); + } +#endif +} + +} // namespace + +nsresult NS_NewChannel(nsIChannel** outChannel, nsIURI* aUri, + nsIPrincipal* aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings /* = nullptr */, + PerformanceStorage* aPerformanceStorage /* = nullptr */, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService* aIoService /* = nullptr */, + uint32_t aSandboxFlags /* = 0 */, + bool aSkipCheckForBrokenURLOrZeroSized /* = false */) { + return NS_NewChannelInternal( + outChannel, aUri, + nullptr, // aLoadingNode, + aLoadingPrincipal, + nullptr, // aTriggeringPrincipal + Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, + aContentPolicyType, aCookieJarSettings, aPerformanceStorage, aLoadGroup, + aCallbacks, aLoadFlags, aIoService, aSandboxFlags, + aSkipCheckForBrokenURLOrZeroSized); +} + +nsresult NS_NewChannel(nsIChannel** outChannel, nsIURI* aUri, + nsIPrincipal* aLoadingPrincipal, + const ClientInfo& aLoadingClientInfo, + const Maybe<ServiceWorkerDescriptor>& aController, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings /* = nullptr */, + PerformanceStorage* aPerformanceStorage /* = nullptr */, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService* aIoService /* = nullptr */, + uint32_t aSandboxFlags /* = 0 */, + bool aSkipCheckForBrokenURLOrZeroSized /* = false */) { + AssertLoadingPrincipalAndClientInfoMatch( + aLoadingPrincipal, aLoadingClientInfo, aContentPolicyType); + + Maybe<ClientInfo> loadingClientInfo; + loadingClientInfo.emplace(aLoadingClientInfo); + + return NS_NewChannelInternal( + outChannel, aUri, + nullptr, // aLoadingNode, + aLoadingPrincipal, + nullptr, // aTriggeringPrincipal + loadingClientInfo, aController, aSecurityFlags, aContentPolicyType, + aCookieJarSettings, aPerformanceStorage, aLoadGroup, aCallbacks, + aLoadFlags, aIoService, aSandboxFlags, aSkipCheckForBrokenURLOrZeroSized); +} + +nsresult NS_NewChannelInternal( + nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + const Maybe<ClientInfo>& aLoadingClientInfo, + const Maybe<ServiceWorkerDescriptor>& aController, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings /* = nullptr */, + PerformanceStorage* aPerformanceStorage /* = nullptr */, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService* aIoService /* = nullptr */, uint32_t aSandboxFlags /* = 0 */, + bool aSkipCheckForBrokenURLOrZeroSized /* = false */) { + NS_ENSURE_ARG_POINTER(outChannel); + + nsCOMPtr<nsIIOService> grip; + nsresult rv = net_EnsureIOService(&aIoService, grip); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel; + rv = aIoService->NewChannelFromURIWithClientAndController( + aUri, aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal, + aLoadingClientInfo, aController, aSecurityFlags, aContentPolicyType, + aSandboxFlags, aSkipCheckForBrokenURLOrZeroSized, + getter_AddRefs(channel)); + if (NS_FAILED(rv)) { + return rv; + } + + if (aLoadGroup) { + rv = channel->SetLoadGroup(aLoadGroup); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aCallbacks) { + rv = channel->SetNotificationCallbacks(aCallbacks); + NS_ENSURE_SUCCESS(rv, rv); + } + +#ifdef DEBUG + nsLoadFlags channelLoadFlags = 0; + channel->GetLoadFlags(&channelLoadFlags); + // Will be removed when we remove LOAD_REPLACE altogether + // This check is trying to catch protocol handlers that still + // try to set the LOAD_REPLACE flag. + MOZ_DIAGNOSTIC_ASSERT(!(channelLoadFlags & nsIChannel::LOAD_REPLACE)); +#endif + + if (aLoadFlags != nsIRequest::LOAD_NORMAL) { + rv = channel->SetLoadFlags(aLoadFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aPerformanceStorage || aCookieJarSettings) { + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + + if (aPerformanceStorage) { + loadInfo->SetPerformanceStorage(aPerformanceStorage); + } + + if (aCookieJarSettings) { + loadInfo->SetCookieJarSettings(aCookieJarSettings); + } + } + + channel.forget(outChannel); + return NS_OK; +} + +nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */ +NS_NewChannelWithTriggeringPrincipal( + nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode, + nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + PerformanceStorage* aPerformanceStorage /* = nullptr */, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService* aIoService /* = nullptr */) { + MOZ_ASSERT(aLoadingNode); + NS_ASSERTION(aTriggeringPrincipal, + "Can not create channel without a triggering Principal!"); + return NS_NewChannelInternal( + outChannel, aUri, aLoadingNode, aLoadingNode->NodePrincipal(), + aTriggeringPrincipal, Maybe<ClientInfo>(), + Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, aContentPolicyType, + aLoadingNode->OwnerDoc()->CookieJarSettings(), aPerformanceStorage, + aLoadGroup, aCallbacks, aLoadFlags, aIoService); +} + +// See NS_NewChannelInternal for usage and argument description +nsresult NS_NewChannelWithTriggeringPrincipal( + nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings /* = nullptr */, + PerformanceStorage* aPerformanceStorage /* = nullptr */, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService* aIoService /* = nullptr */) { + NS_ASSERTION(aLoadingPrincipal, + "Can not create channel without a loading Principal!"); + return NS_NewChannelInternal( + outChannel, aUri, + nullptr, // aLoadingNode + aLoadingPrincipal, aTriggeringPrincipal, Maybe<ClientInfo>(), + Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, aContentPolicyType, + aCookieJarSettings, aPerformanceStorage, aLoadGroup, aCallbacks, + aLoadFlags, aIoService); +} + +// See NS_NewChannelInternal for usage and argument description +nsresult NS_NewChannelWithTriggeringPrincipal( + nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, const ClientInfo& aLoadingClientInfo, + const Maybe<ServiceWorkerDescriptor>& aController, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings /* = nullptr */, + PerformanceStorage* aPerformanceStorage /* = nullptr */, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService* aIoService /* = nullptr */) { + AssertLoadingPrincipalAndClientInfoMatch( + aLoadingPrincipal, aLoadingClientInfo, aContentPolicyType); + + Maybe<ClientInfo> loadingClientInfo; + loadingClientInfo.emplace(aLoadingClientInfo); + + return NS_NewChannelInternal( + outChannel, aUri, + nullptr, // aLoadingNode + aLoadingPrincipal, aTriggeringPrincipal, loadingClientInfo, aController, + aSecurityFlags, aContentPolicyType, aCookieJarSettings, + aPerformanceStorage, aLoadGroup, aCallbacks, aLoadFlags, aIoService); +} + +nsresult NS_NewChannel(nsIChannel** outChannel, nsIURI* aUri, + nsINode* aLoadingNode, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + PerformanceStorage* aPerformanceStorage /* = nullptr */, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService* aIoService /* = nullptr */, + uint32_t aSandboxFlags /* = 0 */, + bool aSkipCheckForBrokenURLOrZeroSized /* = false */) { + NS_ASSERTION(aLoadingNode, "Can not create channel without a loading Node!"); + return NS_NewChannelInternal( + outChannel, aUri, aLoadingNode, aLoadingNode->NodePrincipal(), + nullptr, // aTriggeringPrincipal + Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, + aContentPolicyType, aLoadingNode->OwnerDoc()->CookieJarSettings(), + aPerformanceStorage, aLoadGroup, aCallbacks, aLoadFlags, aIoService, + aSandboxFlags, aSkipCheckForBrokenURLOrZeroSized); +} + +nsresult NS_GetIsDocumentChannel(nsIChannel* aChannel, bool* aIsDocument) { + // Check if this channel is going to be used to create a document. If it has + // LOAD_DOCUMENT_URI set it is trivially creating a document. If + // LOAD_HTML_OBJECT_DATA is set it may or may not be used to create a + // document, depending on its MIME type. + + if (!aChannel || !aIsDocument) { + return NS_ERROR_NULL_POINTER; + } + *aIsDocument = false; + nsLoadFlags loadFlags; + nsresult rv = aChannel->GetLoadFlags(&loadFlags); + if (NS_FAILED(rv)) { + return rv; + } + if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) { + *aIsDocument = true; + return NS_OK; + } + if (!(loadFlags & nsIRequest::LOAD_HTML_OBJECT_DATA)) { + *aIsDocument = false; + return NS_OK; + } + nsAutoCString mimeType; + rv = aChannel->GetContentType(mimeType); + if (NS_FAILED(rv)) { + return rv; + } + if (nsContentUtils::HtmlObjectContentTypeForMIMEType(mimeType) == + nsIObjectLoadingContent::TYPE_DOCUMENT) { + *aIsDocument = true; + return NS_OK; + } + *aIsDocument = false; + return NS_OK; +} + +nsresult NS_MakeAbsoluteURI(nsACString& result, const nsACString& spec, + nsIURI* baseURI) { + nsresult rv; + if (!baseURI) { + NS_WARNING("It doesn't make sense to not supply a base URI"); + result = spec; + rv = NS_OK; + } else if (spec.IsEmpty()) { + rv = baseURI->GetSpec(result); + } else { + rv = baseURI->Resolve(spec, result); + } + return rv; +} + +nsresult NS_MakeAbsoluteURI(char** result, const char* spec, nsIURI* baseURI) { + nsresult rv; + nsAutoCString resultBuf; + rv = NS_MakeAbsoluteURI(resultBuf, nsDependentCString(spec), baseURI); + if (NS_SUCCEEDED(rv)) { + *result = ToNewCString(resultBuf, mozilla::fallible); + if (!*result) rv = NS_ERROR_OUT_OF_MEMORY; + } + return rv; +} + +nsresult NS_MakeAbsoluteURI(nsAString& result, const nsAString& spec, + nsIURI* baseURI) { + nsresult rv; + if (!baseURI) { + NS_WARNING("It doesn't make sense to not supply a base URI"); + result = spec; + rv = NS_OK; + } else { + nsAutoCString resultBuf; + if (spec.IsEmpty()) { + rv = baseURI->GetSpec(resultBuf); + } else { + rv = baseURI->Resolve(NS_ConvertUTF16toUTF8(spec), resultBuf); + } + if (NS_SUCCEEDED(rv)) CopyUTF8toUTF16(resultBuf, result); + } + return rv; +} + +int32_t NS_GetDefaultPort(const char* scheme, + nsIIOService* ioService /* = nullptr */) { + nsresult rv; + + // Getting the default port through the protocol handler previously had a lot + // of XPCOM overhead involved. We optimize the protocols that matter for Web + // pages (HTTP and HTTPS) by hardcoding their default ports here. + // + // XXX: This might not be necessary for performance anymore. + if (strncmp(scheme, "http", 4) == 0) { + if (scheme[4] == 's' && scheme[5] == '\0') { + return 443; + } + if (scheme[4] == '\0') { + return 80; + } + } + + nsCOMPtr<nsIIOService> grip; + net_EnsureIOService(&ioService, grip); + if (!ioService) return -1; + + int32_t port; + rv = ioService->GetDefaultPort(scheme, &port); + return NS_SUCCEEDED(rv) ? port : -1; +} + +/** + * This function is a helper function to apply the ToAscii conversion + * to a string + */ +bool NS_StringToACE(const nsACString& idn, nsACString& result) { + nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID); + if (!idnSrv) return false; + nsresult rv = idnSrv->ConvertUTF8toACE(idn, result); + return NS_SUCCEEDED(rv); +} + +int32_t NS_GetRealPort(nsIURI* aURI) { + int32_t port; + nsresult rv = aURI->GetPort(&port); + if (NS_FAILED(rv)) return -1; + + if (port != -1) return port; // explicitly specified + + // Otherwise, we have to get the default port from the protocol handler + + // Need the scheme first + nsAutoCString scheme; + rv = aURI->GetScheme(scheme); + if (NS_FAILED(rv)) return -1; + + return NS_GetDefaultPort(scheme.get()); +} + +nsresult NS_NewInputStreamChannelInternal( + nsIChannel** outChannel, nsIURI* aUri, + already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType, + const nsACString& aContentCharset, nsILoadInfo* aLoadInfo) { + nsresult rv; + nsCOMPtr<nsIInputStreamChannel> isc = + do_CreateInstance(NS_INPUTSTREAMCHANNEL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = isc->SetURI(aUri); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> stream = std::move(aStream); + rv = isc->SetContentStream(stream); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(isc, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aContentType.IsEmpty()) { + rv = channel->SetContentType(aContentType); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!aContentCharset.IsEmpty()) { + rv = channel->SetContentCharset(aContentCharset); + NS_ENSURE_SUCCESS(rv, rv); + } + + MOZ_ASSERT(aLoadInfo, "need a loadinfo to create a inputstreamchannel"); + channel->SetLoadInfo(aLoadInfo); + + // If we're sandboxed, make sure to clear any owner the channel + // might already have. + if (aLoadInfo && aLoadInfo->GetLoadingSandboxed()) { + channel->SetOwner(nullptr); + } + + channel.forget(outChannel); + return NS_OK; +} + +nsresult NS_NewInputStreamChannelInternal( + nsIChannel** outChannel, nsIURI* aUri, + already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType, + const nsACString& aContentCharset, nsINode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType) { + nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo( + aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags, + aContentPolicyType); + if (!loadInfo) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIInputStream> stream = std::move(aStream); + + return NS_NewInputStreamChannelInternal(outChannel, aUri, stream.forget(), + aContentType, aContentCharset, + loadInfo); +} + +nsresult NS_NewInputStreamChannel( + nsIChannel** outChannel, nsIURI* aUri, + already_AddRefed<nsIInputStream> aStream, nsIPrincipal* aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + const nsACString& aContentType /* = ""_ns */, + const nsACString& aContentCharset /* = ""_ns */) { + nsCOMPtr<nsIInputStream> stream = aStream; + return NS_NewInputStreamChannelInternal(outChannel, aUri, stream.forget(), + aContentType, aContentCharset, + nullptr, // aLoadingNode + aLoadingPrincipal, + nullptr, // aTriggeringPrincipal + aSecurityFlags, aContentPolicyType); +} + +nsresult NS_NewInputStreamChannelInternal(nsIChannel** outChannel, nsIURI* aUri, + const nsAString& aData, + const nsACString& aContentType, + nsILoadInfo* aLoadInfo, + bool aIsSrcdocChannel /* = false */) { + nsresult rv; + nsCOMPtr<nsIStringInputStream> stream; + stream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t len; + char* utf8Bytes = ToNewUTF8String(aData, &len); + rv = stream->AdoptData(utf8Bytes, len); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aUri, + stream.forget(), aContentType, + "UTF-8"_ns, aLoadInfo); + + NS_ENSURE_SUCCESS(rv, rv); + + if (aIsSrcdocChannel) { + nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(channel); + NS_ENSURE_TRUE(inStrmChan, NS_ERROR_FAILURE); + inStrmChan->SetSrcdocData(aData); + } + channel.forget(outChannel); + return NS_OK; +} + +nsresult NS_NewInputStreamChannelInternal( + nsIChannel** outChannel, nsIURI* aUri, const nsAString& aData, + const nsACString& aContentType, nsINode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + bool aIsSrcdocChannel /* = false */) { + nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo( + aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags, + aContentPolicyType); + return NS_NewInputStreamChannelInternal(outChannel, aUri, aData, aContentType, + loadInfo, aIsSrcdocChannel); +} + +nsresult NS_NewInputStreamChannel(nsIChannel** outChannel, nsIURI* aUri, + const nsAString& aData, + const nsACString& aContentType, + nsIPrincipal* aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + bool aIsSrcdocChannel /* = false */) { + return NS_NewInputStreamChannelInternal(outChannel, aUri, aData, aContentType, + nullptr, // aLoadingNode + aLoadingPrincipal, + nullptr, // aTriggeringPrincipal + aSecurityFlags, aContentPolicyType, + aIsSrcdocChannel); +} + +nsresult NS_NewInputStreamPump( + nsIInputStreamPump** aResult, already_AddRefed<nsIInputStream> aStream, + uint32_t aSegsize /* = 0 */, uint32_t aSegcount /* = 0 */, + bool aCloseWhenDone /* = false */, + nsISerialEventTarget* aMainThreadTarget /* = nullptr */) { + nsCOMPtr<nsIInputStream> stream = std::move(aStream); + + nsresult rv; + nsCOMPtr<nsIInputStreamPump> pump = + do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = pump->Init(stream, aSegsize, aSegcount, aCloseWhenDone, + aMainThreadTarget); + if (NS_SUCCEEDED(rv)) { + *aResult = nullptr; + pump.swap(*aResult); + } + } + return rv; +} + +nsresult NS_NewLoadGroup(nsILoadGroup** result, nsIRequestObserver* obs) { + nsresult rv; + nsCOMPtr<nsILoadGroup> group = + do_CreateInstance(NS_LOADGROUP_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = group->SetGroupObserver(obs); + if (NS_SUCCEEDED(rv)) { + *result = nullptr; + group.swap(*result); + } + } + return rv; +} + +bool NS_IsReasonableHTTPHeaderValue(const nsACString& aValue) { + return mozilla::net::nsHttp::IsReasonableHeaderValue(aValue); +} + +bool NS_IsValidHTTPToken(const nsACString& aToken) { + return mozilla::net::nsHttp::IsValidToken(aToken); +} + +void NS_TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest) { + mozilla::net::nsHttp::TrimHTTPWhitespace(aSource, aDest); +} + +nsresult NS_NewLoadGroup(nsILoadGroup** aResult, nsIPrincipal* aPrincipal) { + using mozilla::LoadContext; + nsresult rv; + + nsCOMPtr<nsILoadGroup> group = + do_CreateInstance(NS_LOADGROUP_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<LoadContext> loadContext = new LoadContext(aPrincipal); + rv = group->SetNotificationCallbacks(loadContext); + NS_ENSURE_SUCCESS(rv, rv); + + group.forget(aResult); + return rv; +} + +bool NS_LoadGroupMatchesPrincipal(nsILoadGroup* aLoadGroup, + nsIPrincipal* aPrincipal) { + if (!aPrincipal) { + return false; + } + + // If this is a null principal then the load group doesn't really matter. + // The principal will not be allowed to perform any actions that actually + // use the load group. Unconditionally treat null principals as a match. + if (aPrincipal->GetIsNullPrincipal()) { + return true; + } + + if (!aLoadGroup) { + return false; + } + + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(nullptr, aLoadGroup, NS_GET_IID(nsILoadContext), + getter_AddRefs(loadContext)); + NS_ENSURE_TRUE(loadContext, false); + + return true; +} + +nsresult NS_NewDownloader(nsIStreamListener** result, + nsIDownloadObserver* observer, + nsIFile* downloadLocation /* = nullptr */) { + nsresult rv; + nsCOMPtr<nsIDownloader> downloader = + do_CreateInstance(NS_DOWNLOADER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = downloader->Init(observer, downloadLocation); + if (NS_SUCCEEDED(rv)) { + downloader.forget(result); + } + } + return rv; +} + +nsresult NS_NewIncrementalStreamLoader( + nsIIncrementalStreamLoader** result, + nsIIncrementalStreamLoaderObserver* observer) { + nsresult rv; + nsCOMPtr<nsIIncrementalStreamLoader> loader = + do_CreateInstance(NS_INCREMENTALSTREAMLOADER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = loader->Init(observer); + if (NS_SUCCEEDED(rv)) { + *result = nullptr; + loader.swap(*result); + } + } + return rv; +} + +nsresult NS_NewStreamLoader( + nsIStreamLoader** result, nsIStreamLoaderObserver* observer, + nsIRequestObserver* requestObserver /* = nullptr */) { + nsresult rv; + nsCOMPtr<nsIStreamLoader> loader = + do_CreateInstance(NS_STREAMLOADER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = loader->Init(observer, requestObserver); + if (NS_SUCCEEDED(rv)) { + *result = nullptr; + loader.swap(*result); + } + } + return rv; +} + +nsresult NS_NewStreamLoaderInternal( + nsIStreamLoader** outStream, nsIURI* aUri, + nsIStreamLoaderObserver* aObserver, nsINode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */) { + nsCOMPtr<nsIChannel> channel; + nsresult rv = NS_NewChannelInternal( + getter_AddRefs(channel), aUri, aLoadingNode, aLoadingPrincipal, + nullptr, // aTriggeringPrincipal + Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, + aContentPolicyType, + nullptr, // nsICookieJarSettings + nullptr, // PerformanceStorage + aLoadGroup, aCallbacks, aLoadFlags); + + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewStreamLoader(outStream, aObserver); + NS_ENSURE_SUCCESS(rv, rv); + return channel->AsyncOpen(*outStream); +} + +nsresult NS_NewStreamLoader( + nsIStreamLoader** outStream, nsIURI* aUri, + nsIStreamLoaderObserver* aObserver, nsINode* aLoadingNode, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */) { + NS_ASSERTION(aLoadingNode, + "Can not create stream loader without a loading Node!"); + return NS_NewStreamLoaderInternal( + outStream, aUri, aObserver, aLoadingNode, aLoadingNode->NodePrincipal(), + aSecurityFlags, aContentPolicyType, aLoadGroup, aCallbacks, aLoadFlags); +} + +nsresult NS_NewStreamLoader( + nsIStreamLoader** outStream, nsIURI* aUri, + nsIStreamLoaderObserver* aObserver, nsIPrincipal* aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + nsILoadGroup* aLoadGroup /* = nullptr */, + nsIInterfaceRequestor* aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */) { + return NS_NewStreamLoaderInternal(outStream, aUri, aObserver, + nullptr, // aLoadingNode + aLoadingPrincipal, aSecurityFlags, + aContentPolicyType, aLoadGroup, aCallbacks, + aLoadFlags); +} + +nsresult NS_NewSyncStreamListener(nsIStreamListener** result, + nsIInputStream** stream) { + nsCOMPtr<nsISyncStreamListener> listener = new nsSyncStreamListener(); + nsresult rv = listener->GetInputStream(stream); + if (NS_SUCCEEDED(rv)) { + listener.forget(result); + } + return rv; +} + +nsresult NS_ImplementChannelOpen(nsIChannel* channel, nsIInputStream** result) { + nsCOMPtr<nsIStreamListener> listener; + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewSyncStreamListener(getter_AddRefs(listener), + getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = channel->AsyncOpen(listener); + NS_ENSURE_SUCCESS(rv, rv); + + uint64_t n; + // block until the initial response is received or an error occurs. + rv = stream->Available(&n); + NS_ENSURE_SUCCESS(rv, rv); + + *result = nullptr; + stream.swap(*result); + + return NS_OK; +} + +nsresult NS_NewRequestObserverProxy(nsIRequestObserver** result, + nsIRequestObserver* observer, + nsISupports* context) { + nsCOMPtr<nsIRequestObserverProxy> proxy = new nsRequestObserverProxy(); + nsresult rv = proxy->Init(observer, context); + if (NS_SUCCEEDED(rv)) { + proxy.forget(result); + } + return rv; +} + +nsresult NS_NewSimpleStreamListener( + nsIStreamListener** result, nsIOutputStream* sink, + nsIRequestObserver* observer /* = nullptr */) { + nsresult rv; + nsCOMPtr<nsISimpleStreamListener> listener = + do_CreateInstance(NS_SIMPLESTREAMLISTENER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = listener->Init(sink, observer); + if (NS_SUCCEEDED(rv)) { + listener.forget(result); + } + } + return rv; +} + +nsresult NS_CheckPortSafety(int32_t port, const char* scheme, + nsIIOService* ioService /* = nullptr */) { + nsresult rv; + nsCOMPtr<nsIIOService> grip; + rv = net_EnsureIOService(&ioService, grip); + if (ioService) { + bool allow; + rv = ioService->AllowPort(port, scheme, &allow); + if (NS_SUCCEEDED(rv) && !allow) { + NS_WARNING("port blocked"); + rv = NS_ERROR_PORT_ACCESS_NOT_ALLOWED; + } + } + return rv; +} + +nsresult NS_CheckPortSafety(nsIURI* uri) { + int32_t port; + nsresult rv = uri->GetPort(&port); + if (NS_FAILED(rv) || port == -1) { // port undefined or default-valued + return NS_OK; + } + nsAutoCString scheme; + uri->GetScheme(scheme); + return NS_CheckPortSafety(port, scheme.get()); +} + +nsresult NS_NewProxyInfo(const nsACString& type, const nsACString& host, + int32_t port, uint32_t flags, nsIProxyInfo** result) { + nsresult rv; + nsCOMPtr<nsIProtocolProxyService> pps = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = pps->NewProxyInfo(type, host, port, ""_ns, ""_ns, flags, UINT32_MAX, + nullptr, result); + } + return rv; +} + +nsresult NS_GetFileProtocolHandler(nsIFileProtocolHandler** result, + nsIIOService* ioService /* = nullptr */) { + nsresult rv; + nsCOMPtr<nsIIOService> grip; + rv = net_EnsureIOService(&ioService, grip); + if (ioService) { + nsCOMPtr<nsIProtocolHandler> handler; + rv = ioService->GetProtocolHandler("file", getter_AddRefs(handler)); + if (NS_SUCCEEDED(rv)) rv = CallQueryInterface(handler, result); + } + return rv; +} + +nsresult NS_GetFileFromURLSpec(const nsACString& inURL, nsIFile** result, + nsIIOService* ioService /* = nullptr */) { + nsresult rv; + nsCOMPtr<nsIFileProtocolHandler> fileHandler; + rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService); + if (NS_SUCCEEDED(rv)) rv = fileHandler->GetFileFromURLSpec(inURL, result); + return rv; +} + +nsresult NS_GetURLSpecFromFile(nsIFile* file, nsACString& url, + nsIIOService* ioService /* = nullptr */) { + nsresult rv; + nsCOMPtr<nsIFileProtocolHandler> fileHandler; + rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService); + if (NS_SUCCEEDED(rv)) rv = fileHandler->GetURLSpecFromFile(file, url); + return rv; +} + +nsresult NS_GetURLSpecFromActualFile(nsIFile* file, nsACString& url, + nsIIOService* ioService /* = nullptr */) { + nsresult rv; + nsCOMPtr<nsIFileProtocolHandler> fileHandler; + rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService); + if (NS_SUCCEEDED(rv)) rv = fileHandler->GetURLSpecFromActualFile(file, url); + return rv; +} + +nsresult NS_GetURLSpecFromDir(nsIFile* file, nsACString& url, + nsIIOService* ioService /* = nullptr */) { + nsresult rv; + nsCOMPtr<nsIFileProtocolHandler> fileHandler; + rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService); + if (NS_SUCCEEDED(rv)) rv = fileHandler->GetURLSpecFromDir(file, url); + return rv; +} + +void NS_GetReferrerFromChannel(nsIChannel* channel, nsIURI** referrer) { + *referrer = nullptr; + + if (nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(channel)) { + // We have to check for a property on a property bag because the + // referrer may be empty for security reasons (for example, when loading + // an http page with an https referrer). + nsresult rv; + nsCOMPtr<nsIURI> uri( + do_GetProperty(props, u"docshell.internalReferrer"_ns, &rv)); + if (NS_SUCCEEDED(rv)) { + uri.forget(referrer); + return; + } + } + + // if that didn't work, we can still try to get the referrer from the + // nsIHttpChannel (if we can QI to it) + nsCOMPtr<nsIHttpChannel> chan(do_QueryInterface(channel)); + if (!chan) { + return; + } + + nsCOMPtr<nsIReferrerInfo> referrerInfo = chan->GetReferrerInfo(); + if (!referrerInfo) { + return; + } + + referrerInfo->GetOriginalReferrer(referrer); +} + +already_AddRefed<nsINetUtil> do_GetNetUtil(nsresult* error /* = 0 */) { + nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service(); + nsCOMPtr<nsINetUtil> util; + if (io) util = do_QueryInterface(io); + + if (error) *error = !!util ? NS_OK : NS_ERROR_FAILURE; + return util.forget(); +} + +nsresult NS_ParseRequestContentType(const nsACString& rawContentType, + nsCString& contentType, + nsCString& contentCharset) { + // contentCharset is left untouched if not present in rawContentType + nsresult rv; + nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCString charset; + bool hadCharset; + rv = util->ParseRequestContentType(rawContentType, charset, &hadCharset, + contentType); + if (NS_SUCCEEDED(rv) && hadCharset) contentCharset = charset; + return rv; +} + +nsresult NS_ParseResponseContentType(const nsACString& rawContentType, + nsCString& contentType, + nsCString& contentCharset) { + // contentCharset is left untouched if not present in rawContentType + nsresult rv; + nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCString charset; + bool hadCharset; + rv = util->ParseResponseContentType(rawContentType, charset, &hadCharset, + contentType); + if (NS_SUCCEEDED(rv) && hadCharset) contentCharset = charset; + return rv; +} + +nsresult NS_ExtractCharsetFromContentType(const nsACString& rawContentType, + nsCString& contentCharset, + bool* hadCharset, + int32_t* charsetStart, + int32_t* charsetEnd) { + // contentCharset is left untouched if not present in rawContentType + nsresult rv; + nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + return util->ExtractCharsetFromContentType( + rawContentType, contentCharset, charsetStart, charsetEnd, hadCharset); +} + +nsresult NS_NewAtomicFileOutputStream(nsIOutputStream** result, nsIFile* file, + int32_t ioFlags /* = -1 */, + int32_t perm /* = -1 */, + int32_t behaviorFlags /* = 0 */) { + nsresult rv; + nsCOMPtr<nsIFileOutputStream> out = + do_CreateInstance(NS_ATOMICLOCALFILEOUTPUTSTREAM_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = out->Init(file, ioFlags, perm, behaviorFlags); + if (NS_SUCCEEDED(rv)) out.forget(result); + } + return rv; +} + +nsresult NS_NewSafeLocalFileOutputStream(nsIOutputStream** result, + nsIFile* file, + int32_t ioFlags /* = -1 */, + int32_t perm /* = -1 */, + int32_t behaviorFlags /* = 0 */) { + nsresult rv; + nsCOMPtr<nsIFileOutputStream> out = + do_CreateInstance(NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = out->Init(file, ioFlags, perm, behaviorFlags); + if (NS_SUCCEEDED(rv)) out.forget(result); + } + return rv; +} + +nsresult NS_NewLocalFileRandomAccessStream(nsIRandomAccessStream** result, + nsIFile* file, + int32_t ioFlags /* = -1 */, + int32_t perm /* = -1 */, + int32_t behaviorFlags /* = 0 */) { + nsCOMPtr<nsIFileRandomAccessStream> stream = new nsFileRandomAccessStream(); + nsresult rv = stream->Init(file, ioFlags, perm, behaviorFlags); + if (NS_SUCCEEDED(rv)) { + stream.forget(result); + } + return rv; +} + +mozilla::Result<nsCOMPtr<nsIRandomAccessStream>, nsresult> +NS_NewLocalFileRandomAccessStream(nsIFile* file, int32_t ioFlags /* = -1 */, + int32_t perm /* = -1 */, + int32_t behaviorFlags /* = 0 */) { + nsCOMPtr<nsIRandomAccessStream> stream; + const nsresult rv = NS_NewLocalFileRandomAccessStream( + getter_AddRefs(stream), file, ioFlags, perm, behaviorFlags); + if (NS_SUCCEEDED(rv)) { + return stream; + } + return Err(rv); +} + +nsresult NS_NewBufferedOutputStream( + nsIOutputStream** aResult, already_AddRefed<nsIOutputStream> aOutputStream, + uint32_t aBufferSize) { + nsCOMPtr<nsIOutputStream> outputStream = std::move(aOutputStream); + + nsresult rv; + nsCOMPtr<nsIBufferedOutputStream> out = + do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = out->Init(outputStream, aBufferSize); + if (NS_SUCCEEDED(rv)) { + out.forget(aResult); + } + } + return rv; +} + +[[nodiscard]] nsresult NS_NewBufferedInputStream( + nsIInputStream** aResult, already_AddRefed<nsIInputStream> aInputStream, + uint32_t aBufferSize) { + nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream); + + nsCOMPtr<nsIBufferedInputStream> in; + nsresult rv = nsBufferedInputStream::Create( + NS_GET_IID(nsIBufferedInputStream), getter_AddRefs(in)); + if (NS_SUCCEEDED(rv)) { + rv = in->Init(inputStream, aBufferSize); + if (NS_SUCCEEDED(rv)) { + *aResult = static_cast<nsBufferedInputStream*>(in.get()) + ->GetInputStream() + .take(); + } + } + return rv; +} + +Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewBufferedInputStream( + already_AddRefed<nsIInputStream> aInputStream, uint32_t aBufferSize) { + nsCOMPtr<nsIInputStream> stream; + const nsresult rv = NS_NewBufferedInputStream( + getter_AddRefs(stream), std::move(aInputStream), aBufferSize); + if (NS_SUCCEEDED(rv)) { + return stream; + } + return Err(rv); +} + +namespace { + +#define BUFFER_SIZE 8192 + +class BufferWriter final : public nsIInputStreamCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + BufferWriter(nsIInputStream* aInputStream, void* aBuffer, int64_t aCount) + : mMonitor("BufferWriter.mMonitor"), + mInputStream(aInputStream), + mBuffer(aBuffer), + mCount(aCount), + mWrittenData(0), + mBufferType(aBuffer ? eExternal : eInternal), + mBufferSize(0) { + MOZ_ASSERT(aInputStream); + MOZ_ASSERT(aCount == -1 || aCount > 0); + MOZ_ASSERT_IF(mBuffer, aCount > 0); + } + + nsresult Write() { + NS_ASSERT_OWNINGTHREAD(BufferWriter); + + // Let's make the inputStream buffered if it's not. + if (!NS_InputStreamIsBuffered(mInputStream)) { + nsCOMPtr<nsIInputStream> bufferedStream; + nsresult rv = NS_NewBufferedInputStream( + getter_AddRefs(bufferedStream), mInputStream.forget(), BUFFER_SIZE); + NS_ENSURE_SUCCESS(rv, rv); + + mInputStream = bufferedStream; + } + + mAsyncInputStream = do_QueryInterface(mInputStream); + + if (!mAsyncInputStream) { + return WriteSync(); + } + + // Let's use mAsyncInputStream only. + mInputStream = nullptr; + + return WriteAsync(); + } + + uint64_t WrittenData() const { + NS_ASSERT_OWNINGTHREAD(BufferWriter); + return mWrittenData; + } + + void* StealBuffer() { + NS_ASSERT_OWNINGTHREAD(BufferWriter); + MOZ_ASSERT(mBufferType == eInternal); + + void* buffer = mBuffer; + + mBuffer = nullptr; + mBufferSize = 0; + + return buffer; + } + + private: + ~BufferWriter() { + if (mBuffer && mBufferType == eInternal) { + free(mBuffer); + } + + if (mTaskQueue) { + mTaskQueue->BeginShutdown(); + } + } + + nsresult WriteSync() { + NS_ASSERT_OWNINGTHREAD(BufferWriter); + + uint64_t length = (uint64_t)mCount; + + if (mCount == -1) { + nsresult rv = mInputStream->Available(&length); + NS_ENSURE_SUCCESS(rv, rv); + + if (length == 0) { + // nothing to read. + return NS_OK; + } + } + + if (mBufferType == eInternal) { + mBuffer = malloc(length); + if (NS_WARN_IF(!mBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + uint32_t writtenData; + nsresult rv = mInputStream->ReadSegments(NS_CopySegmentToBuffer, mBuffer, + length, &writtenData); + NS_ENSURE_SUCCESS(rv, rv); + + mWrittenData = writtenData; + return NS_OK; + } + + nsresult WriteAsync() { + NS_ASSERT_OWNINGTHREAD(BufferWriter); + + if (mCount > 0 && mBufferType == eInternal) { + mBuffer = malloc(mCount); + if (NS_WARN_IF(!mBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + while (true) { + if (mCount == -1 && !MaybeExpandBufferSize()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint64_t offset = mWrittenData; + uint64_t length = mCount == -1 ? BUFFER_SIZE : mCount; + + // Let's try to read data directly. + uint32_t writtenData; + nsresult rv = mAsyncInputStream->ReadSegments( + NS_CopySegmentToBuffer, static_cast<char*>(mBuffer) + offset, length, + &writtenData); + + // Operation completed. Nothing more to read. + if (NS_SUCCEEDED(rv) && writtenData == 0) { + return NS_OK; + } + + // If we succeeded, let's try to read again. + if (NS_SUCCEEDED(rv)) { + mWrittenData += writtenData; + if (mCount != -1) { + MOZ_ASSERT(mCount >= writtenData); + mCount -= writtenData; + + // Is this the end of the reading? + if (mCount == 0) { + return NS_OK; + } + } + + continue; + } + + // Async wait... + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + rv = MaybeCreateTaskQueue(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MonitorAutoLock lock(mMonitor); + + rv = mAsyncInputStream->AsyncWait(this, 0, length, mTaskQueue); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + lock.Wait(); + continue; + } + + // Otherwise, let's propagate the error. + return rv; + } + + MOZ_ASSERT_UNREACHABLE("We should not be here"); + return NS_ERROR_FAILURE; + } + + nsresult MaybeCreateTaskQueue() { + NS_ASSERT_OWNINGTHREAD(BufferWriter); + + if (!mTaskQueue) { + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + if (!target) { + return NS_ERROR_FAILURE; + } + + mTaskQueue = TaskQueue::Create(target.forget(), "nsNetUtil:BufferWriter"); + } + + return NS_OK; + } + + NS_IMETHOD + OnInputStreamReady(nsIAsyncInputStream* aStream) override { + MOZ_ASSERT(!NS_IsMainThread()); + + // We have something to read. Let's unlock the main-thread. + MonitorAutoLock lock(mMonitor); + lock.Notify(); + return NS_OK; + } + + bool MaybeExpandBufferSize() { + NS_ASSERT_OWNINGTHREAD(BufferWriter); + + MOZ_ASSERT(mCount == -1); + + if (mBufferSize >= mWrittenData + BUFFER_SIZE) { + // The buffer is big enough. + return true; + } + + CheckedUint32 bufferSize = + std::max<uint32_t>(static_cast<uint32_t>(mWrittenData), BUFFER_SIZE); + while (bufferSize.isValid() && + bufferSize.value() < mWrittenData + BUFFER_SIZE) { + bufferSize *= 2; + } + + if (!bufferSize.isValid()) { + return false; + } + + void* buffer = realloc(mBuffer, bufferSize.value()); + if (!buffer) { + return false; + } + + mBuffer = buffer; + mBufferSize = bufferSize.value(); + return true; + } + + // All the members of this class are touched on the owning thread only. The + // monitor is only used to communicate when there is more data to read. + Monitor mMonitor MOZ_UNANNOTATED; + + nsCOMPtr<nsIInputStream> mInputStream; + nsCOMPtr<nsIAsyncInputStream> mAsyncInputStream; + + RefPtr<TaskQueue> mTaskQueue; + + void* mBuffer; + int64_t mCount; + uint64_t mWrittenData; + + enum { + // The buffer is allocated internally and this object must release it + // in the DTOR if not stolen. The buffer can be reallocated. + eInternal, + + // The buffer is not owned by this object and it cannot be reallocated. + eExternal, + } mBufferType; + + // The following set if needed for the async read. + uint64_t mBufferSize; +}; + +NS_IMPL_ISUPPORTS(BufferWriter, nsIInputStreamCallback) + +} // anonymous namespace + +nsresult NS_ReadInputStreamToBuffer(nsIInputStream* aInputStream, void** aDest, + int64_t aCount, uint64_t* aWritten) { + MOZ_ASSERT(aInputStream); + MOZ_ASSERT(aCount >= -1); + + uint64_t dummyWritten; + if (!aWritten) { + aWritten = &dummyWritten; + } + + if (aCount == 0) { + *aWritten = 0; + return NS_OK; + } + + // This will take care of allocating and reallocating aDest. + RefPtr<BufferWriter> writer = new BufferWriter(aInputStream, *aDest, aCount); + + nsresult rv = writer->Write(); + NS_ENSURE_SUCCESS(rv, rv); + + *aWritten = writer->WrittenData(); + + if (!*aDest) { + *aDest = writer->StealBuffer(); + } + + return NS_OK; +} + +nsresult NS_ReadInputStreamToString(nsIInputStream* aInputStream, + nsACString& aDest, int64_t aCount, + uint64_t* aWritten) { + uint64_t dummyWritten; + if (!aWritten) { + aWritten = &dummyWritten; + } + + // Nothing to do if aCount is 0. + if (aCount == 0) { + aDest.Truncate(); + *aWritten = 0; + return NS_OK; + } + + // If we have the size, we can pre-allocate the buffer. + if (aCount > 0) { + if (NS_WARN_IF(aCount >= INT32_MAX) || + NS_WARN_IF(!aDest.SetLength(aCount, mozilla::fallible))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + void* dest = aDest.BeginWriting(); + nsresult rv = + NS_ReadInputStreamToBuffer(aInputStream, &dest, aCount, aWritten); + NS_ENSURE_SUCCESS(rv, rv); + + if ((uint64_t)aCount > *aWritten) { + aDest.Truncate(*aWritten); + } + + return NS_OK; + } + + // If the size is unknown, BufferWriter will allocate the buffer. + void* dest = nullptr; + nsresult rv = + NS_ReadInputStreamToBuffer(aInputStream, &dest, aCount, aWritten); + MOZ_ASSERT_IF(NS_FAILED(rv), dest == nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + if (!dest) { + MOZ_ASSERT(*aWritten == 0); + aDest.Truncate(); + return NS_OK; + } + + aDest.Adopt(reinterpret_cast<char*>(dest), *aWritten); + return NS_OK; +} + +nsresult NS_NewURI(nsIURI** result, const nsACString& spec, + NotNull<const Encoding*> encoding, + nsIURI* baseURI /* = nullptr */) { + nsAutoCString charset; + encoding->Name(charset); + return NS_NewURI(result, spec, charset.get(), baseURI); +} + +nsresult NS_NewURI(nsIURI** result, const nsAString& aSpec, + const char* charset /* = nullptr */, + nsIURI* baseURI /* = nullptr */) { + nsAutoCString spec; + if (!AppendUTF16toUTF8(aSpec, spec, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_NewURI(result, spec, charset, baseURI); +} + +nsresult NS_NewURI(nsIURI** result, const nsAString& aSpec, + NotNull<const Encoding*> encoding, + nsIURI* baseURI /* = nullptr */) { + nsAutoCString spec; + if (!AppendUTF16toUTF8(aSpec, spec, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_NewURI(result, spec, encoding, baseURI); +} + +nsresult NS_NewURI(nsIURI** result, const char* spec, + nsIURI* baseURI /* = nullptr */) { + return NS_NewURI(result, nsDependentCString(spec), nullptr, baseURI); +} + +static nsresult NewStandardURI(const nsACString& aSpec, const char* aCharset, + nsIURI* aBaseURI, int32_t aDefaultPort, + nsIURI** aURI) { + return NS_MutateURI(new nsStandardURL::Mutator()) + .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_AUTHORITY, + aDefaultPort, aSpec, aCharset, aBaseURI, nullptr) + .Finalize(aURI); +} + +nsresult NS_GetSpecWithNSURLEncoding(nsACString& aResult, + const nsACString& aSpec) { + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURIWithNSURLEncoding(getter_AddRefs(uri), aSpec); + NS_ENSURE_SUCCESS(rv, rv); + return uri->GetAsciiSpec(aResult); +} + +nsresult NS_NewURIWithNSURLEncoding(nsIURI** aResult, const nsACString& aSpec) { + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpec); + NS_ENSURE_SUCCESS(rv, rv); + + // Escape the ref portion of the URL. NSURL is more strict about which + // characters in the URL must be % encoded. For example, an unescaped '#' + // to indicate the beginning of the ref component is accepted by NSURL, but + // '#' characters in the ref must be escaped. Also adds encoding for other + // characters not accepted by NSURL in the ref such as '{', '|', '}', and '^'. + // The ref returned from GetRef() does not include the leading '#'. + nsAutoCString ref, escapedRef; + if (NS_SUCCEEDED(uri->GetRef(ref)) && !ref.IsEmpty()) { + if (!NS_Escape(ref, escapedRef, url_NSURLRef)) { + return NS_ERROR_INVALID_ARG; + } + rv = NS_MutateURI(uri).SetRef(escapedRef).Finalize(uri); + NS_ENSURE_SUCCESS(rv, rv); + } + + uri.forget(aResult); + return NS_OK; +} + +extern MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount; + +template <typename T> +class TlsAutoIncrement { + public: + explicit TlsAutoIncrement(T& var) : mVar(var) { + mValue = mVar.get(); + mVar.set(mValue + 1); + } + ~TlsAutoIncrement() { + typename T::Type value = mVar.get(); + MOZ_ASSERT(value == mValue + 1); + mVar.set(value - 1); + } + + typename T::Type value() { return mValue; } + + private: + typename T::Type mValue; + T& mVar; +}; + +nsresult NS_NewURI(nsIURI** aURI, const nsACString& aSpec, + const char* aCharset /* = nullptr */, + nsIURI* aBaseURI /* = nullptr */) { + TlsAutoIncrement<decltype(gTlsURLRecursionCount)> inc(gTlsURLRecursionCount); + if (inc.value() >= MAX_RECURSION_COUNT) { + return NS_ERROR_MALFORMED_URI; + } + + nsCOMPtr<nsIIOService> ioService = do_GetIOService(); + if (!ioService) { + // Individual protocol handlers unfortunately rely on the ioservice, let's + // return an error here instead of causing unpredictable crashes later. + return NS_ERROR_NOT_AVAILABLE; + } + + if (StaticPrefs::network_url_max_length() && + aSpec.Length() > StaticPrefs::network_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + nsAutoCString scheme; + nsresult rv = net_ExtractURLScheme(aSpec, scheme); + if (NS_FAILED(rv)) { + // then aSpec is relative + if (!aBaseURI) { + return NS_ERROR_MALFORMED_URI; + } + + if (!aSpec.IsEmpty() && aSpec[0] == '#') { + // Looks like a reference instead of a fully-specified URI. + // --> initialize |uri| as a clone of |aBaseURI|, with ref appended. + return NS_GetURIWithNewRef(aBaseURI, aSpec, aURI); + } + + rv = aBaseURI->GetScheme(scheme); + if (NS_FAILED(rv)) return rv; + } + + if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("ws")) { + return NewStandardURI(aSpec, aCharset, aBaseURI, NS_HTTP_DEFAULT_PORT, + aURI); + } + if (scheme.EqualsLiteral("https") || scheme.EqualsLiteral("wss")) { + return NewStandardURI(aSpec, aCharset, aBaseURI, NS_HTTPS_DEFAULT_PORT, + aURI); + } + if (scheme.EqualsLiteral("ftp")) { + return NewStandardURI(aSpec, aCharset, aBaseURI, 21, aURI); + } + + if (scheme.EqualsLiteral("file")) { + return NS_MutateURI(new nsStandardURL::Mutator()) + .Apply(&nsIFileURLMutator::MarkFileURL) + .Apply(&nsIStandardURLMutator::Init, + nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, aSpec, aCharset, + aBaseURI, nullptr) + .Finalize(aURI); + } + + if (scheme.EqualsLiteral("data")) { + return nsDataHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI); + } + + if (scheme.EqualsLiteral("moz-safe-about") || + scheme.EqualsLiteral("page-icon") || scheme.EqualsLiteral("moz") || + scheme.EqualsLiteral("cached-favicon")) { + return NS_MutateURI(new nsSimpleURI::Mutator()) + .SetSpec(aSpec) + .Finalize(aURI); + } + + if (scheme.EqualsLiteral("chrome")) { + return nsChromeProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI, + aURI); + } + + if (scheme.EqualsLiteral("javascript")) { + return nsJSProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI); + } + + if (scheme.EqualsLiteral("blob")) { + return BlobURLProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI, + aURI); + } + + if (scheme.EqualsLiteral("view-source")) { + return nsViewSourceHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI); + } + + if (scheme.EqualsLiteral("resource")) { + RefPtr<nsResProtocolHandler> handler = nsResProtocolHandler::GetSingleton(); + if (!handler) { + return NS_ERROR_NOT_AVAILABLE; + } + return handler->NewURI(aSpec, aCharset, aBaseURI, aURI); + } + + if (scheme.EqualsLiteral("indexeddb") || scheme.EqualsLiteral("uuid")) { + return NS_MutateURI(new nsStandardURL::Mutator()) + .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_AUTHORITY, + 0, aSpec, aCharset, aBaseURI, nullptr) + .Finalize(aURI); + } + + if (scheme.EqualsLiteral("moz-extension")) { + RefPtr<mozilla::net::ExtensionProtocolHandler> handler = + mozilla::net::ExtensionProtocolHandler::GetSingleton(); + if (!handler) { + return NS_ERROR_NOT_AVAILABLE; + } + return handler->NewURI(aSpec, aCharset, aBaseURI, aURI); + } + + if (scheme.EqualsLiteral("moz-page-thumb")) { + // The moz-page-thumb service runs JS to resolve a URI to a + // storage location, so this should only ever run on the main + // thread. + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<mozilla::net::PageThumbProtocolHandler> handler = + mozilla::net::PageThumbProtocolHandler::GetSingleton(); + if (!handler) { + return NS_ERROR_NOT_AVAILABLE; + } + return handler->NewURI(aSpec, aCharset, aBaseURI, aURI); + } + + if (scheme.EqualsLiteral("about")) { + return nsAboutProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI, + aURI); + } + + if (scheme.EqualsLiteral("jar")) { + return NS_MutateURI(new nsJARURI::Mutator()) + .Apply(&nsIJARURIMutator::SetSpecBaseCharset, aSpec, aBaseURI, aCharset) + .Finalize(aURI); + } + + if (scheme.EqualsLiteral("moz-icon")) { + return NS_MutateURI(new nsMozIconURI::Mutator()) + .SetSpec(aSpec) + .Finalize(aURI); + } + +#ifdef MOZ_WIDGET_GTK + if (scheme.EqualsLiteral("smb") || scheme.EqualsLiteral("sftp")) { + return NS_MutateURI(new nsStandardURL::Mutator()) + .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD, + -1, aSpec, aCharset, aBaseURI, nullptr) + .Finalize(aURI); + } +#endif + + if (scheme.EqualsLiteral("android")) { + return NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD, + -1, aSpec, aCharset, aBaseURI, nullptr) + .Finalize(aURI); + } + + // web-extensions can add custom protocol implementations with standard URLs + // that have notion of hostname, authority and relative URLs. Below we + // manually check agains set of known protocols schemes until more general + // solution is in place (See Bug 1569733) + if (!StaticPrefs::network_url_useDefaultURI()) { + if (scheme.EqualsLiteral("ssh")) { + return NewStandardURI(aSpec, aCharset, aBaseURI, 22, aURI); + } + + if (scheme.EqualsLiteral("dweb") || scheme.EqualsLiteral("dat") || + scheme.EqualsLiteral("ipfs") || scheme.EqualsLiteral("ipns") || + scheme.EqualsLiteral("ssb") || scheme.EqualsLiteral("wtp")) { + return NewStandardURI(aSpec, aCharset, aBaseURI, -1, aURI); + } + } + +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) + rv = NS_NewMailnewsURI(aURI, aSpec, aCharset, aBaseURI); + if (rv != NS_ERROR_UNKNOWN_PROTOCOL) { + return rv; + } +#endif + + if (aBaseURI) { + nsAutoCString newSpec; + rv = aBaseURI->Resolve(aSpec, newSpec); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString newScheme; + rv = net_ExtractURLScheme(newSpec, newScheme); + if (NS_SUCCEEDED(rv)) { + // The scheme shouldn't really change at this point. + MOZ_DIAGNOSTIC_ASSERT(newScheme == scheme); + } + + if (StaticPrefs::network_url_useDefaultURI()) { + return NS_MutateURI(new DefaultURI::Mutator()) + .SetSpec(newSpec) + .Finalize(aURI); + } + + return NS_MutateURI(new nsSimpleURI::Mutator()) + .SetSpec(newSpec) + .Finalize(aURI); + } + + if (StaticPrefs::network_url_useDefaultURI()) { + return NS_MutateURI(new DefaultURI::Mutator()) + .SetSpec(aSpec) + .Finalize(aURI); + } + + // Falls back to external protocol handler. + return NS_MutateURI(new nsSimpleURI::Mutator()).SetSpec(aSpec).Finalize(aURI); +} + +nsresult NS_GetSanitizedURIStringFromURI(nsIURI* aUri, + nsAString& aSanitizedSpec) { + aSanitizedSpec.Truncate(); + + nsCOMPtr<nsISensitiveInfoHiddenURI> safeUri = do_QueryInterface(aUri); + nsAutoCString cSpec; + nsresult rv; + if (safeUri) { + rv = safeUri->GetSensitiveInfoHiddenSpec(cSpec); + } else { + rv = aUri->GetSpec(cSpec); + } + + if (NS_SUCCEEDED(rv)) { + aSanitizedSpec.Assign(NS_ConvertUTF8toUTF16(cSpec)); + } + return rv; +} + +nsresult NS_LoadPersistentPropertiesFromURISpec( + nsIPersistentProperties** outResult, const nsACString& aSpec) { + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIInputStream> in; + rv = channel->Open(getter_AddRefs(in)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPersistentProperties> properties = new nsPersistentProperties(); + rv = properties->Load(in); + NS_ENSURE_SUCCESS(rv, rv); + + properties.swap(*outResult); + return NS_OK; +} + +bool NS_UsePrivateBrowsing(nsIChannel* channel) { + OriginAttributes attrs; + bool result = StoragePrincipalHelper::GetOriginAttributes( + channel, attrs, StoragePrincipalHelper::eRegularPrincipal); + NS_ENSURE_TRUE(result, result); + return attrs.mPrivateBrowsingId > 0; +} + +bool NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport) { + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + // TYPE_DOCUMENT loads have a null LoadingPrincipal and can not be cross + // origin. + if (!loadInfo->GetLoadingPrincipal()) { + return false; + } + + // Always treat tainted channels as cross-origin. + if (loadInfo->GetTainting() != LoadTainting::Basic) { + return true; + } + + nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->GetLoadingPrincipal(); + uint32_t mode = loadInfo->GetSecurityMode(); + bool dataInherits = + mode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT || + mode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT || + mode == nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT; + + bool aboutBlankInherits = dataInherits && loadInfo->GetAboutBlankInherits(); + + uint64_t innerWindowID = loadInfo->GetInnerWindowID(); + + for (nsIRedirectHistoryEntry* redirectHistoryEntry : + loadInfo->RedirectChain()) { + nsCOMPtr<nsIPrincipal> principal; + redirectHistoryEntry->GetPrincipal(getter_AddRefs(principal)); + if (!principal) { + return true; + } + + nsCOMPtr<nsIURI> uri; + auto* basePrin = BasePrincipal::Cast(principal); + basePrin->GetURI(getter_AddRefs(uri)); + if (!uri) { + return true; + } + + if (aboutBlankInherits && NS_IsAboutBlank(uri)) { + continue; + } + + nsresult res; + if (aReport) { + res = loadingPrincipal->CheckMayLoadWithReporting(uri, dataInherits, + innerWindowID); + } else { + res = loadingPrincipal->CheckMayLoad(uri, dataInherits); + } + if (NS_FAILED(res)) { + return true; + } + } + + nsCOMPtr<nsIURI> uri; + NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + if (!uri) { + return true; + } + + if (aboutBlankInherits && NS_IsAboutBlank(uri)) { + return false; + } + + nsresult res; + if (aReport) { + res = loadingPrincipal->CheckMayLoadWithReporting(uri, dataInherits, + innerWindowID); + } else { + res = loadingPrincipal->CheckMayLoad(uri, dataInherits); + } + + return NS_FAILED(res); +} + +bool NS_IsSafeMethodNav(nsIChannel* aChannel) { + RefPtr<HttpBaseChannel> baseChan = do_QueryObject(aChannel); + if (!baseChan) { + return false; + } + nsHttpRequestHead* requestHead = baseChan->GetRequestHead(); + if (!requestHead) { + return false; + } + return requestHead->IsSafeMethod(); +} + +void NS_WrapAuthPrompt(nsIAuthPrompt* aAuthPrompt, + nsIAuthPrompt2** aAuthPrompt2) { + nsCOMPtr<nsIAuthPromptAdapterFactory> factory = + do_GetService(NS_AUTHPROMPT_ADAPTER_FACTORY_CONTRACTID); + if (!factory) return; + + NS_WARNING("Using deprecated nsIAuthPrompt"); + factory->CreateAdapter(aAuthPrompt, aAuthPrompt2); +} + +void NS_QueryAuthPrompt2(nsIInterfaceRequestor* aCallbacks, + nsIAuthPrompt2** aAuthPrompt) { + CallGetInterface(aCallbacks, aAuthPrompt); + if (*aAuthPrompt) return; + + // Maybe only nsIAuthPrompt is provided and we have to wrap it. + nsCOMPtr<nsIAuthPrompt> prompt(do_GetInterface(aCallbacks)); + if (!prompt) return; + + NS_WrapAuthPrompt(prompt, aAuthPrompt); +} + +void NS_QueryAuthPrompt2(nsIChannel* aChannel, nsIAuthPrompt2** aAuthPrompt) { + *aAuthPrompt = nullptr; + + // We want to use any auth prompt we can find on the channel's callbacks, + // and if that fails use the loadgroup's prompt (if any) + // Therefore, we can't just use NS_QueryNotificationCallbacks, because + // that would prefer a loadgroup's nsIAuthPrompt2 over a channel's + // nsIAuthPrompt. + nsCOMPtr<nsIInterfaceRequestor> callbacks; + aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) { + NS_QueryAuthPrompt2(callbacks, aAuthPrompt); + if (*aAuthPrompt) return; + } + + nsCOMPtr<nsILoadGroup> group; + aChannel->GetLoadGroup(getter_AddRefs(group)); + if (!group) return; + + group->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (!callbacks) return; + NS_QueryAuthPrompt2(callbacks, aAuthPrompt); +} + +nsresult NS_NewNotificationCallbacksAggregation( + nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup, + nsIEventTarget* target, nsIInterfaceRequestor** result) { + nsCOMPtr<nsIInterfaceRequestor> cbs; + if (loadGroup) loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs)); + return NS_NewInterfaceRequestorAggregation(callbacks, cbs, target, result); +} + +nsresult NS_NewNotificationCallbacksAggregation( + nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup, + nsIInterfaceRequestor** result) { + return NS_NewNotificationCallbacksAggregation(callbacks, loadGroup, nullptr, + result); +} + +nsresult NS_DoImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result) { + MOZ_ASSERT(nestedURI, "Must have a nested URI!"); + MOZ_ASSERT(!*result, "Must have null *result"); + + nsCOMPtr<nsIURI> inner; + nsresult rv = nestedURI->GetInnerURI(getter_AddRefs(inner)); + NS_ENSURE_SUCCESS(rv, rv); + + // We may need to loop here until we reach the innermost + // URI. + nsCOMPtr<nsINestedURI> nestedInner(do_QueryInterface(inner)); + while (nestedInner) { + rv = nestedInner->GetInnerURI(getter_AddRefs(inner)); + NS_ENSURE_SUCCESS(rv, rv); + nestedInner = do_QueryInterface(inner); + } + + // Found the innermost one if we reach here. + inner.swap(*result); + + return rv; +} + +nsresult NS_ImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result) { + // Make it safe to use swap() + *result = nullptr; + + return NS_DoImplGetInnermostURI(nestedURI, result); +} + +already_AddRefed<nsIURI> NS_GetInnermostURI(nsIURI* aURI) { + MOZ_ASSERT(aURI, "Must have URI"); + + nsCOMPtr<nsIURI> uri = aURI; + + nsCOMPtr<nsINestedURI> nestedURI(do_QueryInterface(uri)); + if (!nestedURI) { + return uri.forget(); + } + + nsresult rv = nestedURI->GetInnermostURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return uri.forget(); +} + +nsresult NS_GetFinalChannelURI(nsIChannel* channel, nsIURI** uri) { + *uri = nullptr; + + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + nsCOMPtr<nsIURI> resultPrincipalURI; + loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI)); + if (resultPrincipalURI) { + resultPrincipalURI.forget(uri); + return NS_OK; + } + return channel->GetOriginalURI(uri); +} + +nsresult NS_URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result) { + nsresult rv; + nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + return util->URIChainHasFlags(uri, flags, result); +} + +uint32_t NS_SecurityHashURI(nsIURI* aURI) { + nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI); + + nsAutoCString scheme; + uint32_t schemeHash = 0; + if (NS_SUCCEEDED(baseURI->GetScheme(scheme))) { + schemeHash = mozilla::HashString(scheme); + } + + // TODO figure out how to hash file:// URIs + if (scheme.EqualsLiteral("file")) return schemeHash; // sad face + +#if IS_ORIGIN_IS_FULL_SPEC_DEFINED + bool hasFlag; + if (NS_FAILED(NS_URIChainHasFlags( + baseURI, nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, &hasFlag)) || + hasFlag) { + nsAutoCString spec; + uint32_t specHash; + nsresult res = baseURI->GetSpec(spec); + if (NS_SUCCEEDED(res)) + specHash = mozilla::HashString(spec); + else + specHash = static_cast<uint32_t>(res); + return specHash; + } +#endif + + nsAutoCString host; + uint32_t hostHash = 0; + if (NS_SUCCEEDED(baseURI->GetAsciiHost(host))) { + hostHash = mozilla::HashString(host); + } + + return mozilla::AddToHash(schemeHash, hostHash, NS_GetRealPort(baseURI)); +} + +bool NS_SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI, + bool aStrictFileOriginPolicy) { + nsresult rv; + + // Note that this is not an Equals() test on purpose -- for URIs that don't + // support host/port, we want equality to basically be object identity, for + // security purposes. Otherwise, for example, two javascript: URIs that + // are otherwise unrelated could end up "same origin", which would be + // unfortunate. + if (aSourceURI && aSourceURI == aTargetURI) { + return true; + } + + if (!aTargetURI || !aSourceURI) { + return false; + } + + // If either URI is a nested URI, get the base URI + nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(aSourceURI); + nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI); + +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) + // Check if either URI has a special origin. + nsCOMPtr<nsIURI> origin; + nsCOMPtr<nsIURIWithSpecialOrigin> uriWithSpecialOrigin = + do_QueryInterface(sourceBaseURI); + if (uriWithSpecialOrigin) { + rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + MOZ_ASSERT(origin); + sourceBaseURI = origin; + } + uriWithSpecialOrigin = do_QueryInterface(targetBaseURI); + if (uriWithSpecialOrigin) { + rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + MOZ_ASSERT(origin); + targetBaseURI = origin; + } +#endif + + nsCOMPtr<nsIPrincipal> sourceBlobPrincipal; + if (BlobURLProtocolHandler::GetBlobURLPrincipal( + sourceBaseURI, getter_AddRefs(sourceBlobPrincipal))) { + nsCOMPtr<nsIURI> sourceBlobOwnerURI; + auto* basePrin = BasePrincipal::Cast(sourceBlobPrincipal); + rv = basePrin->GetURI(getter_AddRefs(sourceBlobOwnerURI)); + if (NS_SUCCEEDED(rv)) { + sourceBaseURI = sourceBlobOwnerURI; + } + } + + nsCOMPtr<nsIPrincipal> targetBlobPrincipal; + if (BlobURLProtocolHandler::GetBlobURLPrincipal( + targetBaseURI, getter_AddRefs(targetBlobPrincipal))) { + nsCOMPtr<nsIURI> targetBlobOwnerURI; + auto* basePrin = BasePrincipal::Cast(targetBlobPrincipal); + rv = basePrin->GetURI(getter_AddRefs(targetBlobOwnerURI)); + if (NS_SUCCEEDED(rv)) { + targetBaseURI = targetBlobOwnerURI; + } + } + + if (!sourceBaseURI || !targetBaseURI) return false; + + // Compare schemes + nsAutoCString targetScheme; + bool sameScheme = false; + if (NS_FAILED(targetBaseURI->GetScheme(targetScheme)) || + NS_FAILED(sourceBaseURI->SchemeIs(targetScheme.get(), &sameScheme)) || + !sameScheme) { + // Not same-origin if schemes differ + return false; + } + + // For file scheme, reject unless the files are identical. See + // NS_RelaxStrictFileOriginPolicy for enforcing file same-origin checking + if (targetScheme.EqualsLiteral("file")) { + // in traditional unsafe behavior all files are the same origin + if (!aStrictFileOriginPolicy) return true; + + nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(sourceBaseURI)); + nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(targetBaseURI)); + + if (!sourceFileURL || !targetFileURL) return false; + + nsCOMPtr<nsIFile> sourceFile, targetFile; + + sourceFileURL->GetFile(getter_AddRefs(sourceFile)); + targetFileURL->GetFile(getter_AddRefs(targetFile)); + + if (!sourceFile || !targetFile) return false; + + // Otherwise they had better match + bool filesAreEqual = false; + rv = sourceFile->Equals(targetFile, &filesAreEqual); + return NS_SUCCEEDED(rv) && filesAreEqual; + } + +#if IS_ORIGIN_IS_FULL_SPEC_DEFINED + bool hasFlag; + if (NS_FAILED(NS_URIChainHasFlags( + targetBaseURI, nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, &hasFlag)) || + hasFlag) { + // URIs with this flag have the whole spec as a distinct trust + // domain; use the whole spec for comparison + nsAutoCString targetSpec; + nsAutoCString sourceSpec; + return (NS_SUCCEEDED(targetBaseURI->GetSpec(targetSpec)) && + NS_SUCCEEDED(sourceBaseURI->GetSpec(sourceSpec)) && + targetSpec.Equals(sourceSpec)); + } +#endif + + // Compare hosts + nsAutoCString targetHost; + nsAutoCString sourceHost; + if (NS_FAILED(targetBaseURI->GetAsciiHost(targetHost)) || + NS_FAILED(sourceBaseURI->GetAsciiHost(sourceHost))) { + return false; + } + + nsCOMPtr<nsIStandardURL> targetURL(do_QueryInterface(targetBaseURI)); + nsCOMPtr<nsIStandardURL> sourceURL(do_QueryInterface(sourceBaseURI)); + if (!targetURL || !sourceURL) { + return false; + } + + if (!targetHost.Equals(sourceHost, nsCaseInsensitiveCStringComparator)) { + return false; + } + + return NS_GetRealPort(targetBaseURI) == NS_GetRealPort(sourceBaseURI); +} + +bool NS_URIIsLocalFile(nsIURI* aURI) { + nsCOMPtr<nsINetUtil> util = do_GetNetUtil(); + + bool isFile; + return util && + NS_SUCCEEDED(util->ProtocolHasFlags( + aURI, nsIProtocolHandler::URI_IS_LOCAL_FILE, &isFile)) && + isFile; +} + +bool NS_RelaxStrictFileOriginPolicy(nsIURI* aTargetURI, nsIURI* aSourceURI, + bool aAllowDirectoryTarget /* = false */) { + if (!NS_URIIsLocalFile(aTargetURI)) { + // This is probably not what the caller intended + MOZ_ASSERT_UNREACHABLE( + "NS_RelaxStrictFileOriginPolicy called with non-file URI"); + return false; + } + + if (!NS_URIIsLocalFile(aSourceURI)) { + // If the source is not also a file: uri then forget it + // (don't want resource: principals in a file: doc) + // + // note: we're not de-nesting jar: uris here, we want to + // keep archive content bottled up in its own little island + return false; + } + + // + // pull out the internal files + // + nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(aTargetURI)); + nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(aSourceURI)); + nsCOMPtr<nsIFile> targetFile; + nsCOMPtr<nsIFile> sourceFile; + bool targetIsDir; + + // Make sure targetFile is not a directory (bug 209234) + // and that it exists w/out unescaping (bug 395343) + if (!sourceFileURL || !targetFileURL || + NS_FAILED(targetFileURL->GetFile(getter_AddRefs(targetFile))) || + NS_FAILED(sourceFileURL->GetFile(getter_AddRefs(sourceFile))) || + !targetFile || !sourceFile || NS_FAILED(targetFile->Normalize()) || +#ifndef MOZ_WIDGET_ANDROID + NS_FAILED(sourceFile->Normalize()) || +#endif + (!aAllowDirectoryTarget && + (NS_FAILED(targetFile->IsDirectory(&targetIsDir)) || targetIsDir))) { + return false; + } + + return false; +} + +bool NS_IsInternalSameURIRedirect(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, uint32_t aFlags) { + if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) { + return false; + } + + nsCOMPtr<nsIURI> oldURI, newURI; + aOldChannel->GetURI(getter_AddRefs(oldURI)); + aNewChannel->GetURI(getter_AddRefs(newURI)); + + if (!oldURI || !newURI) { + return false; + } + + bool res; + return NS_SUCCEEDED(oldURI->Equals(newURI, &res)) && res; +} + +bool NS_IsHSTSUpgradeRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel, + uint32_t aFlags) { + if (!(aFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE)) { + return false; + } + + nsCOMPtr<nsIURI> oldURI, newURI; + aOldChannel->GetURI(getter_AddRefs(oldURI)); + aNewChannel->GetURI(getter_AddRefs(newURI)); + + if (!oldURI || !newURI) { + return false; + } + + if (!oldURI->SchemeIs("http")) { + return false; + } + + nsCOMPtr<nsIURI> upgradedURI; + nsresult rv = NS_GetSecureUpgradedURI(oldURI, getter_AddRefs(upgradedURI)); + if (NS_FAILED(rv)) { + return false; + } + + bool res; + return NS_SUCCEEDED(upgradedURI->Equals(newURI, &res)) && res; +} + +bool NS_ShouldRemoveAuthHeaderOnRedirect(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aFlags) { + // we need to strip Authentication headers for external cross-origin redirects + // Howerver, we should NOT strip auth headers for + // - internal redirects/HSTS upgrades + // - same origin redirects + // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch + if ((aFlags & (nsIChannelEventSink::REDIRECT_STS_UPGRADE | + nsIChannelEventSink::REDIRECT_INTERNAL))) { + // this is an internal redirect do not strip auth header + return false; + } + nsCOMPtr<nsIURI> oldUri; + MOZ_ALWAYS_SUCCEEDS( + NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldUri))); + + nsCOMPtr<nsIURI> newUri; + MOZ_ALWAYS_SUCCEEDS( + NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newUri))); + + nsresult rv = nsContentUtils::GetSecurityManager()->CheckSameOriginURI( + newUri, oldUri, false, false); + + return NS_FAILED(rv); +} + +nsresult NS_LinkRedirectChannels(uint64_t channelId, + nsIParentChannel* parentChannel, + nsIChannel** _result) { + nsCOMPtr<nsIRedirectChannelRegistrar> registrar = + RedirectChannelRegistrar::GetOrCreate(); + MOZ_ASSERT(registrar); + + return registrar->LinkChannels(channelId, parentChannel, _result); +} + +nsILoadInfo::CrossOriginEmbedderPolicy +NS_GetCrossOriginEmbedderPolicyFromHeader( + const nsACString& aHeader, bool aIsOriginTrialCoepCredentiallessEnabled) { + nsCOMPtr<nsISFVService> sfv = GetSFVService(); + + nsCOMPtr<nsISFVItem> item; + nsresult rv = sfv->ParseItem(aHeader, getter_AddRefs(item)); + if (NS_FAILED(rv)) { + return nsILoadInfo::EMBEDDER_POLICY_NULL; + } + + nsCOMPtr<nsISFVBareItem> value; + rv = item->GetValue(getter_AddRefs(value)); + if (NS_FAILED(rv)) { + return nsILoadInfo::EMBEDDER_POLICY_NULL; + } + + nsCOMPtr<nsISFVToken> token = do_QueryInterface(value); + if (!token) { + return nsILoadInfo::EMBEDDER_POLICY_NULL; + } + + nsAutoCString embedderPolicy; + rv = token->GetValue(embedderPolicy); + if (NS_FAILED(rv)) { + return nsILoadInfo::EMBEDDER_POLICY_NULL; + } + + if (embedderPolicy.EqualsLiteral("require-corp")) { + return nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP; + } else if (embedderPolicy.EqualsLiteral("credentialless") && + IsCoepCredentiallessEnabled( + aIsOriginTrialCoepCredentiallessEnabled)) { + return nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS; + } + + return nsILoadInfo::EMBEDDER_POLICY_NULL; +} + +/** Given the first (disposition) token from a Content-Disposition header, + * tell whether it indicates the content is inline or attachment + * @param aDispToken the disposition token from the content-disposition header + */ +uint32_t NS_GetContentDispositionFromToken(const nsAString& aDispToken) { + // RFC 2183, section 2.8 says that an unknown disposition + // value should be treated as "attachment" + // If all of these tests eval to false, then we have a content-disposition of + // "attachment" or unknown + if (aDispToken.IsEmpty() || aDispToken.LowerCaseEqualsLiteral("inline") || + // Broken sites just send + // Content-Disposition: filename="file" + // without a disposition token... screen those out. + StringHead(aDispToken, 8).LowerCaseEqualsLiteral("filename")) { + return nsIChannel::DISPOSITION_INLINE; + } + + return nsIChannel::DISPOSITION_ATTACHMENT; +} + +uint32_t NS_GetContentDispositionFromHeader(const nsACString& aHeader, + nsIChannel* aChan /* = nullptr */) { + nsresult rv; + nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = + do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv); + if (NS_FAILED(rv)) return nsIChannel::DISPOSITION_ATTACHMENT; + + nsAutoString dispToken; + rv = mimehdrpar->GetParameterHTTP(aHeader, "", ""_ns, true, nullptr, + dispToken); + + if (NS_FAILED(rv)) { + // special case (see bug 272541): empty disposition type handled as "inline" + if (rv == NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY) { + return nsIChannel::DISPOSITION_INLINE; + } + return nsIChannel::DISPOSITION_ATTACHMENT; + } + + return NS_GetContentDispositionFromToken(dispToken); +} + +nsresult NS_GetFilenameFromDisposition(nsAString& aFilename, + const nsACString& aDisposition) { + aFilename.Truncate(); + + nsresult rv; + nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = + do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + // Get the value of 'filename' parameter + rv = mimehdrpar->GetParameterHTTP(aDisposition, "filename", ""_ns, true, + nullptr, aFilename); + + if (NS_FAILED(rv)) { + aFilename.Truncate(); + return rv; + } + + if (aFilename.IsEmpty()) return NS_ERROR_NOT_AVAILABLE; + + // Filename may still be percent-encoded. Fix: + if (aFilename.FindChar('%') != -1) { + nsCOMPtr<nsITextToSubURI> textToSubURI = + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsAutoString unescaped; + textToSubURI->UnEscapeURIForUI(NS_ConvertUTF16toUTF8(aFilename), + /* dontEscape = */ true, unescaped); + aFilename.Assign(unescaped); + } + } + + return NS_OK; +} + +void net_EnsurePSMInit() { + if (XRE_IsSocketProcess()) { + EnsureNSSInitializedChromeOrContent(); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + DebugOnly<bool> rv = EnsureNSSInitializedChromeOrContent(); + MOZ_ASSERT(rv); +} + +bool NS_IsAboutBlank(nsIURI* uri) { + // GetSpec can be expensive for some URIs, so check the scheme first. + if (!uri->SchemeIs("about")) { + return false; + } + + nsAutoCString spec; + if (NS_FAILED(uri->GetSpec(spec))) { + return false; + } + + return spec.EqualsLiteral("about:blank"); +} + +bool NS_IsAboutSrcdoc(nsIURI* uri) { + // GetSpec can be expensive for some URIs, so check the scheme first. + if (!uri->SchemeIs("about")) { + return false; + } + + nsAutoCString spec; + if (NS_FAILED(uri->GetSpec(spec))) { + return false; + } + + return spec.EqualsLiteral("about:srcdoc"); +} + +nsresult NS_GenerateHostPort(const nsCString& host, int32_t port, + nsACString& hostLine) { + if (strchr(host.get(), ':')) { + // host is an IPv6 address literal and must be encapsulated in []'s + hostLine.Assign('['); + // scope id is not needed for Host header. + int scopeIdPos = host.FindChar('%'); + if (scopeIdPos == -1) { + hostLine.Append(host); + } else if (scopeIdPos > 0) { + hostLine.Append(Substring(host, 0, scopeIdPos)); + } else { + return NS_ERROR_MALFORMED_URI; + } + hostLine.Append(']'); + } else { + hostLine.Assign(host); + } + if (port != -1) { + hostLine.Append(':'); + hostLine.AppendInt(port); + } + return NS_OK; +} + +void NS_SniffContent(const char* aSnifferType, nsIRequest* aRequest, + const uint8_t* aData, uint32_t aLength, + nsACString& aSniffedType) { + using ContentSnifferCache = nsCategoryCache<nsIContentSniffer>; + extern ContentSnifferCache* gNetSniffers; + extern ContentSnifferCache* gDataSniffers; + extern ContentSnifferCache* gORBSniffers; + extern ContentSnifferCache* gNetAndORBSniffers; + ContentSnifferCache* cache = nullptr; + if (!strcmp(aSnifferType, NS_CONTENT_SNIFFER_CATEGORY)) { + if (!gNetSniffers) { + gNetSniffers = new ContentSnifferCache(NS_CONTENT_SNIFFER_CATEGORY); + } + cache = gNetSniffers; + } else if (!strcmp(aSnifferType, NS_DATA_SNIFFER_CATEGORY)) { + if (!gDataSniffers) { + gDataSniffers = new ContentSnifferCache(NS_DATA_SNIFFER_CATEGORY); + } + cache = gDataSniffers; + } else if (!strcmp(aSnifferType, NS_ORB_SNIFFER_CATEGORY)) { + if (!gORBSniffers) { + gORBSniffers = new ContentSnifferCache(NS_ORB_SNIFFER_CATEGORY); + } + cache = gORBSniffers; + } else if (!strcmp(aSnifferType, NS_CONTENT_AND_ORB_SNIFFER_CATEGORY)) { + if (!gNetAndORBSniffers) { + gNetAndORBSniffers = + new ContentSnifferCache(NS_CONTENT_AND_ORB_SNIFFER_CATEGORY); + } + cache = gNetAndORBSniffers; + } else { + // Invalid content sniffer type was requested + MOZ_ASSERT(false); + return; + } + + // In case XCTO nosniff was present, we could just skip sniffing here + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + if (channel) { + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + if (loadInfo->GetSkipContentSniffing()) { + /* Bug 1571742 + * We cannot skip snffing if the current MIME-Type might be a JSON. + * The JSON-Viewer relies on its own sniffer to determine, if it can + * render the page, so we need to make an exception if the Server provides + * a application/ mime, as it might be json. + */ + nsAutoCString currentContentType; + channel->GetContentType(currentContentType); + if (!StringBeginsWith(currentContentType, "application/"_ns)) { + return; + } + } + } + nsCOMArray<nsIContentSniffer> sniffers; + cache->GetEntries(sniffers); + for (int32_t i = 0; i < sniffers.Count(); ++i) { + nsresult rv = sniffers[i]->GetMIMETypeFromContent(aRequest, aData, aLength, + aSniffedType); + if (NS_SUCCEEDED(rv) && !aSniffedType.IsEmpty()) { + return; + } + } + + aSniffedType.Truncate(); +} + +bool NS_IsSrcdocChannel(nsIChannel* aChannel) { + bool isSrcdoc; + nsCOMPtr<nsIInputStreamChannel> isr = do_QueryInterface(aChannel); + if (isr) { + isr->GetIsSrcdocChannel(&isSrcdoc); + return isSrcdoc; + } + nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(aChannel); + if (vsc) { + nsresult rv = vsc->GetIsSrcdocChannel(&isSrcdoc); + if (NS_SUCCEEDED(rv)) { + return isSrcdoc; + } + } + return false; +} + +// helper function for NS_ShouldSecureUpgrade for checking HSTS +bool handleResultFunc(bool aAllowSTS, bool aIsStsHost) { + if (aIsStsHost) { + LOG(("nsHttpChannel::Connect() STS permissions found\n")); + if (aAllowSTS) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::STS); + return true; + } + Telemetry::AccumulateCategorical( + Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::PrefBlockedSTS); + } else { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::NoReasonToUpgrade); + } + return false; +}; +// That function is a helper function of NS_ShouldSecureUpgrade to check if +// CSP upgrade-insecure-requests, Mixed content auto upgrading or HTTPs-Only/- +// First should upgrade the given request. +static bool ShouldSecureUpgradeNoHSTS(nsIURI* aURI, nsILoadInfo* aLoadInfo) { + // 2. CSP upgrade-insecure-requests + if (aLoadInfo->GetUpgradeInsecureRequests()) { + // let's log a message to the console that we are upgrading a request + nsAutoCString scheme; + aURI->GetScheme(scheme); + // append the additional 's' for security to the scheme :-) + scheme.AppendLiteral("s"); + NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault()); + NS_ConvertUTF8toUTF16 reportScheme(scheme); + AutoTArray<nsString, 2> params = {reportSpec, reportScheme}; + uint64_t innerWindowId = aLoadInfo->GetInnerWindowID(); + CSP_LogLocalizedStr("upgradeInsecureRequest", params, + u""_ns, // aSourceFile + u""_ns, // aScriptSample + 0, // aLineNumber + 1, // aColumnNumber + nsIScriptError::warningFlag, + "upgradeInsecureRequest"_ns, innerWindowId, + !!aLoadInfo->GetOriginAttributes().mPrivateBrowsingId); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::CSP); + return true; + } + // 3. Mixed content auto upgrading + if (aLoadInfo->GetBrowserUpgradeInsecureRequests()) { + // let's log a message to the console that we are upgrading a request + nsAutoCString scheme; + aURI->GetScheme(scheme); + // append the additional 's' for security to the scheme :-) + scheme.AppendLiteral("s"); + NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault()); + NS_ConvertUTF8toUTF16 reportScheme(scheme); + AutoTArray<nsString, 2> params = {reportSpec, reportScheme}; + + nsAutoString localizedMsg; + nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES, + "MixedContentAutoUpgrade", params, + localizedMsg); + + // Prepending ixed Content to the outgoing console message + nsString message; + message.AppendLiteral(u"Mixed Content: "); + message.Append(localizedMsg); + + uint64_t innerWindowId = aLoadInfo->GetInnerWindowID(); + nsContentUtils::ReportToConsoleByWindowID( + message, nsIScriptError::warningFlag, "Mixed Content Message"_ns, + innerWindowId, aURI); + + // Set this flag so we know we'll upgrade because of + // 'security.mixed_content.upgrade_display_content'. + aLoadInfo->SetBrowserDidUpgradeInsecureRequests(true); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::BrowserDisplay); + + return true; + } + + // 4. Https-Only + if (nsHTTPSOnlyUtils::ShouldUpgradeRequest(aURI, aLoadInfo)) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::HTTPSOnly); + return true; + } + // 4.a Https-First + if (nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(aURI, aLoadInfo)) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::HTTPSFirst); + return true; + } + return false; +} + +// Check if channel should be upgraded. check in the following order: +// 1. HSTS +// 2. CSP upgrade-insecure-requests +// 3. Mixed content auto upgrading +// 4. Https-Only / first +// (5. Https RR - will be checked in nsHttpChannel) +nsresult NS_ShouldSecureUpgrade( + nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIPrincipal* aChannelResultPrincipal, + bool aAllowSTS, const OriginAttributes& aOriginAttributes, + bool& aShouldUpgrade, std::function<void(bool, nsresult)>&& aResultCallback, + bool& aWillCallback) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + aWillCallback = false; + aShouldUpgrade = false; + + // Even if we're in private browsing mode, we still enforce existing STS + // data (it is read-only). + // if the connection is not using SSL and either the exact host matches or + // a superdomain wants to force HTTPS, do it. + bool isHttps = aURI->SchemeIs("https"); + + // If request is https, then there is nothing to do here. + if (isHttps) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::AlreadyHTTPS); + aShouldUpgrade = false; + return NS_OK; + } + // If it is a mixed content trustworthy loopback, then we shouldn't upgrade + // it. + if (nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI)) { + aShouldUpgrade = false; + return NS_OK; + } + // If no loadInfo exist there is nothing to upgrade here. + if (!aLoadInfo) { + aShouldUpgrade = false; + return NS_OK; + } + MOZ_ASSERT(!aURI->SchemeIs("https")); + + // enforce Strict-Transport-Security + nsISiteSecurityService* sss = gHttpHandler->GetSSService(); + NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY); + + bool isStsHost = false; + // Calling |IsSecureURI| before the storage is ready to read will + // block the main thread. Once the storage is ready, we can call it + // from main thread. + static Atomic<bool, Relaxed> storageReady(false); + if (!storageReady && gSocketTransportService && aResultCallback) { + nsCOMPtr<nsILoadInfo> loadInfo = aLoadInfo; + nsCOMPtr<nsIURI> uri = aURI; + auto callbackWrapper = [resultCallback{std::move(aResultCallback)}, uri, + loadInfo](bool aShouldUpgrade, nsresult aStatus) { + MOZ_ASSERT(NS_IsMainThread()); + + // 1. HSTS upgrade + if (aShouldUpgrade || NS_FAILED(aStatus)) { + resultCallback(aShouldUpgrade, aStatus); + return; + } + // Check if we need to upgrade because of other reasons. + // 2. CSP upgrade-insecure-requests + // 3. Mixed content auto upgrading + // 4. Https-Only / first + bool shouldUpgrade = ShouldSecureUpgradeNoHSTS(uri, loadInfo); + resultCallback(shouldUpgrade, aStatus); + }; + nsCOMPtr<nsISiteSecurityService> service = sss; + nsresult rv = gSocketTransportService->Dispatch( + NS_NewRunnableFunction( + "net::NS_ShouldSecureUpgrade", + [service{std::move(service)}, uri{std::move(uri)}, + originAttributes(aOriginAttributes), + handleResultFunc{std::move(handleResultFunc)}, + callbackWrapper{std::move(callbackWrapper)}, + allowSTS{std::move(aAllowSTS)}]() mutable { + bool isStsHost = false; + nsresult rv = + service->IsSecureURI(uri, originAttributes, &isStsHost); + + // Successfully get the result from |IsSecureURI| implies that + // the storage is ready to read. + storageReady = NS_SUCCEEDED(rv); + bool shouldUpgrade = handleResultFunc(allowSTS, isStsHost); + // Check if request should be upgraded. + NS_DispatchToMainThread(NS_NewRunnableFunction( + "net::NS_ShouldSecureUpgrade::ResultCallback", + [rv, shouldUpgrade, + callbackWrapper{std::move(callbackWrapper)}]() { + callbackWrapper(shouldUpgrade, rv); + })); + }), + NS_DISPATCH_NORMAL); + aWillCallback = NS_SUCCEEDED(rv); + return rv; + } + + nsresult rv = sss->IsSecureURI(aURI, aOriginAttributes, &isStsHost); + + // if the SSS check fails, it's likely because this load is on a + // malformed URI or something else in the setup is wrong, so any error + // should be reported. + NS_ENSURE_SUCCESS(rv, rv); + + aShouldUpgrade = handleResultFunc(aAllowSTS, isStsHost); + if (!aShouldUpgrade) { + // Check for CSP upgrade-insecure-requests, Mixed content auto upgrading + // and Https-Only / -First. + aShouldUpgrade = ShouldSecureUpgradeNoHSTS(aURI, aLoadInfo); + } + return rv; +} + +nsresult NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI) { + NS_MutateURI mutator(aURI); + mutator.SetScheme("https"_ns); // Change the scheme to HTTPS: + + // Change the default port to 443: + nsCOMPtr<nsIStandardURL> stdURL = do_QueryInterface(aURI); + if (stdURL) { + mutator.Apply(&nsIStandardURLMutator::SetDefaultPort, 443, nullptr); + } else { + // If we don't have a nsStandardURL, fall back to using GetPort/SetPort. + // XXXdholbert Is this function even called with a non-nsStandardURL arg, + // in practice? + NS_WARNING("Calling NS_GetSecureUpgradedURI for non nsStandardURL"); + int32_t oldPort = -1; + nsresult rv = aURI->GetPort(&oldPort); + if (NS_FAILED(rv)) return rv; + + // Keep any nonstandard ports so only the scheme is changed. + // For example: + // http://foo.com:80 -> https://foo.com:443 + // http://foo.com:81 -> https://foo.com:81 + + if (oldPort == 80 || oldPort == -1) { + mutator.SetPort(-1); + } else { + mutator.SetPort(oldPort); + } + } + + return mutator.Finalize(aUpgradedURI); +} + +nsresult NS_CompareLoadInfoAndLoadContext(nsIChannel* aChannel) { + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(aChannel, loadContext); + if (!loadContext) { + return NS_OK; + } + + // We try to skip about:newtab. + // about:newtab will use SystemPrincipal to download thumbnails through + // https:// and blob URLs. + bool isAboutPage = false; + nsINode* node = loadInfo->LoadingNode(); + if (node) { + nsIURI* uri = node->OwnerDoc()->GetDocumentURI(); + isAboutPage = uri->SchemeIs("about"); + } + + if (isAboutPage) { + return NS_OK; + } + + // We skip the favicon loading here. The favicon loading might be + // triggered by the XUL image. For that case, the loadContext will have + // default originAttributes since the XUL image uses SystemPrincipal, but + // the loadInfo will use originAttributes from the content. Thus, the + // originAttributes between loadInfo and loadContext will be different. + // That's why we have to skip the comparison for the favicon loading. + if (loadInfo->GetLoadingPrincipal() && + loadInfo->GetLoadingPrincipal()->IsSystemPrincipal() && + loadInfo->InternalContentPolicyType() == + nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) { + return NS_OK; + } + + OriginAttributes originAttrsLoadInfo = loadInfo->GetOriginAttributes(); + OriginAttributes originAttrsLoadContext; + loadContext->GetOriginAttributes(originAttrsLoadContext); + + LOG( + ("NS_CompareLoadInfoAndLoadContext - loadInfo: %d, %d; " + "loadContext: %d, %d. [channel=%p]", + originAttrsLoadInfo.mUserContextId, + originAttrsLoadInfo.mPrivateBrowsingId, + originAttrsLoadContext.mUserContextId, + originAttrsLoadContext.mPrivateBrowsingId, aChannel)); + + MOZ_ASSERT(originAttrsLoadInfo.mUserContextId == + originAttrsLoadContext.mUserContextId, + "The value of mUserContextId in the loadContext and in the " + "loadInfo are not the same!"); + + MOZ_ASSERT(originAttrsLoadInfo.mPrivateBrowsingId == + originAttrsLoadContext.mPrivateBrowsingId, + "The value of mPrivateBrowsingId in the loadContext and in the " + "loadInfo are not the same!"); + + return NS_OK; +} + +nsresult NS_SetRequestBlockingReason(nsIChannel* channel, uint32_t reason) { + NS_ENSURE_ARG(channel); + + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + return NS_SetRequestBlockingReason(loadInfo, reason); +} + +nsresult NS_SetRequestBlockingReason(nsILoadInfo* loadInfo, uint32_t reason) { + NS_ENSURE_ARG(loadInfo); + + return loadInfo->SetRequestBlockingReason(reason); +} + +nsresult NS_SetRequestBlockingReasonIfNull(nsILoadInfo* loadInfo, + uint32_t reason) { + NS_ENSURE_ARG(loadInfo); + + uint32_t existingReason; + if (NS_SUCCEEDED(loadInfo->GetRequestBlockingReason(&existingReason)) && + existingReason != nsILoadInfo::BLOCKING_REASON_NONE) { + return NS_OK; + } + + return loadInfo->SetRequestBlockingReason(reason); +} + +bool NS_IsOffline() { + bool offline = true; + bool connectivity = true; + nsCOMPtr<nsIIOService> ios = do_GetIOService(); + if (ios) { + ios->GetOffline(&offline); + ios->GetConnectivity(&connectivity); + } + return offline || !connectivity; +} + +/** + * This function returns true if this channel should be classified by + * the URL Classifier, false otherwise. + * + * The idea of the algorithm to determine if a channel should be + * classified is based on: + * 1. Channels created by non-privileged code should be classified. + * 2. Top-level document’s channels, if loaded by privileged code + * (system principal), should be classified. + * 3. Any other channel, created by privileged code, is considered safe. + * + * A bad/hacked/corrupted safebrowsing database, plus a mistakenly + * classified critical channel (this may result from a bug in the exemption + * rules or incorrect information being passed into) can cause serious + * problems. For example, if the updater channel is classified and blocked + * by the Safe Browsing, Firefox can't update itself, and there is no way to + * recover from that. + * + * So two safeguards are added to ensure critical channels are never + * automatically classified either because there is a bug in the algorithm + * or the data in loadinfo is wrong. + * 1. beConservative, this is set by ServiceRequest and we treat + * channel created for ServiceRequest as critical channels. + * 2. nsIChannel::LOAD_BYPASS_URL_CLASSIFIER, channel's opener can use this + * flag to enforce bypassing the URL classifier check. + */ +bool NS_ShouldClassifyChannel(nsIChannel* aChannel) { + nsLoadFlags loadFlags; + Unused << aChannel->GetLoadFlags(&loadFlags); + // If our load flags dictate that we must let this channel through without + // URL classification, obey that here without performing more checks. + if (loadFlags & nsIChannel::LOAD_BYPASS_URL_CLASSIFIER) { + return false; + } + + nsCOMPtr<nsIHttpChannelInternal> httpChannel(do_QueryInterface(aChannel)); + if (httpChannel) { + bool beConservative; + nsresult rv = httpChannel->GetBeConservative(&beConservative); + + // beConservative flag, set by ServiceRequest to ensure channels that + // fetch update use conservative TLS setting, are used here to identify + // channels are critical to bypass classification. for channels don't + // support beConservative, continue to apply the exemption rules. + if (NS_SUCCEEDED(rv) && beConservative) { + return false; + } + } + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + ExtContentPolicyType type = loadInfo->GetExternalContentPolicyType(); + // Skip classifying channel triggered by system unless it is a top-level + // load. + return !(loadInfo->TriggeringPrincipal()->IsSystemPrincipal() && + ExtContentPolicy::TYPE_DOCUMENT != type); +} + +namespace mozilla { +namespace net { + +bool InScriptableRange(int64_t val) { + return (val <= kJS_MAX_SAFE_INTEGER) && (val >= kJS_MIN_SAFE_INTEGER); +} + +bool InScriptableRange(uint64_t val) { return val <= kJS_MAX_SAFE_UINTEGER; } + +nsresult GetParameterHTTP(const nsACString& aHeaderVal, const char* aParamName, + nsAString& aResult) { + return nsMIMEHeaderParamImpl::GetParameterHTTP(aHeaderVal, aParamName, + aResult); +} + +bool ChannelIsPost(nsIChannel* aChannel) { + if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) { + nsAutoCString method; + Unused << httpChannel->GetRequestMethod(method); + return method.EqualsLiteral("POST"); + } + return false; +} + +bool SchemeIsHTTP(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("http"); +} + +bool SchemeIsHTTPS(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("https"); +} + +bool SchemeIsJavascript(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("javascript"); +} + +bool SchemeIsChrome(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("chrome"); +} + +bool SchemeIsAbout(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("about"); +} + +bool SchemeIsBlob(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("blob"); +} + +bool SchemeIsFile(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("file"); +} + +bool SchemeIsData(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("data"); +} + +bool SchemeIsViewSource(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("view-source"); +} + +bool SchemeIsResource(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("resource"); +} + +bool SchemeIsFTP(nsIURI* aURI) { + MOZ_ASSERT(aURI); + return aURI->SchemeIs("ftp"); +} + +bool SchemeIsSpecial(const nsACString& aScheme) { + // See https://url.spec.whatwg.org/#special-scheme + return aScheme.EqualsIgnoreCase("ftp") || aScheme.EqualsIgnoreCase("file") || + aScheme.EqualsIgnoreCase("http") || + aScheme.EqualsIgnoreCase("https") || aScheme.EqualsIgnoreCase("ws") || + aScheme.EqualsIgnoreCase("wss"); +} + +bool IsSchemeChangePermitted(nsIURI* aOldURI, const nsACString& newScheme) { + // See step 2.1 in https://url.spec.whatwg.org/#special-scheme + // Note: The spec text uses "buffer" instead of newScheme, and "url" + MOZ_ASSERT(aOldURI); + + nsAutoCString tmp; + nsresult rv = aOldURI->GetScheme(tmp); + // If url's scheme is a special scheme and buffer is not a + // special scheme, then return. + // If url's scheme is not a special scheme and buffer is a + // special scheme, then return. + if (NS_FAILED(rv) || SchemeIsSpecial(tmp) != SchemeIsSpecial(newScheme)) { + return false; + } + + // If url's scheme is "file" and its host is an empty host, then return. + if (aOldURI->SchemeIs("file")) { + rv = aOldURI->GetHost(tmp); + if (NS_FAILED(rv) || tmp.IsEmpty()) { + return false; + } + } + + // URL Spec: If url includes credentials or has a non-null port, and + // buffer is "file", then return. + if (newScheme.EqualsIgnoreCase("file")) { + bool hasUserPass; + if (NS_FAILED(aOldURI->GetHasUserPass(&hasUserPass)) || hasUserPass) { + return false; + } + int32_t port; + rv = aOldURI->GetPort(&port); + if (NS_FAILED(rv) || port != -1) { + return false; + } + } + + return true; +} + +already_AddRefed<nsIURI> TryChangeProtocol(nsIURI* aURI, + const nsAString& aProtocol) { + MOZ_ASSERT(aURI); + + nsAString::const_iterator start; + aProtocol.BeginReading(start); + + nsAString::const_iterator end; + aProtocol.EndReading(end); + + nsAString::const_iterator iter(start); + FindCharInReadable(':', iter, end); + + // Changing the protocol of a URL, changes the "nature" of the URI + // implementation. In order to do this properly, we have to serialize the + // existing URL and reparse it in a new object. + nsCOMPtr<nsIURI> clone; + nsresult rv = NS_MutateURI(aURI) + .SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter))) + .Finalize(clone); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + if (StaticPrefs::network_url_strict_protocol_setter()) { + nsAutoCString newScheme; + rv = clone->GetScheme(newScheme); + if (NS_FAILED(rv) || !net::IsSchemeChangePermitted(aURI, newScheme)) { + nsAutoCString url; + Unused << clone->GetSpec(url); + AutoTArray<nsString, 2> params; + params.AppendElement(NS_ConvertUTF8toUTF16(url)); + params.AppendElement(NS_ConvertUTF8toUTF16(newScheme)); + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "Strict Url Protocol Setter"_ns, nullptr, + nsContentUtils::eNECKO_PROPERTIES, "StrictUrlProtocolSetter", params); + return nullptr; + } + } + + nsAutoCString href; + rv = clone->GetSpec(href); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + RefPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), href); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + return uri.forget(); +} + +// Decode a parameter value using the encoding defined in RFC 5987 (in place) +// +// charset "'" [ language ] "'" value-chars +// +// returns true when decoding happened successfully (otherwise leaves +// passed value alone) +static bool Decode5987Format(nsAString& aEncoded) { + nsresult rv; + nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = + do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv); + if (NS_FAILED(rv)) return false; + + nsAutoCString asciiValue; + + const char16_t* encstart = aEncoded.BeginReading(); + const char16_t* encend = aEncoded.EndReading(); + + // create a plain ASCII string, aborting if we can't do that + // converted form is always shorter than input + while (encstart != encend) { + if (*encstart > 0 && *encstart < 128) { + asciiValue.Append((char)*encstart); + } else { + return false; + } + encstart++; + } + + nsAutoString decoded; + nsAutoCString language; + + rv = mimehdrpar->DecodeRFC5987Param(asciiValue, language, decoded); + if (NS_FAILED(rv)) return false; + + aEncoded = decoded; + return true; +} + +LinkHeader::LinkHeader() { mCrossOrigin.SetIsVoid(true); } + +void LinkHeader::Reset() { + mHref.Truncate(); + mRel.Truncate(); + mTitle.Truncate(); + mNonce.Truncate(); + mIntegrity.Truncate(); + mSrcset.Truncate(); + mSizes.Truncate(); + mType.Truncate(); + mMedia.Truncate(); + mAnchor.Truncate(); + mCrossOrigin.Truncate(); + mReferrerPolicy.Truncate(); + mAs.Truncate(); + mCrossOrigin.SetIsVoid(true); + mFetchPriority.Truncate(); +} + +nsresult LinkHeader::NewResolveHref(nsIURI** aOutURI, nsIURI* aBaseURI) const { + if (mAnchor.IsEmpty()) { + // use the base uri + return NS_NewURI(aOutURI, mHref, nullptr, aBaseURI); + } + + // compute the anchored URI + nsCOMPtr<nsIURI> anchoredURI; + nsresult rv = + NS_NewURI(getter_AddRefs(anchoredURI), mAnchor, nullptr, aBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewURI(aOutURI, mHref, nullptr, anchoredURI); +} + +bool LinkHeader::operator==(const LinkHeader& rhs) const { + return mHref == rhs.mHref && mRel == rhs.mRel && mTitle == rhs.mTitle && + mNonce == rhs.mNonce && mIntegrity == rhs.mIntegrity && + mSrcset == rhs.mSrcset && mSizes == rhs.mSizes && mType == rhs.mType && + mMedia == rhs.mMedia && mAnchor == rhs.mAnchor && + mCrossOrigin == rhs.mCrossOrigin && + mReferrerPolicy == rhs.mReferrerPolicy && mAs == rhs.mAs && + mFetchPriority == rhs.mFetchPriority; +} + +constexpr auto kTitleStar = "title*"_ns; + +nsTArray<LinkHeader> ParseLinkHeader(const nsAString& aLinkData) { + nsTArray<LinkHeader> linkHeaders; + + // keep track where we are within the header field + bool seenParameters = false; + + // parse link content and add to array + LinkHeader header; + nsAutoString titleStar; + + // copy to work buffer + nsAutoString stringList(aLinkData); + + // put an extra null at the end + stringList.Append(kNullCh); + + char16_t* start = stringList.BeginWriting(); + + while (*start != kNullCh) { + // parse link content and call process style link + + // skip leading space + while ((*start != kNullCh) && nsCRT::IsAsciiSpace(*start)) { + ++start; + } + + char16_t* end = start; + char16_t* last = end - 1; + + bool wasQuotedString = false; + + // look for semicolon or comma + while (*end != kNullCh && *end != kSemicolon && *end != kComma) { + char16_t ch = *end; + + if (ch == kQuote || ch == kLessThan) { + // quoted string + + char16_t quote = ch; + if (quote == kLessThan) { + quote = kGreaterThan; + } + + wasQuotedString = (ch == kQuote); + + char16_t* closeQuote = (end + 1); + + // seek closing quote + while (*closeQuote != kNullCh && quote != *closeQuote) { + // in quoted-string, "\" is an escape character + if (wasQuotedString && *closeQuote == kBackSlash && + *(closeQuote + 1) != kNullCh) { + ++closeQuote; + } + + ++closeQuote; + } + + if (quote == *closeQuote) { + // found closer + + // skip to close quote + end = closeQuote; + + last = end - 1; + + ch = *(end + 1); + + if (ch != kNullCh && ch != kSemicolon && ch != kComma) { + // end string here + *(++end) = kNullCh; + + ch = *(end + 1); + + // keep going until semi or comma + while (ch != kNullCh && ch != kSemicolon && ch != kComma) { + ++end; + + ch = *(end + 1); + } + } + } + } + + ++end; + ++last; + } + + char16_t endCh = *end; + + // end string here + *end = kNullCh; + + if (start < end) { + if ((*start == kLessThan) && (*last == kGreaterThan)) { + *last = kNullCh; + + // first instance of <...> wins + // also, do not allow hrefs after the first param was seen + if (header.mHref.IsEmpty() && !seenParameters) { + header.mHref = (start + 1); + header.mHref.StripWhitespace(); + } + } else { + char16_t* equals = start; + seenParameters = true; + + while ((*equals != kNullCh) && (*equals != kEqual)) { + equals++; + } + + const bool hadEquals = *equals != kNullCh; + *equals = kNullCh; + nsAutoString attr(start); + attr.StripWhitespace(); + + char16_t* value = hadEquals ? ++equals : equals; + while (nsCRT::IsAsciiSpace(*value)) { + value++; + } + + if ((*value == kQuote) && (*value == *last)) { + *last = kNullCh; + value++; + } + + if (wasQuotedString) { + // unescape in-place + char16_t* unescaped = value; + char16_t* src = value; + + while (*src != kNullCh) { + if (*src == kBackSlash && *(src + 1) != kNullCh) { + src++; + } + *unescaped++ = *src++; + } + + *unescaped = kNullCh; + } + + if (attr.LowerCaseEqualsASCII(kTitleStar.get())) { + if (titleStar.IsEmpty() && !wasQuotedString) { + // RFC 5987 encoding; uses token format only, so skip if we get + // here with a quoted-string + nsAutoString tmp; + tmp = value; + if (Decode5987Format(tmp)) { + titleStar = tmp; + titleStar.CompressWhitespace(); + } else { + // header value did not parse, throw it away + titleStar.Truncate(); + } + } + } else { + header.MaybeUpdateAttribute(attr, value); + } + } + } + + if (endCh == kComma) { + // hit a comma, process what we've got so far + + header.mHref.Trim(" \t\n\r\f"); // trim HTML5 whitespace + if (!header.mHref.IsEmpty() && !header.mRel.IsEmpty()) { + if (!titleStar.IsEmpty()) { + // prefer RFC 5987 variant over non-I18zed version + header.mTitle = titleStar; + } + linkHeaders.AppendElement(header); + } + + titleStar.Truncate(); + header.Reset(); + + seenParameters = false; + } + + start = ++end; + } + + header.mHref.Trim(" \t\n\r\f"); // trim HTML5 whitespace + if (!header.mHref.IsEmpty() && !header.mRel.IsEmpty()) { + if (!titleStar.IsEmpty()) { + // prefer RFC 5987 variant over non-I18zed version + header.mTitle = titleStar; + } + linkHeaders.AppendElement(header); + } + + return linkHeaders; +} + +void LinkHeader::MaybeUpdateAttribute(const nsAString& aAttribute, + const char16_t* aValue) { + MOZ_ASSERT(!aAttribute.LowerCaseEqualsASCII(kTitleStar.get())); + + if (aAttribute.LowerCaseEqualsLiteral("rel")) { + if (mRel.IsEmpty()) { + mRel = aValue; + mRel.CompressWhitespace(); + } + } else if (aAttribute.LowerCaseEqualsLiteral("title")) { + if (mTitle.IsEmpty()) { + mTitle = aValue; + mTitle.CompressWhitespace(); + } + } else if (aAttribute.LowerCaseEqualsLiteral("type")) { + if (mType.IsEmpty()) { + mType = aValue; + mType.StripWhitespace(); + } + } else if (aAttribute.LowerCaseEqualsLiteral("media")) { + if (mMedia.IsEmpty()) { + mMedia = aValue; + + // The HTML5 spec is formulated in terms of the CSS3 spec, + // which specifies that media queries are case insensitive. + nsContentUtils::ASCIIToLower(mMedia); + } + } else if (aAttribute.LowerCaseEqualsLiteral("anchor")) { + if (mAnchor.IsEmpty()) { + mAnchor = aValue; + mAnchor.StripWhitespace(); + } + } else if (aAttribute.LowerCaseEqualsLiteral("crossorigin")) { + if (mCrossOrigin.IsVoid()) { + mCrossOrigin.SetIsVoid(false); + mCrossOrigin = aValue; + mCrossOrigin.StripWhitespace(); + } + } else if (aAttribute.LowerCaseEqualsLiteral("as")) { + if (mAs.IsEmpty()) { + mAs = aValue; + mAs.CompressWhitespace(); + } + } else if (aAttribute.LowerCaseEqualsLiteral("referrerpolicy")) { + // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#referrer-policy-attribute + // Specs says referrer policy attribute is an enumerated attribute, + // case insensitive and includes the empty string + // We will parse the aValue with AttributeReferrerPolicyFromString + // later, which will handle parsing it as an enumerated attribute. + if (mReferrerPolicy.IsEmpty()) { + mReferrerPolicy = aValue; + } + + } else if (aAttribute.LowerCaseEqualsLiteral("nonce")) { + if (mNonce.IsEmpty()) { + mNonce = aValue; + } + } else if (aAttribute.LowerCaseEqualsLiteral("integrity")) { + if (mIntegrity.IsEmpty()) { + mIntegrity = aValue; + } + } else if (aAttribute.LowerCaseEqualsLiteral("imagesrcset")) { + if (mSrcset.IsEmpty()) { + mSrcset = aValue; + } + } else if (aAttribute.LowerCaseEqualsLiteral("imagesizes")) { + if (mSizes.IsEmpty()) { + mSizes = aValue; + } + } else if (aAttribute.LowerCaseEqualsLiteral("fetchpriority")) { + if (mFetchPriority.IsEmpty()) { + LOG(("Update fetchPriority to \"%s\"", + NS_ConvertUTF16toUTF8(aValue).get())); + mFetchPriority = aValue; + } + } +} + +// We will use official mime-types from: +// https://www.iana.org/assignments/media-types/media-types.xhtml#font +// We do not support old deprecated mime-types for preload feature. +// (We currectly do not support font/collection) +static uint32_t StyleLinkElementFontMimeTypesNum = 5; +static const char* StyleLinkElementFontMimeTypes[] = { + "font/otf", "font/sfnt", "font/ttf", "font/woff", "font/woff2"}; + +bool IsFontMimeType(const nsAString& aType) { + if (aType.IsEmpty()) { + return true; + } + for (uint32_t i = 0; i < StyleLinkElementFontMimeTypesNum; i++) { + if (aType.EqualsASCII(StyleLinkElementFontMimeTypes[i])) { + return true; + } + } + return false; +} + +static const nsAttrValue::EnumTable kAsAttributeTable[] = { + {"", DESTINATION_INVALID}, {"audio", DESTINATION_AUDIO}, + {"font", DESTINATION_FONT}, {"image", DESTINATION_IMAGE}, + {"script", DESTINATION_SCRIPT}, {"style", DESTINATION_STYLE}, + {"track", DESTINATION_TRACK}, {"video", DESTINATION_VIDEO}, + {"fetch", DESTINATION_FETCH}, {nullptr, 0}}; + +void ParseAsValue(const nsAString& aValue, nsAttrValue& aResult) { + DebugOnly<bool> success = + aResult.ParseEnumValue(aValue, kAsAttributeTable, false, + // default value is a empty string + // if aValue is not a value we + // understand + &kAsAttributeTable[0]); + MOZ_ASSERT(success); +} + +nsContentPolicyType AsValueToContentPolicy(const nsAttrValue& aValue) { + switch (aValue.GetEnumValue()) { + case DESTINATION_INVALID: + return nsIContentPolicy::TYPE_INVALID; + case DESTINATION_AUDIO: + return nsIContentPolicy::TYPE_INTERNAL_AUDIO; + case DESTINATION_TRACK: + return nsIContentPolicy::TYPE_INTERNAL_TRACK; + case DESTINATION_VIDEO: + return nsIContentPolicy::TYPE_INTERNAL_VIDEO; + case DESTINATION_FONT: + return nsIContentPolicy::TYPE_FONT; + case DESTINATION_IMAGE: + return nsIContentPolicy::TYPE_IMAGE; + case DESTINATION_SCRIPT: + return nsIContentPolicy::TYPE_SCRIPT; + case DESTINATION_STYLE: + return nsIContentPolicy::TYPE_STYLESHEET; + case DESTINATION_FETCH: + return nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD; + } + return nsIContentPolicy::TYPE_INVALID; +} + +// TODO: implement this using nsAttrValue's destination enums when support for +// the new destinations is added; see this diff for a possible start: +// https://phabricator.services.mozilla.com/D172368?vs=705114&id=708720 +bool IsScriptLikeOrInvalid(const nsAString& aAs) { + return !( + aAs.LowerCaseEqualsASCII("fetch") || aAs.LowerCaseEqualsASCII("audio") || + aAs.LowerCaseEqualsASCII("document") || + aAs.LowerCaseEqualsASCII("embed") || aAs.LowerCaseEqualsASCII("font") || + aAs.LowerCaseEqualsASCII("frame") || aAs.LowerCaseEqualsASCII("iframe") || + aAs.LowerCaseEqualsASCII("image") || + aAs.LowerCaseEqualsASCII("manifest") || + aAs.LowerCaseEqualsASCII("object") || + aAs.LowerCaseEqualsASCII("report") || aAs.LowerCaseEqualsASCII("style") || + aAs.LowerCaseEqualsASCII("track") || aAs.LowerCaseEqualsASCII("video") || + aAs.LowerCaseEqualsASCII("webidentity") || + aAs.LowerCaseEqualsASCII("xslt")); +} + +bool CheckPreloadAttrs(const nsAttrValue& aAs, const nsAString& aType, + const nsAString& aMedia, + mozilla::dom::Document* aDocument) { + nsContentPolicyType policyType = AsValueToContentPolicy(aAs); + if (policyType == nsIContentPolicy::TYPE_INVALID) { + return false; + } + + // Check if media attribute is valid. + if (!aMedia.IsEmpty()) { + RefPtr<mozilla::dom::MediaList> mediaList = + mozilla::dom::MediaList::Create(NS_ConvertUTF16toUTF8(aMedia)); + if (!mediaList->Matches(*aDocument)) { + return false; + } + } + + if (aType.IsEmpty()) { + return true; + } + + if (policyType == nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD) { + return true; + } + + nsAutoString type(aType); + ToLowerCase(type); + if (policyType == nsIContentPolicy::TYPE_MEDIA) { + if (aAs.GetEnumValue() == DESTINATION_TRACK) { + return type.EqualsASCII("text/vtt"); + } + Maybe<MediaContainerType> mimeType = MakeMediaContainerType(aType); + if (!mimeType) { + return false; + } + DecoderDoctorDiagnostics diagnostics; + CanPlayStatus status = + DecoderTraits::CanHandleContainerType(*mimeType, &diagnostics); + // Preload if this return CANPLAY_YES and CANPLAY_MAYBE. + return status != CANPLAY_NO; + } + if (policyType == nsIContentPolicy::TYPE_FONT) { + return IsFontMimeType(type); + } + if (policyType == nsIContentPolicy::TYPE_IMAGE) { + return imgLoader::SupportImageWithMimeType( + NS_ConvertUTF16toUTF8(type), AcceptedMimeTypes::IMAGES_AND_DOCUMENTS); + } + if (policyType == nsIContentPolicy::TYPE_SCRIPT) { + return nsContentUtils::IsJavascriptMIMEType(type); + } + if (policyType == nsIContentPolicy::TYPE_STYLESHEET) { + return type.EqualsASCII("text/css"); + } + return false; +} + +void WarnIgnoredPreload(const mozilla::dom::Document& aDoc, nsIURI& aURI) { + AutoTArray<nsString, 1> params; + { + nsCString uri = nsContentUtils::TruncatedURLForDisplay(&aURI); + AppendUTF8toUTF16(uri, *params.AppendElement()); + } + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, &aDoc, + nsContentUtils::eDOM_PROPERTIES, + "PreloadIgnoredInvalidAttr", params); +} + +nsresult HasRootDomain(const nsACString& aInput, const nsACString& aHost, + bool* aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_FAILURE; + } + + *aResult = false; + + // If the strings are the same, we obviously have a match. + if (aInput == aHost) { + *aResult = true; + return NS_OK; + } + + // If aHost is not found, we know we do not have it as a root domain. + int32_t index = nsAutoCString(aInput).Find(aHost); + if (index == kNotFound) { + return NS_OK; + } + + // Otherwise, we have aHost as our root domain iff the index of aHost is + // aHost.length subtracted from our length and (since we do not have an + // exact match) the character before the index is a dot or slash. + *aResult = index > 0 && (uint32_t)index == aInput.Length() - aHost.Length() && + (aInput[index - 1] == '.' || aInput[index - 1] == '/'); + return NS_OK; +} + +void CheckForBrokenChromeURL(nsILoadInfo* aLoadInfo, nsIURI* aURI) { + if (!aURI) { + return; + } + nsAutoCString scheme; + aURI->GetScheme(scheme); + if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("resource")) { + return; + } + nsAutoCString host; + aURI->GetHost(host); + // Ignore test hits. + if (host.EqualsLiteral("mochitests") || host.EqualsLiteral("reftest")) { + return; + } + + nsAutoCString filePath; + aURI->GetFilePath(filePath); + // Fluent likes checking for files everywhere and expects failure. + if (StringEndsWith(filePath, ".ftl"_ns)) { + return; + } + + // Ignore fetches/xhrs, as they are frequently used in a way where + // non-existence is OK (ie with fallbacks). This risks false negatives (ie + // files that *should* be there but aren't) - which we accept for now. + ExtContentPolicy policy = aLoadInfo + ? aLoadInfo->GetExternalContentPolicyType() + : ExtContentPolicy::TYPE_OTHER; + if (policy == ExtContentPolicy::TYPE_FETCH || + policy == ExtContentPolicy::TYPE_XMLHTTPREQUEST) { + return; + } + + if (aLoadInfo) { + bool shouldSkipCheckForBrokenURLOrZeroSized; + MOZ_ALWAYS_SUCCEEDS(aLoadInfo->GetShouldSkipCheckForBrokenURLOrZeroSized( + &shouldSkipCheckForBrokenURLOrZeroSized)); + if (shouldSkipCheckForBrokenURLOrZeroSized) { + return; + } + } + + nsCString spec; + aURI->GetSpec(spec); + +#ifdef ANDROID + // Various toolkit files use this and are shipped on android, but + // info-pages.css and aboutLicense.css are not - bug 1808987 + if (StringEndsWith(spec, "info-pages.css"_ns) || + StringEndsWith(spec, "aboutLicense.css"_ns) || + // Error page CSS is also missing: bug 1810039 + StringEndsWith(spec, "aboutNetError.css"_ns) || + StringEndsWith(spec, "aboutHttpsOnlyError.css"_ns) || + StringEndsWith(spec, "error-pages.css"_ns) || + // popup.css is used in a single mochitest: bug 1810577 + StringEndsWith(spec, "/popup.css"_ns) || + // Used by an extension installation test - bug 1809650 + StringBeginsWith(spec, "resource://android/assets/web_extensions/"_ns)) { + return; + } +#endif + + // DTD files from gre may not exist when requested by tests. + if (StringBeginsWith(spec, "resource://gre/res/dtd/"_ns)) { + return; + } + + // The background task machinery allows the caller to specify a JSM on the + // command line, which is then looked up in both app-specific and toolkit-wide + // locations. + if (spec.Find("backgroundtasks") != kNotFound) { + return; + } + + if (xpc::IsInAutomation()) { +#ifdef DEBUG + if (NS_IsMainThread()) { + nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect(); + Unused << xpc->DebugDumpJSStack(false, false, false); + } +#endif + MOZ_CRASH_UNSAFE_PRINTF("Missing chrome or resource URLs: %s", spec.get()); + } else { + printf_stderr("Missing chrome or resource URL: %s\n", spec.get()); + } +} + +bool IsCoepCredentiallessEnabled(bool aIsOriginTrialCoepCredentiallessEnabled) { + return StaticPrefs:: + browser_tabs_remote_coep_credentialless_DoNotUseDirectly() || + aIsOriginTrialCoepCredentiallessEnabled; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h new file mode 100644 index 0000000000..c1f30a56ed --- /dev/null +++ b/netwerk/base/nsNetUtil.h @@ -0,0 +1,1105 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 sts=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsNetUtil_h__ +#define nsNetUtil_h__ + +#include <functional> +#include "mozilla/Maybe.h" +#include "mozilla/ResultExtensions.h" +#include "nsAttrValue.h" +#include "nsCOMPtr.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILoadGroup.h" +#include "nsINestedURI.h" +#include "nsINetUtil.h" +#include "nsIRequest.h" +#include "nsILoadInfo.h" +#include "nsIIOService.h" +#include "nsIURI.h" +#include "mozilla/NotNull.h" +#include "mozilla/Services.h" +#include "mozilla/Unused.h" +#include "nsNetCID.h" +#include "nsReadableUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsTArray.h" + +class nsIPrincipal; +class nsIAsyncStreamCopier; +class nsIAuthPrompt; +class nsIAuthPrompt2; +class nsIChannel; +class nsIChannelPolicy; +class nsICookieJarSettings; +class nsIDownloadObserver; +class nsIEventTarget; +class nsIFileProtocolHandler; +class nsIFileRandomAccessStream; +class nsIHttpChannel; +class nsIInputStream; +class nsIInputStreamPump; +class nsIInterfaceRequestor; +class nsIOutputStream; +class nsIParentChannel; +class nsIPersistentProperties; +class nsIProxyInfo; +class nsIRandomAccessStream; +class nsIRequestObserver; +class nsISerialEventTarget; +class nsIStreamListener; +class nsIStreamLoader; +class nsIStreamLoaderObserver; +class nsIIncrementalStreamLoader; +class nsIIncrementalStreamLoaderObserver; + +namespace mozilla { +class Encoding; +class OriginAttributes; +class OriginTrials; +namespace dom { +class ClientInfo; +class PerformanceStorage; +class ServiceWorkerDescriptor; +} // namespace dom + +namespace ipc { +class FileDescriptor; +} // namespace ipc + +} // namespace mozilla + +template <class> +class nsCOMPtr; +template <typename> +struct already_AddRefed; + +already_AddRefed<nsIIOService> do_GetIOService(nsresult* error = nullptr); + +already_AddRefed<nsINetUtil> do_GetNetUtil(nsresult* error = nullptr); + +// private little helper function... don't call this directly! +nsresult net_EnsureIOService(nsIIOService** ios, nsCOMPtr<nsIIOService>& grip); + +nsresult NS_NewURI(nsIURI** aURI, const nsACString& spec, + const char* charset = nullptr, nsIURI* baseURI = nullptr); + +nsresult NS_NewURI(nsIURI** result, const nsACString& spec, + mozilla::NotNull<const mozilla::Encoding*> encoding, + nsIURI* baseURI = nullptr); + +nsresult NS_NewURI(nsIURI** result, const nsAString& spec, + const char* charset = nullptr, nsIURI* baseURI = nullptr); + +nsresult NS_NewURI(nsIURI** result, const nsAString& spec, + mozilla::NotNull<const mozilla::Encoding*> encoding, + nsIURI* baseURI = nullptr); + +nsresult NS_NewURI(nsIURI** result, const char* spec, + nsIURI* baseURI = nullptr); + +nsresult NS_NewFileURI( + nsIURI** result, nsIFile* spec, + nsIIOService* ioService = + nullptr); // pass in nsIIOService to optimize callers + +// Functions for adding additional encoding to a URL for compatibility with +// Apple's NSURL class URLWithString method. +// +// @param aResult +// Out parameter for the encoded URL spec +// @param aSpec +// The spec for the URL to be encoded +nsresult NS_GetSpecWithNSURLEncoding(nsACString& aResult, + const nsACString& aSpec); +// @param aResult +// Out parameter for the encoded URI +// @param aSpec +// The spec for the URL to be encoded +nsresult NS_NewURIWithNSURLEncoding(nsIURI** aResult, const nsACString& aSpec); + +// These methods will only mutate the URI if the ref of aInput doesn't already +// match the ref we are trying to set. +// If aInput has no ref, and we are calling NS_GetURIWithoutRef, or +// NS_GetURIWithNewRef with an empty string, then aOutput will be the same +// as aInput. The same is true if aRef is already equal to the ref of aInput. +// This is OK because URIs are immutable and threadsafe. +// If the URI doesn't support ref fragments aOutput will be the same as aInput. +nsresult NS_GetURIWithNewRef(nsIURI* aInput, const nsACString& aRef, + nsIURI** aOutput); +nsresult NS_GetURIWithoutRef(nsIURI* aInput, nsIURI** aOutput); + +nsresult NS_GetSanitizedURIStringFromURI(nsIURI* aUri, + nsAString& aSanitizedSpec); + +/* + * How to create a new Channel, using NS_NewChannel, + * NS_NewChannelWithTriggeringPrincipal, + * NS_NewInputStreamChannel, NS_NewChannelInternal + * and it's variations: + * + * What specific API function to use: + * * The NS_NewChannelInternal functions should almost never be directly + * called outside of necko code. + * * If possible, use NS_NewChannel() providing a loading *nsINode* + * * If no loading *nsINode* is available, try calling NS_NewChannel() providing + * a loading *ClientInfo*. + * * If no loading *nsINode* or *ClientInfo* are available, call NS_NewChannel() + * providing a loading *nsIPrincipal*. + * * Call NS_NewChannelWithTriggeringPrincipal if the triggeringPrincipal + * is different from the loadingPrincipal. + * * Call NS_NewChannelInternal() providing aLoadInfo object in cases where + * you already have loadInfo object, e.g in case of a channel redirect. + * + * @param aURI + * nsIURI from which to make a channel + * @param aLoadingNode + * @param aLoadingPrincipal + * @param aTriggeringPrincipal + * @param aSecurityFlags + * @param aContentPolicyType + * These will be used as values for the nsILoadInfo object on the + * created channel. For details, see nsILoadInfo in nsILoadInfo.idl + * + * Please note, if you provide both a loadingNode and a loadingPrincipal, + * then loadingPrincipal must be equal to loadingNode->NodePrincipal(). + * But less error prone is to just supply a loadingNode. + * + * Note, if you provide a loading ClientInfo its principal must match the + * loading principal. Currently you must pass both as the loading principal + * may have additional mutable values like CSP on it. In the future these + * will be removed from nsIPrincipal and the API can be changed to take just + * the loading ClientInfo. + * + * Keep in mind that URIs coming from a webpage should *never* use the + * systemPrincipal as the loadingPrincipal. + */ +nsresult NS_NewChannelInternal( + nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + const mozilla::Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo, + const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings = nullptr, + mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr, + nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0, + bool aSkipCheckForBrokenURLOrZeroSized = false); + +// See NS_NewChannelInternal for usage and argument description +nsresult NS_NewChannelInternal( + nsIChannel** outChannel, nsIURI* aUri, nsILoadInfo* aLoadInfo, + mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr, + nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService* aIoService = nullptr); + +// See NS_NewChannelInternal for usage and argument description +nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */ +NS_NewChannelWithTriggeringPrincipal( + nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode, + nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr, + nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService* aIoService = nullptr); + +// See NS_NewChannelInternal for usage and argument description +nsresult NS_NewChannelWithTriggeringPrincipal( + nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings = nullptr, + mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr, + nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService* aIoService = nullptr); + +// See NS_NewChannelInternal for usage and argument description +nsresult NS_NewChannelWithTriggeringPrincipal( + nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + const mozilla::dom::ClientInfo& aLoadingClientInfo, + const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings = nullptr, + mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr, + nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService* aIoService = nullptr); + +// See NS_NewChannelInternal for usage and argument description +nsresult NS_NewChannel( + nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr, + nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0, + bool aSkipCheckForBrokenURLOrZeroSized = false); + +// See NS_NewChannelInternal for usage and argument description +nsresult NS_NewChannel( + nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings = nullptr, + mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr, + nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0, + bool aSkipCheckForBrokenURLOrZeroSized = false); + +// See NS_NewChannelInternal for usage and argument description +nsresult NS_NewChannel( + nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal, + const mozilla::dom::ClientInfo& aLoadingClientInfo, + const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + nsICookieJarSettings* aCookieJarSettings = nullptr, + mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr, + nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0, + bool aSkipCheckForBrokenURLOrZeroSized = false); + +nsresult NS_GetIsDocumentChannel(nsIChannel* aChannel, bool* aIsDocument); + +nsresult NS_MakeAbsoluteURI(nsACString& result, const nsACString& spec, + nsIURI* baseURI); + +nsresult NS_MakeAbsoluteURI(char** result, const char* spec, nsIURI* baseURI); + +nsresult NS_MakeAbsoluteURI(nsAString& result, const nsAString& spec, + nsIURI* baseURI); + +/** + * This function is a helper function to get a scheme's default port. + */ +int32_t NS_GetDefaultPort(const char* scheme, + nsIIOService* ioService = nullptr); + +/** + * This function is a helper function to apply the ToAscii conversion + * to a string + */ +bool NS_StringToACE(const nsACString& idn, nsACString& result); + +/** + * This function is a helper function to get a protocol's default port if the + * URI does not specify a port explicitly. Returns -1 if this protocol has no + * concept of ports or if there was an error getting the port. + */ +int32_t NS_GetRealPort(nsIURI* aURI); + +nsresult NS_NewInputStreamChannelInternal( + nsIChannel** outChannel, nsIURI* aUri, + already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType, + const nsACString& aContentCharset, nsILoadInfo* aLoadInfo); + +nsresult NS_NewInputStreamChannelInternal( + nsIChannel** outChannel, nsIURI* aUri, + already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType, + const nsACString& aContentCharset, nsINode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType); + +nsresult NS_NewInputStreamChannel(nsIChannel** outChannel, nsIURI* aUri, + already_AddRefed<nsIInputStream> aStream, + nsIPrincipal* aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + const nsACString& aContentType = ""_ns, + const nsACString& aContentCharset = ""_ns); + +nsresult NS_NewInputStreamChannelInternal( + nsIChannel** outChannel, nsIURI* aUri, const nsAString& aData, + const nsACString& aContentType, nsINode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + bool aIsSrcdocChannel = false); + +nsresult NS_NewInputStreamChannelInternal(nsIChannel** outChannel, nsIURI* aUri, + const nsAString& aData, + const nsACString& aContentType, + nsILoadInfo* aLoadInfo, + bool aIsSrcdocChannel = false); + +nsresult NS_NewInputStreamChannel(nsIChannel** outChannel, nsIURI* aUri, + const nsAString& aData, + const nsACString& aContentType, + nsIPrincipal* aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + bool aIsSrcdocChannel = false); + +nsresult NS_NewInputStreamPump( + nsIInputStreamPump** aResult, already_AddRefed<nsIInputStream> aStream, + uint32_t aSegsize = 0, uint32_t aSegcount = 0, bool aCloseWhenDone = false, + nsISerialEventTarget* aMainThreadTarget = nullptr); + +nsresult NS_NewLoadGroup(nsILoadGroup** result, nsIRequestObserver* obs); + +// Create a new nsILoadGroup that will match the given principal. +nsresult NS_NewLoadGroup(nsILoadGroup** aResult, nsIPrincipal* aPrincipal); + +// Determine if the given loadGroup/principal pair will produce a principal +// with similar permissions when passed to NS_NewChannel(). This checks for +// things like making sure the browser element flag matches. Without +// an appropriate load group these values can be lost when getting the result +// principal back out of the channel. Null principals are also always allowed +// as they do not have permissions to actually use the load group. +bool NS_LoadGroupMatchesPrincipal(nsILoadGroup* aLoadGroup, + nsIPrincipal* aPrincipal); + +nsresult NS_NewDownloader(nsIStreamListener** result, + nsIDownloadObserver* observer, + nsIFile* downloadLocation = nullptr); + +nsresult NS_NewStreamLoader(nsIStreamLoader** result, + nsIStreamLoaderObserver* observer, + nsIRequestObserver* requestObserver = nullptr); + +nsresult NS_NewIncrementalStreamLoader( + nsIIncrementalStreamLoader** result, + nsIIncrementalStreamLoaderObserver* observer); + +nsresult NS_NewStreamLoaderInternal( + nsIStreamLoader** outStream, nsIURI* aUri, + nsIStreamLoaderObserver* aObserver, nsINode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL); + +nsresult NS_NewStreamLoader(nsIStreamLoader** outStream, nsIURI* aUri, + nsIStreamLoaderObserver* aObserver, + nsINode* aLoadingNode, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL); + +nsresult NS_NewStreamLoader(nsIStreamLoader** outStream, nsIURI* aUri, + nsIStreamLoaderObserver* aObserver, + nsIPrincipal* aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsILoadGroup* aLoadGroup = nullptr, + nsIInterfaceRequestor* aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL); + +nsresult NS_NewSyncStreamListener(nsIStreamListener** result, + nsIInputStream** stream); + +/** + * Implement the nsIChannel::Open(nsIInputStream**) method using the channel's + * AsyncOpen method. + * + * NOTE: Reading from the returned nsIInputStream may spin the current + * thread's event queue, which could result in any event being processed. + */ +nsresult NS_ImplementChannelOpen(nsIChannel* channel, nsIInputStream** result); + +nsresult NS_NewRequestObserverProxy(nsIRequestObserver** result, + nsIRequestObserver* observer, + nsISupports* context); + +nsresult NS_NewSimpleStreamListener(nsIStreamListener** result, + nsIOutputStream* sink, + nsIRequestObserver* observer = nullptr); + +nsresult NS_CheckPortSafety(int32_t port, const char* scheme, + nsIIOService* ioService = nullptr); + +// Determine if this URI is using a safe port. +nsresult NS_CheckPortSafety(nsIURI* uri); + +nsresult NS_NewProxyInfo(const nsACString& type, const nsACString& host, + int32_t port, uint32_t flags, nsIProxyInfo** result); + +nsresult NS_GetFileProtocolHandler(nsIFileProtocolHandler** result, + nsIIOService* ioService = nullptr); + +nsresult NS_GetFileFromURLSpec(const nsACString& inURL, nsIFile** result, + nsIIOService* ioService = nullptr); + +nsresult NS_GetURLSpecFromFile(nsIFile* file, nsACString& url, + nsIIOService* ioService = nullptr); + +/** + * Converts the nsIFile to the corresponding URL string. + * Should only be called on files which are not directories, + * is otherwise identical to NS_GetURLSpecFromFile, but is + * usually more efficient. + * Warning: this restriction may not be enforced at runtime! + */ +nsresult NS_GetURLSpecFromActualFile(nsIFile* file, nsACString& url, + nsIIOService* ioService = nullptr); + +/** + * Converts the nsIFile to the corresponding URL string. + * Should only be called on files which are directories, + * is otherwise identical to NS_GetURLSpecFromFile, but is + * usually more efficient. + * Warning: this restriction may not be enforced at runtime! + */ +nsresult NS_GetURLSpecFromDir(nsIFile* file, nsACString& url, + nsIIOService* ioService = nullptr); + +/** + * Obtains the referrer for a given channel. This first tries to obtain the + * referrer from the property docshell.internalReferrer, and if that doesn't + * work and the channel is an nsIHTTPChannel, we check it's referrer property. + * + */ +void NS_GetReferrerFromChannel(nsIChannel* channel, nsIURI** referrer); + +nsresult NS_ParseRequestContentType(const nsACString& rawContentType, + nsCString& contentType, + nsCString& contentCharset); + +nsresult NS_ParseResponseContentType(const nsACString& rawContentType, + nsCString& contentType, + nsCString& contentCharset); + +nsresult NS_ExtractCharsetFromContentType(const nsACString& rawContentType, + nsCString& contentCharset, + bool* hadCharset, + int32_t* charsetStart, + int32_t* charsetEnd); + +nsresult NS_NewLocalFileInputStream(nsIInputStream** result, nsIFile* file, + int32_t ioFlags = -1, int32_t perm = -1, + int32_t behaviorFlags = 0); + +mozilla::Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewLocalFileInputStream( + nsIFile* file, int32_t ioFlags = -1, int32_t perm = -1, + int32_t behaviorFlags = 0); + +nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result, nsIFile* file, + int32_t ioFlags = -1, int32_t perm = -1, + int32_t behaviorFlags = 0); + +mozilla::Result<nsCOMPtr<nsIOutputStream>, nsresult> +NS_NewLocalFileOutputStream(nsIFile* file, int32_t ioFlags = -1, + int32_t perm = -1, int32_t behaviorFlags = 0); + +nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result, + const mozilla::ipc::FileDescriptor& fd); + +// returns a file output stream which can be QI'ed to nsISafeOutputStream. +nsresult NS_NewAtomicFileOutputStream(nsIOutputStream** result, nsIFile* file, + int32_t ioFlags = -1, int32_t perm = -1, + int32_t behaviorFlags = 0); + +// returns a file output stream which can be QI'ed to nsISafeOutputStream. +nsresult NS_NewSafeLocalFileOutputStream(nsIOutputStream** result, + nsIFile* file, int32_t ioFlags = -1, + int32_t perm = -1, + int32_t behaviorFlags = 0); + +nsresult NS_NewLocalFileRandomAccessStream(nsIRandomAccessStream** result, + nsIFile* file, int32_t ioFlags = -1, + int32_t perm = -1, + int32_t behaviorFlags = 0); + +mozilla::Result<nsCOMPtr<nsIRandomAccessStream>, nsresult> +NS_NewLocalFileRandomAccessStream(nsIFile* file, int32_t ioFlags = -1, + int32_t perm = -1, int32_t behaviorFlags = 0); + +[[nodiscard]] nsresult NS_NewBufferedInputStream( + nsIInputStream** aResult, already_AddRefed<nsIInputStream> aInputStream, + uint32_t aBufferSize); + +mozilla::Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewBufferedInputStream( + already_AddRefed<nsIInputStream> aInputStream, uint32_t aBufferSize); + +// note: the resulting stream can be QI'ed to nsISafeOutputStream iff the +// provided stream supports it. +nsresult NS_NewBufferedOutputStream( + nsIOutputStream** aResult, already_AddRefed<nsIOutputStream> aOutputStream, + uint32_t aBufferSize); + +/** + * This function reads an inputStream and stores its content into a buffer. In + * general, you should avoid using this function because, it blocks the current + * thread until the operation is done. + * If the inputStream is async, the reading happens on an I/O thread. + * + * @param aInputStream the inputStream. + * @param aDest the destination buffer. if *aDest is null, it will be allocated + * with the size of the written data. if aDest is not null, aCount + * must greater than 0. + * @param aCount the amount of data to read. Use -1 if you want that all the + * stream is read. + * @param aWritten this pointer will be used to store the number of data + * written in the buffer. If you don't need, pass nullptr. + */ +nsresult NS_ReadInputStreamToBuffer(nsIInputStream* aInputStream, void** aDest, + int64_t aCount, + uint64_t* aWritten = nullptr); + +/** + * See the comment for NS_ReadInputStreamToBuffer + */ +nsresult NS_ReadInputStreamToString(nsIInputStream* aInputStream, + nsACString& aDest, int64_t aCount, + uint64_t* aWritten = nullptr); + +nsresult NS_LoadPersistentPropertiesFromURISpec( + nsIPersistentProperties** outResult, const nsACString& aSpec); + +/** + * NS_QueryNotificationCallbacks implements the canonical algorithm for + * querying interfaces from a channel's notification callbacks. It first + * searches the channel's notificationCallbacks attribute, and if the interface + * is not found there, then it inspects the notificationCallbacks attribute of + * the channel's loadGroup. + * + * Note: templatized only because nsIWebSocketChannel is currently not an + * nsIChannel. + */ +template <class T> +inline void NS_QueryNotificationCallbacks(T* channel, const nsIID& iid, + void** result) { + MOZ_ASSERT(channel, "null channel"); + *result = nullptr; + + nsCOMPtr<nsIInterfaceRequestor> cbs; + mozilla::Unused << channel->GetNotificationCallbacks(getter_AddRefs(cbs)); + if (cbs) cbs->GetInterface(iid, result); + if (!*result) { + // try load group's notification callbacks... + nsCOMPtr<nsILoadGroup> loadGroup; + mozilla::Unused << channel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) { + loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs)); + if (cbs) cbs->GetInterface(iid, result); + } + } +} + +// template helper: +// Note: "class C" templatized only because nsIWebSocketChannel is currently not +// an nsIChannel. + +template <class C, class T> +inline void NS_QueryNotificationCallbacks(C* channel, nsCOMPtr<T>& result) { + NS_QueryNotificationCallbacks(channel, NS_GET_TEMPLATE_IID(T), + getter_AddRefs(result)); +} + +/** + * Alternate form of NS_QueryNotificationCallbacks designed for use by + * nsIChannel implementations. + */ +inline void NS_QueryNotificationCallbacks(nsIInterfaceRequestor* callbacks, + nsILoadGroup* loadGroup, + const nsIID& iid, void** result) { + *result = nullptr; + + if (callbacks) callbacks->GetInterface(iid, result); + if (!*result) { + // try load group's notification callbacks... + if (loadGroup) { + nsCOMPtr<nsIInterfaceRequestor> cbs; + loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs)); + if (cbs) cbs->GetInterface(iid, result); + } + } +} + +/** + * Returns true if channel is using Private Browsing, or false if not. + * Returns false if channel's callbacks don't implement nsILoadContext. + */ +bool NS_UsePrivateBrowsing(nsIChannel* channel); + +/** + * Returns true if the channel has visited any cross-origin URLs on any + * URLs that it was redirected through. + */ +bool NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport = false); + +/** + * Returns true if the channel has a safe method. + */ +bool NS_IsSafeMethodNav(nsIChannel* aChannel); + +// Unique first-party domain for separating the safebrowsing cookie. +// Note if this value is changed, code in test_cookiejars_safebrowsing.js and +// nsUrlClassifierHashCompleter.js should also be changed. +#define NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN \ + "safebrowsing.86868755-6b82-4842-b301-72671a0db32e.mozilla" + +// Unique first-party domain for separating about uri. +#define ABOUT_URI_FIRST_PARTY_DOMAIN \ + "about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla" + +/** + * Wraps an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2. This + * method is provided mainly for use by other methods in this file. + * + * *aAuthPrompt2 should be set to null before calling this function. + */ +void NS_WrapAuthPrompt(nsIAuthPrompt* aAuthPrompt, + nsIAuthPrompt2** aAuthPrompt2); + +/** + * Gets an auth prompt from an interface requestor. This takes care of wrapping + * an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2. + */ +void NS_QueryAuthPrompt2(nsIInterfaceRequestor* aCallbacks, + nsIAuthPrompt2** aAuthPrompt); + +/** + * Gets an nsIAuthPrompt2 from a channel. Use this instead of + * NS_QueryNotificationCallbacks for better backwards compatibility. + */ +void NS_QueryAuthPrompt2(nsIChannel* aChannel, nsIAuthPrompt2** aAuthPrompt); + +/* template helper */ +template <class T> +inline void NS_QueryNotificationCallbacks(nsIInterfaceRequestor* callbacks, + nsILoadGroup* loadGroup, + nsCOMPtr<T>& result) { + NS_QueryNotificationCallbacks(callbacks, loadGroup, NS_GET_TEMPLATE_IID(T), + getter_AddRefs(result)); +} + +/* template helper */ +template <class T> +inline void NS_QueryNotificationCallbacks( + const nsCOMPtr<nsIInterfaceRequestor>& aCallbacks, + const nsCOMPtr<nsILoadGroup>& aLoadGroup, nsCOMPtr<T>& aResult) { + NS_QueryNotificationCallbacks(aCallbacks.get(), aLoadGroup.get(), aResult); +} + +/* template helper */ +template <class T> +inline void NS_QueryNotificationCallbacks(const nsCOMPtr<nsIChannel>& aChannel, + nsCOMPtr<T>& aResult) { + NS_QueryNotificationCallbacks(aChannel.get(), aResult); +} + +/** + * This function returns a nsIInterfaceRequestor instance that returns the + * same result as NS_QueryNotificationCallbacks when queried. It is useful + * as the value for nsISocketTransport::securityCallbacks. + */ +nsresult NS_NewNotificationCallbacksAggregation( + nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup, + nsIEventTarget* target, nsIInterfaceRequestor** result); + +nsresult NS_NewNotificationCallbacksAggregation( + nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup, + nsIInterfaceRequestor** result); + +/** + * Helper function for testing online/offline state of the browser. + */ +bool NS_IsOffline(); + +/** + * Helper functions for implementing nsINestedURI::innermostURI. + * + * Note that NS_DoImplGetInnermostURI is "private" -- call + * NS_ImplGetInnermostURI instead. + */ +nsresult NS_DoImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result); + +nsresult NS_ImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result); + +/** + * Helper function for testing whether the given URI, or any of its + * inner URIs, has all the given protocol flags. + */ +nsresult NS_URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result); + +/** + * Helper function for getting the innermost URI for a given URI. The return + * value could be just the object passed in if it's not a nested URI. + */ +already_AddRefed<nsIURI> NS_GetInnermostURI(nsIURI* aURI); + +/** + * Helper function for getting the host name of the innermost URI for a given + * URI. The return value could be the host name of the URI passed in if it's + * not a nested URI. + */ +inline nsresult NS_GetInnermostURIHost(nsIURI* aURI, nsACString& aHost) { + aHost.Truncate(); + + // This block is optimized in order to avoid the overhead of calling + // NS_GetInnermostURI() which incurs a lot of overhead in terms of + // AddRef/Release calls. + nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(aURI); + if (nestedURI) { + // We have a nested URI! + nsCOMPtr<nsIURI> uri; + nsresult rv = nestedURI->GetInnermostURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = uri->GetAsciiHost(aHost); + if (NS_FAILED(rv)) { + return rv; + } + } else { + // We have a non-nested URI! + nsresult rv = aURI->GetAsciiHost(aHost); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +/** + * Get the "final" URI for a channel. This is either channel's load info + * resultPrincipalURI, if set, or GetOriginalURI. In most cases (but not all) + * load info resultPrincipalURI, if set, corresponds to URI of the channel if + * it's required to represent the actual principal for the channel. + */ +nsresult NS_GetFinalChannelURI(nsIChannel* channel, nsIURI** uri); + +// NS_SecurityHashURI must return the same hash value for any two URIs that +// compare equal according to NS_SecurityCompareURIs. Unfortunately, in the +// case of files, it's not clear we can do anything better than returning +// the schemeHash, so hashing files degenerates to storing them in a list. +uint32_t NS_SecurityHashURI(nsIURI* aURI); + +bool NS_SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI, + bool aStrictFileOriginPolicy); + +bool NS_URIIsLocalFile(nsIURI* aURI); + +// When strict file origin policy is enabled, SecurityCompareURIs will fail for +// file URIs that do not point to the same local file. This call provides an +// alternate file-specific origin check that allows target files that are +// contained in the same directory as the source. +// +// https://developer.mozilla.org/en-US/docs/Same-origin_policy_for_file:_URIs +bool NS_RelaxStrictFileOriginPolicy(nsIURI* aTargetURI, nsIURI* aSourceURI, + bool aAllowDirectoryTarget = false); + +bool NS_IsInternalSameURIRedirect(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, uint32_t aFlags); + +bool NS_IsHSTSUpgradeRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel, + uint32_t aFlags); + +bool NS_ShouldRemoveAuthHeaderOnRedirect(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aFlags); + +nsresult NS_LinkRedirectChannels(uint64_t channelId, + nsIParentChannel* parentChannel, + nsIChannel** _result); + +/** + * Returns nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP if `aHeader` is + * "require-corp" and nsILoadInfo::EMBEDDER_POLICY_NULL otherwise. + * + * See: https://mikewest.github.io/corpp/#parsing + */ +nsILoadInfo::CrossOriginEmbedderPolicy +NS_GetCrossOriginEmbedderPolicyFromHeader( + const nsACString& aHeader, bool aIsOriginTrialCoepCredentiallessEnabled); + +/** Given the first (disposition) token from a Content-Disposition header, + * tell whether it indicates the content is inline or attachment + * @param aDispToken the disposition token from the content-disposition header + */ +uint32_t NS_GetContentDispositionFromToken(const nsAString& aDispToken); + +/** Determine the disposition (inline/attachment) of the content based on the + * Content-Disposition header + * @param aHeader the content-disposition header (full value) + * @param aChan the channel the header came from + */ +uint32_t NS_GetContentDispositionFromHeader(const nsACString& aHeader, + nsIChannel* aChan = nullptr); + +/** Extracts the filename out of a content-disposition header + * @param aFilename [out] The filename. Can be empty on error. + * @param aDisposition Value of a Content-Disposition header + + */ +nsresult NS_GetFilenameFromDisposition(nsAString& aFilename, + const nsACString& aDisposition); + +/** + * Make sure Personal Security Manager is initialized + */ +void net_EnsurePSMInit(); + +/** + * Test whether a URI is "about:blank". |uri| must not be null + */ +bool NS_IsAboutBlank(nsIURI* uri); + +/** + * Test whether a URI is "about:srcdoc". |uri| must not be null + */ +bool NS_IsAboutSrcdoc(nsIURI* uri); + +nsresult NS_GenerateHostPort(const nsCString& host, int32_t port, + nsACString& hostLine); + +/** + * Sniff the content type for a given request or a given buffer. + * + * aSnifferType can be either NS_CONTENT_SNIFFER_CATEGORY or + * NS_DATA_SNIFFER_CATEGORY. The function returns the sniffed content type + * in the aSniffedType argument. This argument will not be modified if the + * content type could not be sniffed. + */ +void NS_SniffContent(const char* aSnifferType, nsIRequest* aRequest, + const uint8_t* aData, uint32_t aLength, + nsACString& aSniffedType); + +/** + * Whether the channel was created to load a srcdoc document. + * Note that view-source:about:srcdoc is classified as a srcdoc document by + * this function, which may not be applicable everywhere. + */ +bool NS_IsSrcdocChannel(nsIChannel* aChannel); + +/** + * Return true if the given string is a reasonable HTTP header value given the + * definition in RFC 2616 section 4.2. Currently we don't pay the cost to do + * full, sctrict validation here since it would require fulling parsing the + * value. + */ +bool NS_IsReasonableHTTPHeaderValue(const nsACString& aValue); + +/** + * Return true if the given string is a valid HTTP token per RFC 2616 section + * 2.2. + */ +bool NS_IsValidHTTPToken(const nsACString& aToken); + +/** + * Strip the leading or trailing HTTP whitespace per fetch spec section 2.2. + */ +void NS_TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest); + +template <typename Char> +constexpr bool NS_IsHTTPTokenPoint(Char aChar) { + using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type; + auto c = static_cast<UnsignedChar>(aChar); + return c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || + c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' || + c == '^' || c == '_' || c == '`' || c == '|' || c == '~' || + mozilla::IsAsciiAlphanumeric(c); +} + +template <typename Char> +constexpr bool NS_IsHTTPQuotedStringTokenPoint(Char aChar) { + using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type; + auto c = static_cast<UnsignedChar>(aChar); + return c == 0x9 || (c >= ' ' && c <= '~') || mozilla::IsNonAsciiLatin1(c); +} + +template <typename Char> +constexpr bool NS_IsHTTPWhitespace(Char aChar) { + using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type; + auto c = static_cast<UnsignedChar>(aChar); + return c == 0x9 || c == 0xA || c == 0xD || c == 0x20; +} + +/** + * Return true if the given request must be upgraded to HTTPS. + * If |aResultCallback| is provided and the storage is not ready to read, the + * result will be sent back through the callback and |aWillCallback| will be + * true. Otherwiew, the result will be set to |aShouldUpgrade| and + * |aWillCallback| is false. + */ +nsresult NS_ShouldSecureUpgrade( + nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIPrincipal* aChannelResultPrincipal, + bool aAllowSTS, const mozilla::OriginAttributes& aOriginAttributes, + bool& aShouldUpgrade, std::function<void(bool, nsresult)>&& aResultCallback, + bool& aWillCallback); + +/** + * Returns an https URI for channels that need to go through secure upgrades. + */ +nsresult NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI); + +nsresult NS_CompareLoadInfoAndLoadContext(nsIChannel* aChannel); + +/** + * Return true if this channel should be classified by the URL classifier. + */ +bool NS_ShouldClassifyChannel(nsIChannel* aChannel); + +/** + * Helper to set the blocking reason on loadinfo of the channel. + */ +nsresult NS_SetRequestBlockingReason(nsIChannel* channel, uint32_t reason); +nsresult NS_SetRequestBlockingReason(nsILoadInfo* loadInfo, uint32_t reason); +nsresult NS_SetRequestBlockingReasonIfNull(nsILoadInfo* loadInfo, + uint32_t reason); + +namespace mozilla { +namespace net { + +const static uint64_t kJS_MAX_SAFE_UINTEGER = +9007199254740991ULL; +const static int64_t kJS_MIN_SAFE_INTEGER = -9007199254740991LL; +const static int64_t kJS_MAX_SAFE_INTEGER = +9007199254740991LL; + +// Make sure a 64bit value can be captured by JS MAX_SAFE_INTEGER +bool InScriptableRange(int64_t val); + +// Make sure a 64bit value can be captured by JS MAX_SAFE_INTEGER +bool InScriptableRange(uint64_t val); + +/** + * Given the value of a single header field (such as + * Content-Disposition and Content-Type) and the name of a parameter + * (e.g. filename, name, charset), returns the value of the parameter. + * See nsIMIMEHeaderParam.idl for more information. + * + * @param aHeaderVal a header string to get the value of a parameter + * from. + * @param aParamName the name of a MIME header parameter (e.g. + * filename, name, charset). If empty or nullptr, + * returns the first (possibly) _unnamed_ 'parameter'. + * @return the value of <code>aParamName</code> in Unichar(UTF-16). + */ +nsresult GetParameterHTTP(const nsACString& aHeaderVal, const char* aParamName, + nsAString& aResult); + +/** + * Helper function that determines if channel is an HTTP POST. + * + * @param aChannel + * The channel to test + * + * @return True if channel is an HTTP post. + */ +bool ChannelIsPost(nsIChannel* aChannel); + +/** + * Convenience functions for verifying nsIURI schemes. These functions simply + * wrap aURI->SchemeIs(), but specify the protocol as part of the function name. + */ + +bool SchemeIsHTTP(nsIURI* aURI); +bool SchemeIsHTTPS(nsIURI* aURI); +bool SchemeIsJavascript(nsIURI* aURI); +bool SchemeIsChrome(nsIURI* aURI); +bool SchemeIsAbout(nsIURI* aURI); +bool SchemeIsBlob(nsIURI* aURI); +bool SchemeIsFile(nsIURI* aURI); +bool SchemeIsData(nsIURI* aURI); +bool SchemeIsViewSource(nsIURI* aURI); +bool SchemeIsResource(nsIURI* aURI); +bool SchemeIsFTP(nsIURI* aURI); + +// Helper functions for SetProtocol methods to follow +// step 2.1 in https://url.spec.whatwg.org/#scheme-state +bool SchemeIsSpecial(const nsACString&); +bool IsSchemeChangePermitted(nsIURI*, const nsACString&); +already_AddRefed<nsIURI> TryChangeProtocol(nsIURI*, const nsAString&); + +struct LinkHeader { + nsString mHref; + nsString mRel; + nsString mTitle; + nsString mNonce; + nsString mIntegrity; + nsString mSrcset; + nsString mSizes; + nsString mType; + nsString mMedia; + nsString mAnchor; + nsString mCrossOrigin; + nsString mReferrerPolicy; + nsString mAs; + nsString mFetchPriority; + + LinkHeader(); + void Reset(); + + nsresult NewResolveHref(nsIURI** aOutURI, nsIURI* aBaseURI) const; + + bool operator==(const LinkHeader& rhs) const; + + void MaybeUpdateAttribute(const nsAString& aAttribute, + const char16_t* aValue); +}; + +// Implements roughly step 2 to 4 of +// <https://httpwg.org/specs/rfc8288.html#parse-set>. +nsTArray<LinkHeader> ParseLinkHeader(const nsAString& aLinkData); + +enum ASDestination : uint8_t { + DESTINATION_INVALID, + DESTINATION_AUDIO, + DESTINATION_DOCUMENT, + DESTINATION_EMBED, + DESTINATION_FONT, + DESTINATION_IMAGE, + DESTINATION_MANIFEST, + DESTINATION_OBJECT, + DESTINATION_REPORT, + DESTINATION_SCRIPT, + DESTINATION_SERVICEWORKER, + DESTINATION_SHAREDWORKER, + DESTINATION_STYLE, + DESTINATION_TRACK, + DESTINATION_VIDEO, + DESTINATION_WORKER, + DESTINATION_XSLT, + DESTINATION_FETCH +}; + +void ParseAsValue(const nsAString& aValue, nsAttrValue& aResult); +nsContentPolicyType AsValueToContentPolicy(const nsAttrValue& aValue); +bool IsScriptLikeOrInvalid(const nsAString& aAs); + +bool CheckPreloadAttrs(const nsAttrValue& aAs, const nsAString& aType, + const nsAString& aMedia, + mozilla::dom::Document* aDocument); +void WarnIgnoredPreload(const mozilla::dom::Document&, nsIURI&); + +/** + * Returns true if the |aInput| in is part of the root domain of |aHost|. + * For example, if |aInput| is "www.mozilla.org", and we pass in + * "mozilla.org" as |aHost|, this will return true. It would return false + * the other way around. + * + * @param aInput The host to be analyzed. + * @param aHost The host to compare to. + */ +nsresult HasRootDomain(const nsACString& aInput, const nsACString& aHost, + bool* aResult); + +void CheckForBrokenChromeURL(nsILoadInfo* aLoadInfo, nsIURI* aURI); + +bool IsCoepCredentiallessEnabled(bool aIsOriginTrialCoepCredentiallessEnabled); + +} // namespace net +} // namespace mozilla + +#endif // !nsNetUtil_h__ diff --git a/netwerk/base/nsPACMan.cpp b/netwerk/base/nsPACMan.cpp new file mode 100644 index 0000000000..0cf66c8ee6 --- /dev/null +++ b/netwerk/base/nsPACMan.cpp @@ -0,0 +1,1009 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPACMan.h" + +#include "mozilla/Preferences.h" +#include "nsContentUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIAuthPrompt.h" +#include "nsIDHCPClient.h" +#include "nsIHttpChannel.h" +#include "nsIPrefBranch.h" +#include "nsIPromptFactory.h" +#include "nsIProtocolProxyService.h" +#include "nsISystemProxySettings.h" +#include "nsIOService.h" +#include "nsNetUtil.h" +#include "nsThreadUtils.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Try.h" + +//----------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +LazyLogModule gProxyLog("proxy"); + +#undef LOG +#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args) +#define MOZ_WPAD_URL "http://wpad/wpad.dat" +#define MOZ_DHCP_WPAD_OPTION 252 + +// These pointers are declared in nsProtocolProxyService.cpp +extern const char kProxyType_HTTPS[]; +extern const char kProxyType_DIRECT[]; + +// The PAC thread does evaluations of both PAC files and +// nsISystemProxySettings because they can both block the calling thread and we +// don't want that on the main thread + +// Check to see if the underlying request was not an error page in the case of +// a HTTP request. For other types of channels, just return true. +static bool HttpRequestSucceeded(nsIStreamLoader* loader) { + nsCOMPtr<nsIRequest> request; + loader->GetRequest(getter_AddRefs(request)); + + bool result = true; // default to assuming success + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request); + if (httpChannel) { + // failsafe + Unused << httpChannel->GetRequestSucceeded(&result); + } + + return result; +} + +// Read preference setting of extra JavaScript context heap size. +// PrefService tends to be run on main thread, where ProxyAutoConfig runs on +// ProxyResolution thread, so it's read here and passed to ProxyAutoConfig. +static uint32_t GetExtraJSContextHeapSize() { + MOZ_ASSERT(NS_IsMainThread()); + + static int32_t extraSize = -1; + + if (extraSize < 0) { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + int32_t value; + + if (prefs && + NS_SUCCEEDED(prefs->GetIntPref( + "network.proxy.autoconfig_extra_jscontext_heap_size", &value))) { + LOG(("autoconfig_extra_jscontext_heap_size: %d\n", value)); + + extraSize = value; + } + } + + return extraSize < 0 ? 0 : extraSize; +} + +// Read network proxy type from preference +// Used to verify that the preference is WPAD in nsPACMan::ConfigureWPAD +nsresult GetNetworkProxyTypeFromPref(int32_t* type) { + *type = 0; + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + + if (!prefs) { + LOG(("Failed to get a preference service object")); + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + nsresult rv = prefs->GetIntPref("network.proxy.type", type); + if (NS_FAILED(rv)) { + LOG(("Failed to retrieve network.proxy.type from prefs")); + return rv; + } + LOG(("network.proxy.type pref retrieved: %d\n", *type)); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +// The ExecuteCallback runnable is triggered by +// nsPACManCallback::OnQueryComplete on the Main thread when its completion is +// discovered on the pac thread + +class ExecuteCallback final : public Runnable { + public: + ExecuteCallback(nsPACManCallback* aCallback, nsresult status) + : Runnable("net::ExecuteCallback"), + mCallback(aCallback), + mStatus(status) {} + + void SetPACString(const nsACString& pacString) { mPACString = pacString; } + + void SetPACURL(const nsACString& pacURL) { mPACURL = pacURL; } + + NS_IMETHOD Run() override { + mCallback->OnQueryComplete(mStatus, mPACString, mPACURL); + mCallback = nullptr; + return NS_OK; + } + + private: + RefPtr<nsPACManCallback> mCallback; + nsresult mStatus; + nsCString mPACString; + nsCString mPACURL; +}; + +//----------------------------------------------------------------------------- + +// The PAC thread must be deleted from the main thread, this class +// acts as a proxy to do that, as the PACMan is reference counted +// and might be destroyed on either thread + +class ShutdownThread final : public Runnable { + public: + explicit ShutdownThread(nsIThread* thread) + : Runnable("net::ShutdownThread"), mThread(thread) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + mThread->Shutdown(); + return NS_OK; + } + + private: + nsCOMPtr<nsIThread> mThread; +}; + +// Dispatch this to wait until the PAC thread shuts down. + +class WaitForThreadShutdown final : public Runnable { + public: + explicit WaitForThreadShutdown(nsPACMan* aPACMan) + : Runnable("net::WaitForThreadShutdown"), mPACMan(aPACMan) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + if (mPACMan->mPACThread) { + mPACMan->mPACThread->Shutdown(); + mPACMan->mPACThread = nullptr; + } + return NS_OK; + } + + private: + RefPtr<nsPACMan> mPACMan; +}; + +//----------------------------------------------------------------------------- + +// PACLoadComplete allows the PAC thread to tell the main thread that +// the javascript PAC file has been installed (perhaps unsuccessfully) +// and that there is no reason to queue executions anymore + +class PACLoadComplete final : public Runnable { + public: + explicit PACLoadComplete(nsPACMan* aPACMan) + : Runnable("net::PACLoadComplete"), mPACMan(aPACMan) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + { + auto loader = mPACMan->mLoader.Lock(); + loader.ref() = nullptr; + } + mPACMan->PostProcessPendingQ(); + return NS_OK; + } + + private: + RefPtr<nsPACMan> mPACMan; +}; + +//----------------------------------------------------------------------------- + +// ConfigureWPADComplete allows the PAC thread to tell the main thread that +// the URL for the PAC file has been found +class ConfigureWPADComplete final : public Runnable { + public: + ConfigureWPADComplete(nsPACMan* aPACMan, const nsACString& aPACURISpec) + : Runnable("net::ConfigureWPADComplete"), + mPACMan(aPACMan), + mPACURISpec(aPACURISpec) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + mPACMan->AssignPACURISpec(mPACURISpec); + mPACMan->ContinueLoadingAfterPACUriKnown(); + return NS_OK; + } + + private: + RefPtr<nsPACMan> mPACMan; + nsCString mPACURISpec; +}; + +//----------------------------------------------------------------------------- + +// ExecutePACThreadAction is used to proxy actions from the main +// thread onto the PAC thread. There are 4 options: process the queue, +// cancel the queue, query DHCP for the PAC option +// and setup the javascript context with a new PAC file + +class ExecutePACThreadAction final : public Runnable { + public: + // by default we just process the queue + explicit ExecutePACThreadAction(nsPACMan* aPACMan) + : Runnable("net::ExecutePACThreadAction"), + mPACMan(aPACMan), + mCancel(false), + mCancelStatus(NS_OK), + mSetupPAC(false), + mExtraHeapSize(0), + mConfigureWPAD(false), + mShutdown(false) {} + + void CancelQueue(nsresult status, bool aShutdown) { + mCancel = true; + mCancelStatus = status; + mShutdown = aShutdown; + } + + void SetupPAC(const char* data, uint32_t dataLen, const nsACString& pacURI, + uint32_t extraHeapSize) { + mSetupPAC = true; + mSetupPACData.Assign(data, dataLen); + mSetupPACURI = pacURI; + mExtraHeapSize = extraHeapSize; + } + + void ConfigureWPAD() { mConfigureWPAD = true; } + + NS_IMETHOD Run() override { + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + if (mCancel) { + mPACMan->CancelPendingQ(mCancelStatus, mShutdown); + mCancel = false; + return NS_OK; + } + + if (mSetupPAC) { + mSetupPAC = false; + + nsCOMPtr<nsISerialEventTarget> target = mPACMan->GetNeckoTarget(); + mPACMan->mPAC->ConfigurePAC(mSetupPACURI, mSetupPACData, + mPACMan->mIncludePath, mExtraHeapSize, + target); + + RefPtr<PACLoadComplete> runnable = new PACLoadComplete(mPACMan); + mPACMan->Dispatch(runnable.forget()); + return NS_OK; + } + + if (mConfigureWPAD) { + nsAutoCString spec; + mConfigureWPAD = false; + mPACMan->ConfigureWPAD(spec); + RefPtr<ConfigureWPADComplete> runnable = + new ConfigureWPADComplete(mPACMan, spec); + mPACMan->Dispatch(runnable.forget()); + return NS_OK; + } + + mPACMan->ProcessPendingQ(); + return NS_OK; + } + + private: + RefPtr<nsPACMan> mPACMan; + + bool mCancel; + nsresult mCancelStatus; + + bool mSetupPAC; + uint32_t mExtraHeapSize; + nsCString mSetupPACData; + nsCString mSetupPACURI; + bool mConfigureWPAD; + bool mShutdown; +}; + +//----------------------------------------------------------------------------- + +PendingPACQuery::PendingPACQuery(nsPACMan* pacMan, nsIURI* uri, + nsPACManCallback* callback, uint32_t flags, + bool mainThreadResponse) + : Runnable("net::PendingPACQuery"), + mPort(0), + mFlags(flags), + mPACMan(pacMan), + mCallback(callback), + mOnMainThreadOnly(mainThreadResponse) { + uri->GetAsciiSpec(mSpec); + uri->GetAsciiHost(mHost); + uri->GetScheme(mScheme); + uri->GetPort(&mPort); +} + +void PendingPACQuery::Complete(nsresult status, const nsACString& pacString) { + if (!mCallback) return; + RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, status); + runnable->SetPACString(pacString); + if (mOnMainThreadOnly) { + mPACMan->Dispatch(runnable.forget()); + } else { + runnable->Run(); + } +} + +void PendingPACQuery::UseAlternatePACFile(const nsACString& pacURL) { + if (!mCallback) return; + + RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, NS_OK); + runnable->SetPACURL(pacURL); + if (mOnMainThreadOnly) { + mPACMan->Dispatch(runnable.forget()); + } else { + runnable->Run(); + } +} + +NS_IMETHODIMP +PendingPACQuery::Run() { + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + mPACMan->PostQuery(this); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +static bool sThreadLocalSetup = false; +static uint32_t sThreadLocalIndex = 0xdeadbeef; // out of range + +static const char* kPACIncludePath = + "network.proxy.autoconfig_url.include_path"; + +nsPACMan::nsPACMan(nsISerialEventTarget* mainThreadEventTarget) + : NeckoTargetHolder(mainThreadEventTarget), + mLoader("nsPACMan::mLoader"), + mLoadPending(false), + mShutdown(false), + mLoadFailureCount(0), + mInProgress(false), + mAutoDetect(false), + mWPADOverDHCPEnabled(false), + mProxyConfigType(0) { + MOZ_ASSERT(NS_IsMainThread(), "pacman must be created on main thread"); + mIncludePath = Preferences::GetBool(kPACIncludePath, false); + if (StaticPrefs::network_proxy_parse_pac_on_socket_process() && + gIOService->SocketProcessReady()) { + mPAC = MakeUnique<RemoteProxyAutoConfig>(); + } else { + mPAC = MakeUnique<ProxyAutoConfig>(); + if (!sThreadLocalSetup) { + sThreadLocalSetup = true; + PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr); + } + mPAC->SetThreadLocalIndex(sThreadLocalIndex); + } +} + +nsPACMan::~nsPACMan() { + MOZ_ASSERT(mShutdown, "Shutdown must be called before dtor."); + + if (mPACThread) { + if (NS_IsMainThread()) { + mPACThread->Shutdown(); + mPACThread = nullptr; + } else { + RefPtr<ShutdownThread> runnable = new ShutdownThread(mPACThread); + Dispatch(runnable.forget()); + } + } + +#ifdef DEBUG + { + auto loader = mLoader.Lock(); + NS_ASSERTION(loader.ref() == nullptr, "pac man not shutdown properly"); + } +#endif + + NS_ASSERTION(mPendingQ.isEmpty(), "pac man not shutdown properly"); +} + +void nsPACMan::Shutdown() { + MOZ_ASSERT(NS_IsMainThread(), "pacman must be shutdown on main thread"); + if (mShutdown) { + return; + } + + CancelExistingLoad(); + + if (mPACThread) { + PostCancelPendingQ(NS_ERROR_ABORT, /*aShutdown =*/true); + + // Shutdown is initiated from an observer. We don't want to block the + // observer service on thread shutdown so we post a shutdown runnable that + // will run after we return instead. + RefPtr<WaitForThreadShutdown> runnable = new WaitForThreadShutdown(this); + Dispatch(runnable.forget()); + } + + mShutdown = true; +} + +nsresult nsPACMan::DispatchToPAC(already_AddRefed<nsIRunnable> aEvent, + bool aSync) { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + + nsCOMPtr<nsIRunnable> e(aEvent); + + if (mShutdown) { + return NS_ERROR_NOT_AVAILABLE; + } + + // Lazily create the PAC thread. This method is main-thread only so we don't + // have to worry about threading issues here. + if (!mPACThread) { + MOZ_TRY(NS_NewNamedThread("ProxyResolution", getter_AddRefs(mPACThread))); + nsresult rv = mPAC->Init(mPACThread); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (aSync) { + return NS_DispatchAndSpinEventLoopUntilComplete( + "nsPACMan::DispatchToPAC"_ns, mPACThread, e.forget()); + } else { + return mPACThread->Dispatch(e.forget()); + } +} + +nsresult nsPACMan::AsyncGetProxyForURI(nsIURI* uri, nsPACManCallback* callback, + uint32_t flags, + bool mainThreadResponse) { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + if (mShutdown) return NS_ERROR_NOT_AVAILABLE; + + // Maybe Reload PAC + if (!mPACURISpec.IsEmpty() && !mScheduledReload.IsNull() && + TimeStamp::Now() > mScheduledReload) { + LOG(("nsPACMan::AsyncGetProxyForURI reload as scheduled\n")); + + LoadPACFromURI(mAutoDetect ? ""_ns : mPACURISpec, false); + } + + RefPtr<PendingPACQuery> query = + new PendingPACQuery(this, uri, callback, flags, mainThreadResponse); + + if (IsPACURI(uri)) { + // deal with this directly instead of queueing it + query->Complete(NS_OK, ""_ns); + return NS_OK; + } + + return DispatchToPAC(query.forget()); +} + +nsresult nsPACMan::PostQuery(PendingPACQuery* query) { + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + + if (mShutdown) { + query->Complete(NS_ERROR_NOT_AVAILABLE, ""_ns); + return NS_OK; + } + + // add a reference to the query while it is in the pending list + RefPtr<PendingPACQuery> addref(query); + mPendingQ.insertBack(addref.forget().take()); + ProcessPendingQ(); + return NS_OK; +} + +nsresult nsPACMan::LoadPACFromURI(const nsACString& aSpec) { + return LoadPACFromURI(aSpec, true); +} + +nsresult nsPACMan::LoadPACFromURI(const nsACString& aSpec, + bool aResetLoadFailureCount) { + NS_ENSURE_STATE(!mShutdown); + + nsCOMPtr<nsIStreamLoader> loader = + do_CreateInstance(NS_STREAMLOADER_CONTRACTID); + NS_ENSURE_STATE(loader); + + LOG(("nsPACMan::LoadPACFromURI aSpec: %s, aResetLoadFailureCount: %s\n", + aSpec.BeginReading(), aResetLoadFailureCount ? "true" : "false")); + + CancelExistingLoad(); + + { + auto locked = mLoader.Lock(); + locked.ref() = loader.forget(); + } + mPACURIRedirectSpec.Truncate(); + mNormalPACURISpec.Truncate(); // set at load time + if (aResetLoadFailureCount) { + mLoadFailureCount = 0; + } + mAutoDetect = aSpec.IsEmpty(); + mPACURISpec.Assign(aSpec); + + // reset to Null + mScheduledReload = TimeStamp(); + + // if we're on the main thread here so we can get hold of prefs, + // we check that we have WPAD preffed on if we're auto-detecting + if (mAutoDetect && NS_IsMainThread()) { + nsresult rv = GetNetworkProxyTypeFromPref(&mProxyConfigType); + if (NS_FAILED(rv)) { + return rv; + } + if (mProxyConfigType != nsIProtocolProxyService::PROXYCONFIG_WPAD) { + LOG( + ("LoadPACFromURI - Aborting WPAD autodetection because the pref " + "doesn't match anymore")); + return NS_BINDING_ABORTED; + } + } + // Since we might get called from nsProtocolProxyService::Init, we need to + // post an event back to the main thread before we try to use the IO service. + // + // But, we need to flag ourselves as loading, so that we queue up any PAC + // queries the enter between now and when we actually load the PAC file. + + if (!mLoadPending) { + nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( + "nsPACMan::StartLoading", this, &nsPACMan::StartLoading); + nsresult rv = + NS_IsMainThread() + ? Dispatch(runnable.forget()) + : GetCurrentSerialEventTarget()->Dispatch(runnable.forget()); + if (NS_FAILED(rv)) return rv; + mLoadPending = true; + } + + return NS_OK; +} + +nsresult nsPACMan::GetPACFromDHCP(nsACString& aSpec) { + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + if (!mDHCPClient) { + LOG( + ("nsPACMan::GetPACFromDHCP DHCP option %d query failed because there " + "is no DHCP client available\n", + MOZ_DHCP_WPAD_OPTION)); + return NS_ERROR_NOT_IMPLEMENTED; + } + nsresult rv; + rv = mDHCPClient->GetOption(MOZ_DHCP_WPAD_OPTION, aSpec); + if (NS_FAILED(rv)) { + LOG(( + "nsPACMan::GetPACFromDHCP DHCP option %d query failed with result %d\n", + MOZ_DHCP_WPAD_OPTION, (uint32_t)rv)); + } else { + LOG( + ("nsPACMan::GetPACFromDHCP DHCP option %d query succeeded, finding PAC " + "URL %s\n", + MOZ_DHCP_WPAD_OPTION, aSpec.BeginReading())); + } + return rv; +} + +nsresult nsPACMan::ConfigureWPAD(nsACString& aSpec) { + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + + if (mProxyConfigType != nsIProtocolProxyService::PROXYCONFIG_WPAD) { + LOG( + ("ConfigureWPAD - Aborting WPAD autodetection because the pref " + "doesn't match anymore")); + return NS_BINDING_ABORTED; + } + + aSpec.Truncate(); + if (mWPADOverDHCPEnabled) { + GetPACFromDHCP(aSpec); + } + + if (aSpec.IsEmpty()) { + // We diverge from the WPAD spec here in that we don't walk the + // hosts's FQDN, stripping components until we hit a TLD. Doing so + // is dangerous in the face of an incomplete list of TLDs, and TLDs + // get added over time. We could consider doing only a single + // substitution of the first component, if that proves to help + // compatibility. + aSpec.AssignLiteral(MOZ_WPAD_URL); + } + return NS_OK; +} + +void nsPACMan::AssignPACURISpec(const nsACString& aSpec) { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + mPACURISpec.Assign(aSpec); +} + +void nsPACMan::StartLoading() { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + mLoadPending = false; + + { + // CancelExistingLoad was called... + nsCOMPtr<nsIStreamLoader> loader; + { + auto locked = mLoader.Lock(); + loader = locked.ref(); + } + if (!loader) { + PostCancelPendingQ(NS_ERROR_ABORT); + return; + } + } + + if (mAutoDetect) { + nsresult rv = GetNetworkProxyTypeFromPref(&mProxyConfigType); + if (NS_FAILED(rv)) { + NS_WARNING( + "Could not retrieve Network Proxy Type pref when auto-detecting " + "proxy. Halting."); + return; + } + RefPtr<ExecutePACThreadAction> wpadConfigurer = + new ExecutePACThreadAction(this); + wpadConfigurer->ConfigureWPAD(); + DispatchToPAC(wpadConfigurer.forget()); + } else { + ContinueLoadingAfterPACUriKnown(); + } +} + +void nsPACMan::ContinueLoadingAfterPACUriKnown() { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + + nsCOMPtr<nsIStreamLoader> loader; + { + auto locked = mLoader.Lock(); + loader = locked.ref(); + } + + // CancelExistingLoad was called... + if (!loader) { + PostCancelPendingQ(NS_ERROR_ABORT); + return; + } + if (NS_SUCCEEDED(loader->Init(this, nullptr))) { + // Always hit the origin server when loading PAC. + nsCOMPtr<nsIIOService> ios = do_GetIOService(); + if (ios) { + nsCOMPtr<nsIChannel> channel; + nsCOMPtr<nsIURI> pacURI; + NS_NewURI(getter_AddRefs(pacURI), mPACURISpec); + + // NOTE: This results in GetProxyForURI being called + if (pacURI) { + nsresult rv = pacURI->GetSpec(mNormalPACURISpec); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + NS_NewChannel(getter_AddRefs(channel), pacURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // nsICookieJarSettings + nullptr, // PerformanceStorage + nullptr, // aLoadGroup + nullptr, // aCallbacks + nsIRequest::LOAD_NORMAL, ios); + } else { + LOG(("nsPACMan::StartLoading Failed pacspec uri conversion %s\n", + mPACURISpec.get())); + } + + if (channel) { + // allow deprecated HTTP request from SystemPrincipal + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + loadInfo->SetAllowDeprecatedSystemRequests(true); + loadInfo->SetHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_EXEMPT); + + channel->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE); + channel->SetNotificationCallbacks(this); + if (NS_SUCCEEDED(channel->AsyncOpen(loader))) return; + } + } + } + + CancelExistingLoad(); + PostCancelPendingQ(NS_ERROR_UNEXPECTED); +} + +void nsPACMan::OnLoadFailure() { + int32_t minInterval = 5; // 5 seconds + int32_t maxInterval = 300; // 5 minutes + + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefs) { + prefs->GetIntPref("network.proxy.autoconfig_retry_interval_min", + &minInterval); + prefs->GetIntPref("network.proxy.autoconfig_retry_interval_max", + &maxInterval); + } + + int32_t interval = minInterval << mLoadFailureCount++; // seconds + if (!interval || interval > maxInterval) interval = maxInterval; + + mScheduledReload = TimeStamp::Now() + TimeDuration::FromSeconds(interval); + + LOG(("OnLoadFailure: retry in %d seconds (%d fails)\n", interval, + (uint32_t)mLoadFailureCount)); + + // while we wait for the retry queued members should try direct + // even if that means fast failure. + PostCancelPendingQ(NS_ERROR_NOT_AVAILABLE); +} + +void nsPACMan::CancelExistingLoad() { + nsCOMPtr<nsIStreamLoader> loader; + { + auto locked = mLoader.Lock(); + loader.swap(*locked); + } + if (loader) { + nsCOMPtr<nsIRequest> request; + loader->GetRequest(getter_AddRefs(request)); + if (request) { + request->Cancel(NS_ERROR_ABORT); + } + } +} + +void nsPACMan::PostProcessPendingQ() { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this); + DispatchToPAC(pending.forget()); +} + +void nsPACMan::PostCancelPendingQ(nsresult status, bool aShutdown) { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this); + pending->CancelQueue(status, aShutdown); + DispatchToPAC(pending.forget()); +} + +void nsPACMan::CancelPendingQ(nsresult status, bool aShutdown) { + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + RefPtr<PendingPACQuery> query; + + while (!mPendingQ.isEmpty()) { + query = dont_AddRef(mPendingQ.popLast()); + query->Complete(status, ""_ns); + } + + if (aShutdown) { + mPAC->Shutdown(); + } +} + +void nsPACMan::ProcessPendingQ() { + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + while (ProcessPending()) { + ; + } + + if (mShutdown) { + mPAC->Shutdown(); + } else { + // do GC while the thread has nothing pending + mPAC->GC(); + } +} + +// returns true if progress was made by shortening the queue +bool nsPACMan::ProcessPending() { + if (mPendingQ.isEmpty()) return false; + + // queue during normal load, but if we are retrying a failed load then + // fast fail the queries + if (mInProgress || (IsLoading() && !mLoadFailureCount)) return false; + + RefPtr<PendingPACQuery> query(dont_AddRef(mPendingQ.popFirst())); + + // Having |mLoadFailureCount > 0| means we haven't had a sucessful PAC load + // yet. We should use DIRECT instead. + if (mShutdown || IsLoading() || mLoadFailureCount > 0) { + query->Complete(NS_ERROR_NOT_AVAILABLE, ""_ns); + return true; + } + + nsAutoCString pacString; + bool completed = false; + mInProgress = true; + nsAutoCString PACURI; + + // first we need to consider the system proxy changing the pac url + if (mSystemProxySettings && + NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) && + !PACURI.IsEmpty() && !PACURI.Equals(mPACURISpec)) { + query->UseAlternatePACFile(PACURI); + LOG(("Use PAC from system settings: %s\n", PACURI.get())); + completed = true; + } + + // now try the system proxy settings for this particular url if + // PAC was not specified + if (!completed && mSystemProxySettings && PACURI.IsEmpty() && + NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI( + query->mSpec, query->mScheme, query->mHost, query->mPort, + pacString))) { + if (query->mFlags & nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY && + query->mFlags & nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY) { + if (StringBeginsWith(pacString, nsDependentCString(kProxyType_DIRECT), + nsCaseInsensitiveUTF8StringComparator)) { + // DIRECT indicates that system proxy settings are not configured to use + // SOCKS proxy. Try https proxy as a secondary preferrable proxy. This + // is mainly for websocket whose precedence is SOCKS > HTTPS > DIRECT. + NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI( + query->mSpec, nsDependentCString(kProxyType_HTTPS), query->mHost, + query->mPort, pacString)); + } + } + LOG(("Use proxy from system settings: %s\n", pacString.get())); + query->Complete(NS_OK, pacString); + completed = true; + } + + // the systemproxysettings didn't complete the resolution. try via PAC + if (!completed) { + auto callback = [query(query)](nsresult aStatus, + const nsACString& aResult) { + LOG(("Use proxy from PAC: %s\n", PromiseFlatCString(aResult).get())); + query->Complete(aStatus, aResult); + }; + mPAC->GetProxyForURIWithCallback(query->mSpec, query->mHost, + std::move(callback)); + } + + mInProgress = false; + return true; +} + +NS_IMPL_ISUPPORTS(nsPACMan, nsIStreamLoaderObserver, nsIInterfaceRequestor, + nsIChannelEventSink) + +NS_IMETHODIMP +nsPACMan::OnStreamComplete(nsIStreamLoader* loader, nsISupports* context, + nsresult status, uint32_t dataLen, + const uint8_t* data) { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + + bool loadSucceeded = NS_SUCCEEDED(status) && HttpRequestSucceeded(loader); + { + auto locked = mLoader.Lock(); + if (locked.ref() != loader) { + // If this happens, then it means that LoadPACFromURI was called more + // than once before the initial call completed. In this case, status + // should be NS_ERROR_ABORT, and if so, then we know that we can and + // should delay any processing. + LOG(("OnStreamComplete: called more than once\n")); + if (status == NS_ERROR_ABORT) { + return NS_OK; + } + } else if (!loadSucceeded) { + // We have to clear the loader to indicate that we are not loading PAC + // currently. + // Note that we can only clear the loader when |loader| and |mLoader| are + // the same one. + locked.ref() = nullptr; + } + } + + LOG(("OnStreamComplete: entry\n")); + + if (loadSucceeded) { + // Get the URI spec used to load this PAC script. + nsAutoCString pacURI; + { + nsCOMPtr<nsIRequest> request; + loader->GetRequest(getter_AddRefs(request)); + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + if (channel) { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + if (uri) uri->GetAsciiSpec(pacURI); + } + } + + nsCOMPtr<nsIProtocolProxyService> pps = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); + MOZ_ASSERT(pps); + if (pps) { + pps->NotifyProxyConfigChangedInternal(); + } + + // We succeeded in loading the pac file using a bunch of interfaces that are + // main thread only. Unfortunately, we have to initialize the instance of + // the PAC evaluator (NS_PROXYAUTOCONFIG_CONTRACTID) on the PAC thread, + // because that's where it will be used. + RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this); + pending->SetupPAC(reinterpret_cast<const char*>(data), dataLen, pacURI, + GetExtraJSContextHeapSize()); + DispatchToPAC(pending.forget()); + + LOG(("OnStreamComplete: process the PAC contents\n")); + + // Even if the PAC file could not be parsed, we did succeed in loading the + // data for it. + mLoadFailureCount = 0; + } else { + // We were unable to load the PAC file (presumably because of a network + // failure). Try again a little later. + LOG(("OnStreamComplete: unable to load PAC, retry later\n")); + OnLoadFailure(); + } + + if (NS_SUCCEEDED(status)) { + PostProcessPendingQ(); + } else { + PostCancelPendingQ(status); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPACMan::GetInterface(const nsIID& iid, void** result) { + // In case loading the PAC file requires authentication. + if (iid.Equals(NS_GET_IID(nsIAuthPrompt))) { + nsCOMPtr<nsIPromptFactory> promptFac = + do_GetService("@mozilla.org/prompter;1"); + NS_ENSURE_TRUE(promptFac, NS_ERROR_NO_INTERFACE); + nsresult rv = + promptFac->GetPrompt(nullptr, iid, reinterpret_cast<void**>(result)); + if (NS_FAILED(rv)) { + return NS_ERROR_NO_INTERFACE; + } + return NS_OK; + } + + // In case loading the PAC file results in a redirect. + if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) { + NS_ADDREF_THIS(); + *result = static_cast<nsIChannelEventSink*>(this); + return NS_OK; + } + + return NS_ERROR_NO_INTERFACE; +} + +NS_IMETHODIMP +nsPACMan::AsyncOnChannelRedirect(nsIChannel* oldChannel, nsIChannel* newChannel, + uint32_t flags, + nsIAsyncVerifyRedirectCallback* callback) { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + + nsresult rv = NS_OK; + nsCOMPtr<nsIURI> pacURI; + if (NS_FAILED((rv = newChannel->GetURI(getter_AddRefs(pacURI))))) return rv; + + rv = pacURI->GetSpec(mPACURIRedirectSpec); + if (NS_FAILED(rv)) return rv; + + LOG(("nsPACMan redirect from original %s to redirected %s\n", + mPACURISpec.get(), mPACURIRedirectSpec.get())); + + // do not update mPACURISpec - that needs to stay as the + // configured URI so that we can determine when the config changes. + // However do track the most recent URI in the redirect change + // as mPACURIRedirectSpec so that URI can be allowed to bypass + // the proxy and actually fetch the pac file. + + callback->OnRedirectVerifyCallback(NS_OK); + return NS_OK; +} + +nsresult nsPACMan::Init(nsISystemProxySettings* systemProxySettings) { + mSystemProxySettings = systemProxySettings; + mDHCPClient = do_GetService(NS_DHCPCLIENT_CONTRACTID); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsPACMan.h b/netwerk/base/nsPACMan.h new file mode 100644 index 0000000000..dd7d6edb8c --- /dev/null +++ b/netwerk/base/nsPACMan.h @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPACMan_h__ +#define nsPACMan_h__ + +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/DataMutex.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Logging.h" +#include "mozilla/net/NeckoTargetHolder.h" +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" +#include "nsIChannelEventSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsIStreamLoader.h" +#include "nsThreadUtils.h" +#include "nsIURI.h" +#include "nsString.h" +#include "ProxyAutoConfig.h" + +class nsISystemProxySettings; +class nsIDHCPClient; +class nsIThread; + +namespace mozilla { +namespace net { + +class nsPACMan; +class WaitForThreadShutdown; + +/** + * This class defines a callback interface used by AsyncGetProxyForURI. + */ +class NS_NO_VTABLE nsPACManCallback : public nsISupports { + public: + /** + * This method is invoked on the same thread that called AsyncGetProxyForURI. + * + * @param status + * This parameter indicates whether or not the PAC query succeeded. + * @param pacString + * This parameter holds the value of the PAC string. It is empty when + * status is a failure code. + * @param newPACURL + * This parameter holds the URL of a new PAC file that should be loaded + * before the query is evaluated again. At least one of pacString and + * newPACURL should be 0 length. + */ + virtual void OnQueryComplete(nsresult status, const nsACString& pacString, + const nsACString& newPACURL) = 0; +}; + +class PendingPACQuery final : public Runnable, + public LinkedListElement<PendingPACQuery> { + public: + PendingPACQuery(nsPACMan* pacMan, nsIURI* uri, nsPACManCallback* callback, + uint32_t flags, bool mainThreadResponse); + + // can be called from either thread + void Complete(nsresult status, const nsACString& pacString); + void UseAlternatePACFile(const nsACString& pacURL); + + nsCString mSpec; + nsCString mScheme; + nsCString mHost; + int32_t mPort; + uint32_t mFlags; + + NS_IMETHOD Run(void) override; /* Runnable */ + + private: + nsPACMan* mPACMan; // weak reference + + private: + RefPtr<nsPACManCallback> mCallback; + bool mOnMainThreadOnly; +}; + +/** + * This class provides an abstraction layer above the PAC thread. The methods + * defined on this class are intended to be called on the main thread only. + */ + +class nsPACMan final : public nsIStreamLoaderObserver, + public nsIInterfaceRequestor, + public nsIChannelEventSink, + public NeckoTargetHolder { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit nsPACMan(nsISerialEventTarget* mainThreadEventTarget); + + /** + * This method may be called to shutdown the PAC manager. Any async queries + * that have not yet completed will either finish normally or be canceled by + * the time this method returns. + */ + void Shutdown(); + + /** + * This method queries a PAC result asynchronously. The callback runs on the + * calling thread. If the PAC file has not yet been loaded, then this method + * will queue up the request, and complete it once the PAC file has been + * loaded. + * + * @param uri + * The URI to query. + * @param callback + * The callback to run once the PAC result is available. + * @param flags + * A bit-wise combination of the RESOLVE_ flags defined above. Pass + * 0 to specify the default behavior. + * @param mustCallbackOnMainThread + * If set to false the callback can be made from the PAC thread + */ + nsresult AsyncGetProxyForURI(nsIURI* uri, nsPACManCallback* callback, + uint32_t flags, bool mainThreadResponse); + + /** + * This method may be called to reload the PAC file. While we are loading + * the PAC file, any asynchronous PAC queries will be queued up to be + * processed once the PAC file finishes loading. + * + * @param aSpec + * The non normalized uri spec of this URI used for comparison with + * system proxy settings to determine if the PAC uri has changed. + */ + nsresult LoadPACFromURI(const nsACString& aSpec); + + /** + * Returns true if we are currently loading the PAC file. + */ + bool IsLoading() { + auto loader = mLoader.Lock(); + return loader.ref() != nullptr; + } + + /** + * Returns true if the given URI matches the URI of our PAC file or the + * URI it has been redirected to. In the case of a chain of redirections + * only the current one being followed and the original are considered + * becuase this information is used, respectively, to determine if we + * should bypass the proxy (to fetch the pac file) or if the pac + * configuration has changed (and we should reload the pac file) + */ + bool IsPACURI(const nsACString& spec) { + return mPACURISpec.Equals(spec) || mPACURIRedirectSpec.Equals(spec) || + mNormalPACURISpec.Equals(spec); + } + + bool IsPACURI(nsIURI* uri) { + if (mPACURISpec.IsEmpty() && mPACURIRedirectSpec.IsEmpty()) { + return false; + } + + nsAutoCString tmp; + nsresult rv = uri->GetSpec(tmp); + if (NS_FAILED(rv)) { + return false; + } + + return IsPACURI(tmp); + } + + bool IsUsingWPAD() { return mAutoDetect; } + + nsresult Init(nsISystemProxySettings*); + static nsPACMan* sInstance; + + // PAC thread operations only + void ProcessPendingQ(); + void CancelPendingQ(nsresult, bool aShutdown); + + void SetWPADOverDHCPEnabled(bool aValue) { mWPADOverDHCPEnabled = aValue; } + + private: + NS_DECL_NSISTREAMLOADEROBSERVER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + + friend class PendingPACQuery; + friend class PACLoadComplete; + friend class ConfigureWPADComplete; + friend class ExecutePACThreadAction; + friend class WaitForThreadShutdown; + friend class TestPACMan; + + ~nsPACMan(); + + /** + * Cancel any existing load if any. + */ + void CancelExistingLoad(); + + /** + * Start loading the PAC file. + */ + void StartLoading(); + + /** + * Continue loading the PAC file. + */ + void ContinueLoadingAfterPACUriKnown(); + + /** + * This method may be called to reload the PAC file. While we are loading + * the PAC file, any asynchronous PAC queries will be queued up to be + * processed once the PAC file finishes loading. + * + * @param aSpec + * The non normalized uri spec of this URI used for comparison with + * system proxy settings to determine if the PAC uri has changed. + * @param aResetLoadFailureCount + * A flag saying whether the exponential back-off for attempting to + * reload the PAC should be reset. + */ + nsresult LoadPACFromURI(const nsACString& aSpec, bool aResetLoadFailureCount); + + /** + * Reload the PAC file if there is reason to. + */ + void MaybeReloadPAC(); + + /** + * Called when we fail to load the PAC file. + */ + void OnLoadFailure(); + + /** + * PostQuery() only runs on the PAC thread and it is used to + * place a pendingPACQuery into the queue and potentially + * execute the queue if it was otherwise empty + */ + nsresult PostQuery(PendingPACQuery* query); + + // Having found the PAC URI on the PAC thread, copy it to a string which + // can be altered on the main thread. + void AssignPACURISpec(const nsACString& aSpec); + + // PAC thread operations only + void PostProcessPendingQ(); + void PostCancelPendingQ(nsresult, bool aShutdown = false); + bool ProcessPending(); + nsresult GetPACFromDHCP(nsACString& aSpec); + nsresult ConfigureWPAD(nsACString& aSpec); + + private: + /** + * Dispatches a runnable to the PAC processing thread. Handles lazy + * instantiation of the thread. + * + * @param aEvent The event to disptach. + * @param aSync Whether or not this should be synchronous dispatch. + */ + nsresult DispatchToPAC(already_AddRefed<nsIRunnable> aEvent, + bool aSync = false); + + UniquePtr<ProxyAutoConfigBase> mPAC; + nsCOMPtr<nsIThread> mPACThread; + nsCOMPtr<nsISystemProxySettings> mSystemProxySettings; + nsCOMPtr<nsIDHCPClient> mDHCPClient; + + LinkedList<PendingPACQuery> mPendingQ; /* pac thread only */ + + // These specs are not nsIURI so that they can be used off the main thread. + // The non-normalized versions are directly from the configuration, the + // normalized version has been extracted from an nsIURI + nsCString mPACURISpec; + nsCString mPACURIRedirectSpec; + nsCString mNormalPACURISpec; + + DataMutex<nsCOMPtr<nsIStreamLoader>> mLoader; + bool mLoadPending; + Atomic<bool, Relaxed> mShutdown; + TimeStamp mScheduledReload; + Atomic<uint32_t, Relaxed> mLoadFailureCount; + + bool mInProgress; + bool mIncludePath; + bool mAutoDetect; + bool mWPADOverDHCPEnabled; + int32_t mProxyConfigType; +}; + +extern LazyLogModule gProxyLog; + +} // namespace net +} // namespace mozilla + +#endif // nsPACMan_h__ diff --git a/netwerk/base/nsPISocketTransportService.idl b/netwerk/base/nsPISocketTransportService.idl new file mode 100644 index 0000000000..e7c8ac5a60 --- /dev/null +++ b/netwerk/base/nsPISocketTransportService.idl @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISocketTransportService.idl" + +/** + * This is a private interface used by the internals of the networking library. + * It will never be frozen. Do not use it in external code. + */ +[builtinclass, scriptable, uuid(18f73bf1-b35b-4b7b-aa9a-11bcbdbc389c)] +interface nsPISocketTransportService : nsIRoutedSocketTransportService +{ + /** + * init/shutdown routines. + */ + void init(); + void shutdown(in bool aXpcomShutdown); + + /** + * controls the TCP sender window clamp + */ + readonly attribute long sendBufferSize; + + /** + * Controls whether the socket transport service is offline. + * Setting it offline will cause non-local socket detachment. + */ + attribute boolean offline; + + /** + * Controls the default timeout (in seconds) for sending keepalive probes. + */ + readonly attribute long keepaliveIdleTime; + + /** + * Controls the default interval (in seconds) between retrying keepalive probes. + */ + readonly attribute long keepaliveRetryInterval; + + /** + * Controls the default retransmission count for keepalive probes. + */ + readonly attribute long keepaliveProbeCount; +}; + +%{C++ +/* + * I/O activity observer topic. Sends out information about the + * amount of data we're sending/receiving via sockets and disk files. + * + * Activated via the "io.activity.enabled" preference. + */ +#define NS_IO_ACTIVITY "io-activity" + + +%} diff --git a/netwerk/base/nsPreloadedStream.cpp b/netwerk/base/nsPreloadedStream.cpp new file mode 100644 index 0000000000..cd7a7e79d2 --- /dev/null +++ b/netwerk/base/nsPreloadedStream.cpp @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPreloadedStream.h" +#include "nsIRunnable.h" + +#include "nsThreadUtils.h" +#include <algorithm> + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(nsPreloadedStream, nsIInputStream, nsIAsyncInputStream, + nsIInputStreamCallback) + +nsPreloadedStream::nsPreloadedStream(nsIAsyncInputStream* aStream, + const char* data, uint32_t datalen) + : mStream(aStream), + mOffset(0), + mLen(datalen), + mCallback("nsPreloadedStream") { + mBuf = (char*)moz_xmalloc(datalen); + memcpy(mBuf, data, datalen); +} + +nsPreloadedStream::~nsPreloadedStream() { free(mBuf); } + +NS_IMETHODIMP +nsPreloadedStream::Close() { + mLen = 0; + return mStream->Close(); +} + +NS_IMETHODIMP +nsPreloadedStream::Available(uint64_t* _retval) { + uint64_t avail = 0; + + nsresult rv = mStream->Available(&avail); + if (NS_FAILED(rv)) return rv; + *_retval = avail + mLen; + return NS_OK; +} + +NS_IMETHODIMP +nsPreloadedStream::StreamStatus() { return mStream->StreamStatus(); } + +NS_IMETHODIMP +nsPreloadedStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) { + if (!mLen) return mStream->Read(aBuf, aCount, _retval); + + uint32_t toRead = std::min(mLen, aCount); + memcpy(aBuf, mBuf + mOffset, toRead); + mOffset += toRead; + mLen -= toRead; + *_retval = toRead; + return NS_OK; +} + +NS_IMETHODIMP +nsPreloadedStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* result) { + if (!mLen) return mStream->ReadSegments(aWriter, aClosure, aCount, result); + + *result = 0; + while (mLen > 0 && aCount > 0) { + uint32_t toRead = std::min(mLen, aCount); + uint32_t didRead = 0; + nsresult rv; + + rv = aWriter(this, aClosure, mBuf + mOffset, *result, toRead, &didRead); + + if (NS_FAILED(rv)) return NS_OK; + + *result += didRead; + mOffset += didRead; + mLen -= didRead; + aCount -= didRead; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPreloadedStream::IsNonBlocking(bool* _retval) { + return mStream->IsNonBlocking(_retval); +} + +NS_IMETHODIMP +nsPreloadedStream::CloseWithStatus(nsresult aStatus) { + mLen = 0; + return mStream->CloseWithStatus(aStatus); +} + +class RunOnThread : public Runnable { + public: + RunOnThread(nsIAsyncInputStream* aStream, nsIInputStreamCallback* aCallback) + : Runnable("net::RunOnThread"), mStream(aStream), mCallback(aCallback) {} + + virtual ~RunOnThread() = default; + + NS_IMETHOD Run() override { + mCallback->OnInputStreamReady(mStream); + return NS_OK; + } + + private: + nsCOMPtr<nsIAsyncInputStream> mStream; + nsCOMPtr<nsIInputStreamCallback> mCallback; +}; + +NS_IMETHODIMP +nsPreloadedStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + if (!mLen) { + { + auto lock = mCallback.Lock(); + *lock = aCallback; + } + return mStream->AsyncWait(aCallback ? this : nullptr, aFlags, + aRequestedCount, aEventTarget); + } + + if (!aCallback) return NS_OK; + + if (!aEventTarget) return aCallback->OnInputStreamReady(this); + + nsCOMPtr<nsIRunnable> event = new RunOnThread(this, aCallback); + return aEventTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); +} + +NS_IMETHODIMP +nsPreloadedStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { + nsCOMPtr<nsIInputStreamCallback> callback; + { + auto lock = mCallback.Lock(); + callback = lock->forget(); + } + if (callback) { + return callback->OnInputStreamReady(this); + } + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsPreloadedStream.h b/netwerk/base/nsPreloadedStream.h new file mode 100644 index 0000000000..5c550b43e7 --- /dev/null +++ b/netwerk/base/nsPreloadedStream.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This class allows you to prefix an existing nsIAsyncInputStream + * with a preloaded block of data known at construction time by wrapping the + * two data sources into a new nsIAsyncInputStream. Readers of the new + * stream initially see the preloaded data and when that has been exhausted + * they automatically read from the wrapped stream. + * + * It is used by nsHttpConnection when it has over buffered while reading from + * the HTTP input socket and accidentally consumed data that belongs to + * a different protocol via the HTTP Upgrade mechanism. That over-buffered + * data is preloaded together with the input socket to form the new input socket + * given to the new protocol handler. + */ + +#ifndef nsPreloadedStream_h__ +#define nsPreloadedStream_h__ + +#include "nsIAsyncInputStream.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/DataMutex.h" + +namespace mozilla { +namespace net { + +class nsPreloadedStream final : public nsIAsyncInputStream, + public nsIInputStreamCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + + nsPreloadedStream(nsIAsyncInputStream* aStream, const char* data, + uint32_t datalen); + + private: + ~nsPreloadedStream(); + + nsCOMPtr<nsIAsyncInputStream> mStream; + + char* mBuf; + uint32_t mOffset; + uint32_t mLen; + + DataMutex<nsCOMPtr<nsIInputStreamCallback>> mCallback; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/nsProtocolProxyService.cpp b/netwerk/base/nsProtocolProxyService.cpp new file mode 100644 index 0000000000..9f721f94e8 --- /dev/null +++ b/netwerk/base/nsProtocolProxyService.cpp @@ -0,0 +1,2459 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" + +#include "nsProtocolProxyService.h" +#include "nsProxyInfo.h" +#include "nsIClassInfoImpl.h" +#include "nsIIOService.h" +#include "nsIObserverService.h" +#include "nsIProtocolHandler.h" +#include "nsIProtocolProxyCallback.h" +#include "nsIChannel.h" +#include "nsICancelable.h" +#include "nsDNSService2.h" +#include "nsPIDNSService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsContentUtils.h" +#include "nsCRT.h" +#include "nsThreadUtils.h" +#include "nsQueryObject.h" +#include "nsSOCKSIOLayer.h" +#include "nsString.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "prnetdb.h" +#include "nsPACMan.h" +#include "nsProxyRelease.h" +#include "mozilla/Mutex.h" +#include "mozilla/CondVar.h" +#include "nsISystemProxySettings.h" +#include "nsINetworkLinkService.h" +#include "nsIHttpChannelInternal.h" +#include "mozilla/dom/nsMixedContentBlocker.h" +#include "mozilla/Logging.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Tokenizer.h" +#include "mozilla/Unused.h" + +//---------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +extern const char kProxyType_HTTP[]; +extern const char kProxyType_HTTPS[]; +extern const char kProxyType_SOCKS[]; +extern const char kProxyType_SOCKS4[]; +extern const char kProxyType_SOCKS5[]; +extern const char kProxyType_DIRECT[]; +extern const char kProxyType_PROXY[]; + +#undef LOG +#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args) + +//---------------------------------------------------------------------------- + +#define PROXY_PREF_BRANCH "network.proxy" +#define PROXY_PREF(x) PROXY_PREF_BRANCH "." x + +//---------------------------------------------------------------------------- + +// This structure is intended to be allocated on the stack +struct nsProtocolInfo { + nsAutoCString scheme; + uint32_t flags = 0; + int32_t defaultPort = 0; +}; + +//---------------------------------------------------------------------------- + +// Return the channel's proxy URI, or if it doesn't exist, the +// channel's main URI. +static nsresult GetProxyURI(nsIChannel* channel, nsIURI** aOut) { + nsresult rv = NS_OK; + nsCOMPtr<nsIURI> proxyURI; + nsCOMPtr<nsIHttpChannelInternal> httpChannel(do_QueryInterface(channel)); + if (httpChannel) { + rv = httpChannel->GetProxyURI(getter_AddRefs(proxyURI)); + } + if (!proxyURI) { + rv = channel->GetURI(getter_AddRefs(proxyURI)); + } + if (NS_FAILED(rv)) { + return rv; + } + proxyURI.forget(aOut); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +nsProtocolProxyService::FilterLink::FilterLink(uint32_t p, + nsIProtocolProxyFilter* f) + : position(p), filter(f), channelFilter(nullptr) { + LOG(("nsProtocolProxyService::FilterLink::FilterLink %p, filter=%p", this, + f)); +} +nsProtocolProxyService::FilterLink::FilterLink( + uint32_t p, nsIProtocolProxyChannelFilter* cf) + : position(p), filter(nullptr), channelFilter(cf) { + LOG(("nsProtocolProxyService::FilterLink::FilterLink %p, channel-filter=%p", + this, cf)); +} + +nsProtocolProxyService::FilterLink::~FilterLink() { + LOG(("nsProtocolProxyService::FilterLink::~FilterLink %p", this)); +} + +//----------------------------------------------------------------------------- + +// The nsPACManCallback portion of this implementation should be run +// on the main thread - so call nsPACMan::AsyncGetProxyForURI() with +// a true mainThreadResponse parameter. +class nsAsyncResolveRequest final : public nsIRunnable, + public nsPACManCallback, + public nsICancelable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + nsAsyncResolveRequest(nsProtocolProxyService* pps, nsIChannel* channel, + uint32_t aResolveFlags, + nsIProtocolProxyCallback* callback) + : mResolveFlags(aResolveFlags), + mPPS(pps), + mXPComPPS(pps), + mChannel(channel), + mCallback(callback) { + NS_ASSERTION(mCallback, "null callback"); + } + + private: + ~nsAsyncResolveRequest() { + if (!NS_IsMainThread()) { + // these xpcom pointers might need to be proxied back to the + // main thread to delete safely, but if this request had its + // callbacks called normally they will all be null and this is a nop + + if (mChannel) { + NS_ReleaseOnMainThread("nsAsyncResolveRequest::mChannel", + mChannel.forget()); + } + + if (mCallback) { + NS_ReleaseOnMainThread("nsAsyncResolveRequest::mCallback", + mCallback.forget()); + } + + if (mProxyInfo) { + NS_ReleaseOnMainThread("nsAsyncResolveRequest::mProxyInfo", + mProxyInfo.forget()); + } + + if (mXPComPPS) { + NS_ReleaseOnMainThread("nsAsyncResolveRequest::mXPComPPS", + mXPComPPS.forget()); + } + } + } + + // Helper class to loop over all registered asynchronous filters. + // There is a cycle between nsAsyncResolveRequest and this class that + // is broken after the last filter has called back on this object. + class AsyncApplyFilters final : public nsIProxyProtocolFilterResult, + public nsIRunnable, + public nsICancelable { + // The reference counter is thread-safe, but the processing logic is + // considered single thread only. We want the counter be thread safe, + // since this class can be released on a background thread. + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROXYPROTOCOLFILTERRESULT + NS_DECL_NSIRUNNABLE + NS_DECL_NSICANCELABLE + + using Callback = + std::function<nsresult(nsAsyncResolveRequest*, nsIProxyInfo*, bool)>; + + explicit AsyncApplyFilters(nsProtocolInfo& aInfo, + Callback const& aCallback); + // This method starts the processing or filters. If all of them + // answer synchronously (call back from within applyFilters) this method + // will return immediately and the returning result will carry return + // result of the callback given in constructor. + // This method is looping the registered filters (that have been copied + // locally) as long as an answer from a filter is obtained synchronously. + // Note that filters are processed serially to let them build a list + // of proxy info. + nsresult AsyncProcess(nsAsyncResolveRequest* aRequest); + + private: + using FilterLink = nsProtocolProxyService::FilterLink; + + virtual ~AsyncApplyFilters(); + // Processes the next filter and loops until a filter is successfully + // called on or it has called back to us. + nsresult ProcessNextFilter(); + // Called after the last filter has been processed (=called back or failed + // to be called on) + nsresult Finish(); + + nsProtocolInfo mInfo; + // This is nullified before we call back on the request or when + // Cancel() on this object has been called to break the cycle + // and signal to stop. + RefPtr<nsAsyncResolveRequest> mRequest; + Callback mCallback; + // A shallow snapshot of filters as they were registered at the moment + // we started to process filters for the given resolve request. + nsTArray<RefPtr<FilterLink>> mFiltersCopy; + + nsTArray<RefPtr<FilterLink>>::index_type mNextFilterIndex; + // true when we are calling ProcessNextFilter() from inside AsyncProcess(), + // false otherwise. + bool mProcessingInLoop; + // true after a filter called back to us with a result, dropped to false + // just before we call a filter. + bool mFilterCalledBack; + + // This keeps the initial value we pass to the first filter in line and also + // collects the result from each filter call. + nsCOMPtr<nsIProxyInfo> mProxyInfo; + + // The logic is written as non-thread safe, assert single-thread usage. + nsCOMPtr<nsISerialEventTarget> mProcessingThread; + }; + + void EnsureResolveFlagsMatch() { + nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(mProxyInfo); + if (!pi || pi->ResolveFlags() == mResolveFlags) { + return; + } + + nsCOMPtr<nsIProxyInfo> proxyInfo = + pi->CloneProxyInfoWithNewResolveFlags(mResolveFlags); + mProxyInfo.swap(proxyInfo); + } + + public: + nsresult ProcessLocally(nsProtocolInfo& info, nsIProxyInfo* pi, + bool isSyncOK) { + SetResult(NS_OK, pi); + + auto consumeFiltersResult = [isSyncOK](nsAsyncResolveRequest* ctx, + nsIProxyInfo* pi, + bool aCalledAsync) -> nsresult { + ctx->SetResult(NS_OK, pi); + if (isSyncOK || aCalledAsync) { + ctx->Run(); + return NS_OK; + } + + return ctx->DispatchCallback(); + }; + + mAsyncFilterApplier = new AsyncApplyFilters(info, consumeFiltersResult); + // may call consumeFiltersResult() directly + return mAsyncFilterApplier->AsyncProcess(this); + } + + void SetResult(nsresult status, nsIProxyInfo* pi) { + mStatus = status; + mProxyInfo = pi; + } + + NS_IMETHOD Run() override { + if (mCallback) DoCallback(); + return NS_OK; + } + + NS_IMETHOD Cancel(nsresult reason) override { + NS_ENSURE_ARG(NS_FAILED(reason)); + + if (mAsyncFilterApplier) { + mAsyncFilterApplier->Cancel(reason); + } + + // If we've already called DoCallback then, nothing more to do. + if (!mCallback) return NS_OK; + + SetResult(reason, nullptr); + return DispatchCallback(); + } + + nsresult DispatchCallback() { + if (mDispatched) { // Only need to dispatch once + return NS_OK; + } + + nsresult rv = NS_DispatchToCurrentThread(this); + if (NS_FAILED(rv)) { + NS_WARNING("unable to dispatch callback event"); + } else { + mDispatched = true; + return NS_OK; + } + + mCallback = nullptr; // break possible reference cycle + return rv; + } + + private: + // Called asynchronously, so we do not need to post another PLEvent + // before calling DoCallback. + void OnQueryComplete(nsresult status, const nsACString& pacString, + const nsACString& newPACURL) override { + // If we've already called DoCallback then, nothing more to do. + if (!mCallback) return; + + // Provided we haven't been canceled... + if (mStatus == NS_OK) { + mStatus = status; + mPACString = pacString; + mPACURL = newPACURL; + } + + // In the cancelation case, we may still have another PLEvent in + // the queue that wants to call DoCallback. No need to wait for + // it, just run the callback now. + DoCallback(); + } + + void DoCallback() { + bool pacAvailable = true; + if (mStatus == NS_ERROR_NOT_AVAILABLE && !mProxyInfo) { + // If the PAC service is not avail (e.g. failed pac load + // or shutdown) then we will be going direct. Make that + // mapping now so that any filters are still applied. + mPACString = "DIRECT;"_ns; + mStatus = NS_OK; + + LOG(("pac not available, use DIRECT\n")); + pacAvailable = false; + } + + // Generate proxy info from the PAC string if appropriate + if (NS_SUCCEEDED(mStatus) && !mProxyInfo && !mPACString.IsEmpty()) { + mPPS->ProcessPACString(mPACString, mResolveFlags, + getter_AddRefs(mProxyInfo)); + nsCOMPtr<nsIURI> proxyURI; + GetProxyURI(mChannel, getter_AddRefs(proxyURI)); + + // Now apply proxy filters + nsProtocolInfo info; + mStatus = mPPS->GetProtocolInfo(proxyURI, &info); + + auto consumeFiltersResult = [pacAvailable](nsAsyncResolveRequest* self, + nsIProxyInfo* pi, + bool async) -> nsresult { + LOG(("DoCallback::consumeFiltersResult this=%p, pi=%p, async=%d", self, + pi, async)); + + self->mProxyInfo = pi; + + if (pacAvailable) { + // if !pacAvailable, it was already logged above + LOG(("pac thread callback %s\n", self->mPACString.get())); + } + + if (NS_SUCCEEDED(self->mStatus)) { + self->mPPS->MaybeDisableDNSPrefetch(self->mProxyInfo); + } + + self->EnsureResolveFlagsMatch(); + self->mCallback->OnProxyAvailable(self, self->mChannel, + self->mProxyInfo, self->mStatus); + + return NS_OK; + }; + + if (NS_SUCCEEDED(mStatus)) { + mAsyncFilterApplier = new AsyncApplyFilters(info, consumeFiltersResult); + // This may call consumeFiltersResult() directly. + mAsyncFilterApplier->AsyncProcess(this); + return; + } + + consumeFiltersResult(this, nullptr, false); + } else if (NS_SUCCEEDED(mStatus) && !mPACURL.IsEmpty()) { + LOG(("pac thread callback indicates new pac file load\n")); + + nsCOMPtr<nsIURI> proxyURI; + GetProxyURI(mChannel, getter_AddRefs(proxyURI)); + + // trigger load of new pac url + nsresult rv = mPPS->ConfigureFromPAC(mPACURL, false); + if (NS_SUCCEEDED(rv)) { + // now that the load is triggered, we can resubmit the query + RefPtr<nsAsyncResolveRequest> newRequest = + new nsAsyncResolveRequest(mPPS, mChannel, mResolveFlags, mCallback); + rv = mPPS->mPACMan->AsyncGetProxyForURI(proxyURI, newRequest, + mResolveFlags, true); + } + + if (NS_FAILED(rv)) { + mCallback->OnProxyAvailable(this, mChannel, nullptr, rv); + } + + // do not call onproxyavailable() in SUCCESS case - the newRequest will + // take care of that + } else { + LOG(("pac thread callback did not provide information %" PRIX32 "\n", + static_cast<uint32_t>(mStatus))); + if (NS_SUCCEEDED(mStatus)) mPPS->MaybeDisableDNSPrefetch(mProxyInfo); + EnsureResolveFlagsMatch(); + mCallback->OnProxyAvailable(this, mChannel, mProxyInfo, mStatus); + } + + // We are on the main thread now and don't need these any more so + // release them to avoid having to proxy them back to the main thread + // in the dtor + mCallback = nullptr; // in case the callback holds an owning ref to us + mPPS = nullptr; + mXPComPPS = nullptr; + mChannel = nullptr; + mProxyInfo = nullptr; + } + + private: + nsresult mStatus{NS_OK}; + nsCString mPACString; + nsCString mPACURL; + bool mDispatched{false}; + uint32_t mResolveFlags; + + nsProtocolProxyService* mPPS; + nsCOMPtr<nsIProtocolProxyService> mXPComPPS; + nsCOMPtr<nsIChannel> mChannel; + nsCOMPtr<nsIProtocolProxyCallback> mCallback; + nsCOMPtr<nsIProxyInfo> mProxyInfo; + + RefPtr<AsyncApplyFilters> mAsyncFilterApplier; +}; + +NS_IMPL_ISUPPORTS(nsAsyncResolveRequest, nsICancelable, nsIRunnable) + +NS_IMPL_ISUPPORTS(nsAsyncResolveRequest::AsyncApplyFilters, + nsIProxyProtocolFilterResult, nsICancelable, nsIRunnable) + +nsAsyncResolveRequest::AsyncApplyFilters::AsyncApplyFilters( + nsProtocolInfo& aInfo, Callback const& aCallback) + : mInfo(aInfo), + mCallback(aCallback), + mNextFilterIndex(0), + mProcessingInLoop(false), + mFilterCalledBack(false) { + LOG(("AsyncApplyFilters %p", this)); +} + +nsAsyncResolveRequest::AsyncApplyFilters::~AsyncApplyFilters() { + LOG(("~AsyncApplyFilters %p", this)); + + MOZ_ASSERT(!mRequest); + MOZ_ASSERT(!mProxyInfo); + MOZ_ASSERT(!mFiltersCopy.Length()); +} + +nsresult nsAsyncResolveRequest::AsyncApplyFilters::AsyncProcess( + nsAsyncResolveRequest* aRequest) { + LOG(("AsyncApplyFilters::AsyncProcess %p for req %p", this, aRequest)); + + MOZ_ASSERT(!mRequest, "AsyncApplyFilters started more than once!"); + + if (!(mInfo.flags & nsIProtocolHandler::ALLOWS_PROXY)) { + // Calling the callback directly (not via Finish()) since we + // don't want to prune. + return mCallback(aRequest, aRequest->mProxyInfo, false); + } + + mProcessingThread = NS_GetCurrentThread(); + + mRequest = aRequest; + mProxyInfo = aRequest->mProxyInfo; + + aRequest->mPPS->CopyFilters(mFiltersCopy); + + // We want to give filters a chance to process in a single loop to prevent + // any current-thread dispatch delays when those are not needed. + // This code is rather "loopy" than "recursive" to prevent long stack traces. + do { + MOZ_ASSERT(!mProcessingInLoop); + + mozilla::AutoRestore<bool> restore(mProcessingInLoop); + mProcessingInLoop = true; + + nsresult rv = ProcessNextFilter(); + if (NS_FAILED(rv)) { + return rv; + } + } while (mFilterCalledBack); + + return NS_OK; +} + +nsresult nsAsyncResolveRequest::AsyncApplyFilters::ProcessNextFilter() { + LOG(("AsyncApplyFilters::ProcessNextFilter %p ENTER pi=%p", this, + mProxyInfo.get())); + + RefPtr<FilterLink> filter; + do { + mFilterCalledBack = false; + + if (!mRequest) { + // We got canceled + LOG((" canceled")); + return NS_OK; // should we let the consumer know? + } + + if (mNextFilterIndex == mFiltersCopy.Length()) { + return Finish(); + } + + filter = mFiltersCopy[mNextFilterIndex++]; + + // Loop until a call to a filter succeeded. Other option is to recurse + // but that would waste stack trace when a number of filters gets registered + // and all from some reason tend to fail. + // The !mFilterCalledBack part of the condition is there to protect us from + // calling on another filter when the current one managed to call back and + // then threw. We already have the result so take it and use it since + // the next filter will be processed by the root loop or a call to + // ProcessNextFilter has already been dispatched to this thread. + LOG((" calling filter %p pi=%p", filter.get(), mProxyInfo.get())); + } while (!mRequest->mPPS->ApplyFilter(filter, mRequest->mChannel, mInfo, + mProxyInfo, this) && + !mFilterCalledBack); + + LOG(("AsyncApplyFilters::ProcessNextFilter %p LEAVE pi=%p", this, + mProxyInfo.get())); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncResolveRequest::AsyncApplyFilters::OnProxyFilterResult( + nsIProxyInfo* aProxyInfo) { + LOG(("AsyncApplyFilters::OnProxyFilterResult %p pi=%p", this, aProxyInfo)); + + MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread()); + MOZ_ASSERT(!mFilterCalledBack); + + if (mFilterCalledBack) { + LOG((" duplicate notification?")); + return NS_OK; + } + + mFilterCalledBack = true; + + if (!mRequest) { + // We got canceled + LOG((" canceled")); + return NS_OK; + } + + mProxyInfo = aProxyInfo; + + if (mProcessingInLoop) { + // No need to call/dispatch ProcessNextFilter(), we are in a control + // loop that will do this for us and save recursion/dispatching. + LOG((" in a root loop")); + return NS_OK; + } + + if (mNextFilterIndex == mFiltersCopy.Length()) { + // We are done, all filters have been called on! + Finish(); + return NS_OK; + } + + // Redispatch, since we don't want long stacks when filters respond + // synchronously. + LOG((" redispatching")); + NS_DispatchToCurrentThread(this); + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncResolveRequest::AsyncApplyFilters::Run() { + LOG(("AsyncApplyFilters::Run %p", this)); + + MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread()); + + ProcessNextFilter(); + return NS_OK; +} + +nsresult nsAsyncResolveRequest::AsyncApplyFilters::Finish() { + LOG(("AsyncApplyFilters::Finish %p pi=%p", this, mProxyInfo.get())); + + MOZ_ASSERT(mRequest); + + mFiltersCopy.Clear(); + + RefPtr<nsAsyncResolveRequest> request; + request.swap(mRequest); + + nsCOMPtr<nsIProxyInfo> pi; + pi.swap(mProxyInfo); + + request->mPPS->PruneProxyInfo(mInfo, pi); + return mCallback(request, pi, !mProcessingInLoop); +} + +NS_IMETHODIMP +nsAsyncResolveRequest::AsyncApplyFilters::Cancel(nsresult reason) { + LOG(("AsyncApplyFilters::Cancel %p", this)); + + MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread()); + + // This will be called only from inside the request, so don't call + // its's callback. Dropping the members means we simply break the cycle. + mFiltersCopy.Clear(); + mProxyInfo = nullptr; + mRequest = nullptr; + + return NS_OK; +} + +// Bug 1366133: make GetPACURI off-main-thread since it may hang on Windows +// platform +class AsyncGetPACURIRequest final : public nsIRunnable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + using CallbackFunc = nsresult (nsProtocolProxyService::*)(bool, bool, + nsresult, + const nsACString&); + + AsyncGetPACURIRequest(nsProtocolProxyService* aService, + CallbackFunc aCallback, + nsISystemProxySettings* aSystemProxySettings, + bool aMainThreadOnly, bool aForceReload, + bool aResetPACThread) + : mIsMainThreadOnly(aMainThreadOnly), + mService(aService), + mServiceHolder(do_QueryObject(aService)), + mCallback(aCallback), + mSystemProxySettings(aSystemProxySettings), + mForceReload(aForceReload), + mResetPACThread(aResetPACThread) { + MOZ_ASSERT(NS_IsMainThread()); + Unused << mIsMainThreadOnly; + } + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread() == mIsMainThreadOnly); + + nsCString pacUri; + nsresult rv = mSystemProxySettings->GetPACURI(pacUri); + + nsCOMPtr<nsIRunnable> event = + NewNonOwningCancelableRunnableMethod<bool, bool, nsresult, nsCString>( + "AsyncGetPACURIRequestCallback", mService, mCallback, mForceReload, + mResetPACThread, rv, pacUri); + + return NS_DispatchToMainThread(event); + } + + private: + ~AsyncGetPACURIRequest() { + NS_ReleaseOnMainThread("AsyncGetPACURIRequest::mServiceHolder", + mServiceHolder.forget()); + } + + bool mIsMainThreadOnly; + + nsProtocolProxyService* mService; // ref-count is hold by mServiceHolder + nsCOMPtr<nsIProtocolProxyService2> mServiceHolder; + CallbackFunc mCallback; + nsCOMPtr<nsISystemProxySettings> mSystemProxySettings; + + bool mForceReload; + bool mResetPACThread; +}; + +NS_IMPL_ISUPPORTS(AsyncGetPACURIRequest, nsIRunnable) + +//---------------------------------------------------------------------------- + +// +// apply mask to address (zeros out excluded bits). +// +// NOTE: we do the byte swapping here to minimize overall swapping. +// +static void proxy_MaskIPv6Addr(PRIPv6Addr& addr, uint16_t mask_len) { + if (mask_len == 128) return; + + if (mask_len > 96) { + addr.pr_s6_addr32[3] = + PR_htonl(PR_ntohl(addr.pr_s6_addr32[3]) & (~0uL << (128 - mask_len))); + } else if (mask_len > 64) { + addr.pr_s6_addr32[3] = 0; + addr.pr_s6_addr32[2] = + PR_htonl(PR_ntohl(addr.pr_s6_addr32[2]) & (~0uL << (96 - mask_len))); + } else if (mask_len > 32) { + addr.pr_s6_addr32[3] = 0; + addr.pr_s6_addr32[2] = 0; + addr.pr_s6_addr32[1] = + PR_htonl(PR_ntohl(addr.pr_s6_addr32[1]) & (~0uL << (64 - mask_len))); + } else { + addr.pr_s6_addr32[3] = 0; + addr.pr_s6_addr32[2] = 0; + addr.pr_s6_addr32[1] = 0; + addr.pr_s6_addr32[0] = + PR_htonl(PR_ntohl(addr.pr_s6_addr32[0]) & (~0uL << (32 - mask_len))); + } +} + +static void proxy_GetStringPref(nsIPrefBranch* aPrefBranch, const char* aPref, + nsCString& aResult) { + nsAutoCString temp; + nsresult rv = aPrefBranch->GetCharPref(aPref, temp); + if (NS_FAILED(rv)) { + aResult.Truncate(); + } else { + aResult.Assign(temp); + // all of our string prefs are hostnames, so we should remove any + // whitespace characters that the user might have unknowingly entered. + aResult.StripWhitespace(); + } +} + +static void proxy_GetIntPref(nsIPrefBranch* aPrefBranch, const char* aPref, + int32_t& aResult) { + int32_t temp; + nsresult rv = aPrefBranch->GetIntPref(aPref, &temp); + if (NS_FAILED(rv)) { + aResult = -1; + } else { + aResult = temp; + } +} + +static void proxy_GetBoolPref(nsIPrefBranch* aPrefBranch, const char* aPref, + bool& aResult) { + bool temp; + nsresult rv = aPrefBranch->GetBoolPref(aPref, &temp); + if (NS_FAILED(rv)) { + aResult = false; + } else { + aResult = temp; + } +} + +//---------------------------------------------------------------------------- + +static const int32_t PROXYCONFIG_DIRECT4X = 3; +static const int32_t PROXYCONFIG_COUNT = 6; + +NS_IMPL_ADDREF(nsProtocolProxyService) +NS_IMPL_RELEASE(nsProtocolProxyService) +NS_IMPL_CLASSINFO(nsProtocolProxyService, nullptr, nsIClassInfo::SINGLETON, + NS_PROTOCOLPROXYSERVICE_CID) + +// NS_IMPL_QUERY_INTERFACE_CI with the nsProtocolProxyService QI change +NS_INTERFACE_MAP_BEGIN(nsProtocolProxyService) + NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService) + NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService2) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsINamed) + NS_INTERFACE_MAP_ENTRY_CONCRETE(nsProtocolProxyService) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIProtocolProxyService) + NS_IMPL_QUERY_CLASSINFO(nsProtocolProxyService) +NS_INTERFACE_MAP_END + +NS_IMPL_CI_INTERFACE_GETTER(nsProtocolProxyService, nsIProtocolProxyService, + nsIProtocolProxyService2) + +nsProtocolProxyService::nsProtocolProxyService() : mSessionStart(PR_Now()) {} + +nsProtocolProxyService::~nsProtocolProxyService() { + // These should have been cleaned up in our Observe method. + NS_ASSERTION(mHostFiltersArray.Length() == 0 && mFilters.Length() == 0 && + mPACMan == nullptr, + "what happened to xpcom-shutdown?"); +} + +// nsProtocolProxyService methods +nsresult nsProtocolProxyService::Init() { + // failure to access prefs is non-fatal + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefBranch) { + // monitor proxy prefs + prefBranch->AddObserver(PROXY_PREF_BRANCH, this, false); + + // read all prefs + PrefsChanged(prefBranch, nullptr); + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + // register for shutdown notification so we can clean ourselves up + // properly. + obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + obs->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); + } + + return NS_OK; +} + +// ReloadNetworkPAC() checks if there's a non-networked PAC in use then avoids +// to call ReloadPAC() +nsresult nsProtocolProxyService::ReloadNetworkPAC() { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) { + return NS_OK; + } + + int32_t type; + nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type); + if (NS_FAILED(rv)) { + return NS_OK; + } + + if (type == PROXYCONFIG_PAC) { + nsAutoCString pacSpec; + prefs->GetCharPref(PROXY_PREF("autoconfig_url"), pacSpec); + if (!pacSpec.IsEmpty()) { + nsCOMPtr<nsIURI> pacURI; + rv = NS_NewURI(getter_AddRefs(pacURI), pacSpec); + if (!NS_SUCCEEDED(rv)) { + return rv; + } + + nsProtocolInfo pac; + rv = GetProtocolInfo(pacURI, &pac); + if (!NS_SUCCEEDED(rv)) { + return rv; + } + + if (!pac.scheme.EqualsLiteral("file") && + !pac.scheme.EqualsLiteral("data")) { + LOG((": received network changed event, reload PAC")); + ReloadPAC(); + } + } + } else if ((type == PROXYCONFIG_WPAD) || (type == PROXYCONFIG_SYSTEM)) { + ReloadPAC(); + } + + return NS_OK; +} + +nsresult nsProtocolProxyService::AsyncConfigureFromPAC(bool aForceReload, + bool aResetPACThread) { + MOZ_ASSERT(NS_IsMainThread()); + + bool mainThreadOnly; + nsresult rv = mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIRunnable> req = new AsyncGetPACURIRequest( + this, &nsProtocolProxyService::OnAsyncGetPACURI, mSystemProxySettings, + mainThreadOnly, aForceReload, aResetPACThread); + + if (mainThreadOnly) { + return req->Run(); + } + + return NS_DispatchBackgroundTask(req.forget(), + nsIEventTarget::DISPATCH_NORMAL); +} + +nsresult nsProtocolProxyService::OnAsyncGetPACURI(bool aForceReload, + bool aResetPACThread, + nsresult aResult, + const nsACString& aUri) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aResetPACThread) { + ResetPACThread(); + } + + if (NS_SUCCEEDED(aResult) && !aUri.IsEmpty()) { + ConfigureFromPAC(PromiseFlatCString(aUri), aForceReload); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsProtocolProxyService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + mIsShutdown = true; + // cleanup + mHostFiltersArray.Clear(); + mFilters.Clear(); + + if (mPACMan) { + mPACMan->Shutdown(); + mPACMan = nullptr; + } + + if (mReloadPACTimer) { + mReloadPACTimer->Cancel(); + mReloadPACTimer = nullptr; + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_NETWORK_LINK_TOPIC); + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + + } else if (strcmp(aTopic, NS_NETWORK_LINK_TOPIC) == 0) { + nsCString converted = NS_ConvertUTF16toUTF8(aData); + const char* state = converted.get(); + if (!strcmp(state, NS_NETWORK_LINK_DATA_CHANGED)) { + uint32_t delay = StaticPrefs::network_proxy_reload_pac_delay(); + LOG(("nsProtocolProxyService::Observe call ReloadNetworkPAC() delay=%u", + delay)); + + if (delay) { + if (mReloadPACTimer) { + mReloadPACTimer->Cancel(); + mReloadPACTimer = nullptr; + } + NS_NewTimerWithCallback(getter_AddRefs(mReloadPACTimer), this, delay, + nsITimer::TYPE_ONE_SHOT); + } else { + ReloadNetworkPAC(); + } + } + } else { + NS_ASSERTION(strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0, + "what is this random observer event?"); + nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject); + if (prefs) PrefsChanged(prefs, NS_LossyConvertUTF16toASCII(aData).get()); + } + return NS_OK; +} + +NS_IMETHODIMP +nsProtocolProxyService::Notify(nsITimer* aTimer) { + MOZ_ASSERT(aTimer == mReloadPACTimer); + ReloadNetworkPAC(); + return NS_OK; +} + +NS_IMETHODIMP +nsProtocolProxyService::GetName(nsACString& aName) { + aName.AssignLiteral("nsProtocolProxyService"); + return NS_OK; +} + +void nsProtocolProxyService::PrefsChanged(nsIPrefBranch* prefBranch, + const char* pref) { + nsresult rv = NS_OK; + bool reloadPAC = false; + nsAutoCString tempString; + auto invokeCallback = + MakeScopeExit([&] { NotifyProxyConfigChangedInternal(); }); + + if (!pref || !strcmp(pref, PROXY_PREF("type"))) { + int32_t type = -1; + rv = prefBranch->GetIntPref(PROXY_PREF("type"), &type); + if (NS_SUCCEEDED(rv)) { + // bug 115720 - for ns4.x backwards compatibility + if (type == PROXYCONFIG_DIRECT4X) { + type = PROXYCONFIG_DIRECT; + // Reset the type so that the dialog looks correct, and we + // don't have to handle this case everywhere else + // I'm paranoid about a loop of some sort - only do this + // if we're enumerating all prefs, and ignore any error + if (!pref) prefBranch->SetIntPref(PROXY_PREF("type"), type); + } else if (type >= PROXYCONFIG_COUNT) { + LOG(("unknown proxy type: %" PRId32 "; assuming direct\n", type)); + type = PROXYCONFIG_DIRECT; + } + mProxyConfig = type; + reloadPAC = true; + } + + if (mProxyConfig == PROXYCONFIG_SYSTEM) { + mSystemProxySettings = do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID); + if (!mSystemProxySettings) mProxyConfig = PROXYCONFIG_DIRECT; + ResetPACThread(); + } else { + if (mSystemProxySettings) { + mSystemProxySettings = nullptr; + ResetPACThread(); + } + } + } + + if (!pref || !strcmp(pref, PROXY_PREF("http"))) { + proxy_GetStringPref(prefBranch, PROXY_PREF("http"), mHTTPProxyHost); + } + + if (!pref || !strcmp(pref, PROXY_PREF("http_port"))) { + proxy_GetIntPref(prefBranch, PROXY_PREF("http_port"), mHTTPProxyPort); + } + + if (!pref || !strcmp(pref, PROXY_PREF("ssl"))) { + proxy_GetStringPref(prefBranch, PROXY_PREF("ssl"), mHTTPSProxyHost); + } + + if (!pref || !strcmp(pref, PROXY_PREF("ssl_port"))) { + proxy_GetIntPref(prefBranch, PROXY_PREF("ssl_port"), mHTTPSProxyPort); + } + + if (!pref || !strcmp(pref, PROXY_PREF("socks"))) { + proxy_GetStringPref(prefBranch, PROXY_PREF("socks"), mSOCKSProxyTarget); + } + + if (!pref || !strcmp(pref, PROXY_PREF("socks_port"))) { + proxy_GetIntPref(prefBranch, PROXY_PREF("socks_port"), mSOCKSProxyPort); + } + + if (!pref || !strcmp(pref, PROXY_PREF("socks_version"))) { + int32_t version; + proxy_GetIntPref(prefBranch, PROXY_PREF("socks_version"), version); + // make sure this preference value remains sane + if (version == 5) { + mSOCKSProxyVersion = 5; + } else { + mSOCKSProxyVersion = 4; + } + } + + if (!pref || !strcmp(pref, PROXY_PREF("socks_remote_dns"))) { + proxy_GetBoolPref(prefBranch, PROXY_PREF("socks_remote_dns"), + mSOCKSProxyRemoteDNS); + } + + if (!pref || !strcmp(pref, PROXY_PREF("proxy_over_tls"))) { + proxy_GetBoolPref(prefBranch, PROXY_PREF("proxy_over_tls"), mProxyOverTLS); + } + + if (!pref || !strcmp(pref, PROXY_PREF("enable_wpad_over_dhcp"))) { + proxy_GetBoolPref(prefBranch, PROXY_PREF("enable_wpad_over_dhcp"), + mWPADOverDHCPEnabled); + reloadPAC = reloadPAC || mProxyConfig == PROXYCONFIG_WPAD; + } + + if (!pref || !strcmp(pref, PROXY_PREF("failover_timeout"))) { + proxy_GetIntPref(prefBranch, PROXY_PREF("failover_timeout"), + mFailedProxyTimeout); + } + + if (!pref || !strcmp(pref, PROXY_PREF("no_proxies_on"))) { + rv = prefBranch->GetCharPref(PROXY_PREF("no_proxies_on"), tempString); + if (NS_SUCCEEDED(rv)) LoadHostFilters(tempString); + } + + // We're done if not using something that could give us a PAC URL + // (PAC, WPAD or System) + if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD && + mProxyConfig != PROXYCONFIG_SYSTEM) { + return; + } + + // OK, we need to reload the PAC file if: + // 1) network.proxy.type changed, or + // 2) network.proxy.autoconfig_url changed and PAC is configured + + if (!pref || !strcmp(pref, PROXY_PREF("autoconfig_url"))) reloadPAC = true; + + if (reloadPAC) { + tempString.Truncate(); + if (mProxyConfig == PROXYCONFIG_PAC) { + prefBranch->GetCharPref(PROXY_PREF("autoconfig_url"), tempString); + if (mPACMan && !mPACMan->IsPACURI(tempString)) { + LOG(("PAC Thread URI Changed - Reset Pac Thread")); + ResetPACThread(); + } + } else if (mProxyConfig == PROXYCONFIG_WPAD) { + LOG(("Auto-detecting proxy - Reset Pac Thread")); + ResetPACThread(); + } else if (mSystemProxySettings) { + // Get System Proxy settings if available + AsyncConfigureFromPAC(false, false); + } + if (!tempString.IsEmpty() || mProxyConfig == PROXYCONFIG_WPAD) { + ConfigureFromPAC(tempString, false); + } + } +} + +bool nsProtocolProxyService::CanUseProxy(nsIURI* aURI, int32_t defaultPort) { + int32_t port; + nsAutoCString host; + + nsresult rv = aURI->GetAsciiHost(host); + if (NS_FAILED(rv) || host.IsEmpty()) return false; + + rv = aURI->GetPort(&port); + if (NS_FAILED(rv)) return false; + if (port == -1) port = defaultPort; + + PRNetAddr addr; + bool is_ipaddr = (PR_StringToNetAddr(host.get(), &addr) == PR_SUCCESS); + + PRIPv6Addr ipv6; + if (is_ipaddr) { + // convert parsed address to IPv6 + if (addr.raw.family == PR_AF_INET) { + // convert to IPv4-mapped address + PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &ipv6); + } else if (addr.raw.family == PR_AF_INET6) { + // copy the address + memcpy(&ipv6, &addr.ipv6.ip, sizeof(PRIPv6Addr)); + } else { + NS_WARNING("unknown address family"); + return true; // allow proxying + } + } + + // Don't use proxy for local hosts (plain hostname, no dots) + if ((!is_ipaddr && mFilterLocalHosts && !host.Contains('.')) || + // This method detects if we have network.proxy.allow_hijacking_localhost + // pref enabled. If it's true then this method will always return false + // otherwise it returns true if the host matches an address that's + // hardcoded to the loopback address. + (!StaticPrefs::network_proxy_allow_hijacking_localhost() && + nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(host))) { + LOG(("Not using proxy for this local host [%s]!\n", host.get())); + return false; // don't allow proxying + } + + int32_t index = -1; + while (++index < int32_t(mHostFiltersArray.Length())) { + const auto& hinfo = mHostFiltersArray[index]; + + if (is_ipaddr != hinfo->is_ipaddr) continue; + if (hinfo->port && hinfo->port != port) continue; + + if (is_ipaddr) { + // generate masked version of target IPv6 address + PRIPv6Addr masked; + memcpy(&masked, &ipv6, sizeof(PRIPv6Addr)); + proxy_MaskIPv6Addr(masked, hinfo->ip.mask_len); + + // check for a match + if (memcmp(&masked, &hinfo->ip.addr, sizeof(PRIPv6Addr)) == 0) { + return false; // proxy disallowed + } + } else { + uint32_t host_len = host.Length(); + uint32_t filter_host_len = hinfo->name.host_len; + + if (host_len >= filter_host_len) { + // + // compare last |filter_host_len| bytes of target hostname. + // + const char* host_tail = host.get() + host_len - filter_host_len; + if (!nsCRT::strncasecmp(host_tail, hinfo->name.host, filter_host_len)) { + // If the tail of the host string matches the filter + + if (filter_host_len > 0 && hinfo->name.host[0] == '.') { + // If the filter was of the form .foo.bar.tld, all such + // matches are correct + return false; // proxy disallowed + } + + // abc-def.example.org should not match def.example.org + // however, *.def.example.org should match .def.example.org + // We check that the filter doesn't start with a `.`. If it does, + // then the strncasecmp above should suffice. If it doesn't, + // then we should only consider it a match if the strncasecmp happened + // at a subdomain boundary + if (host_len > filter_host_len && *(host_tail - 1) == '.') { + // If the host was something.foo.bar.tld and the filter + // was foo.bar.tld, it's still a match. + // the character right before the tail must be a + // `.` for this to work + return false; // proxy disallowed + } + + if (host_len == filter_host_len) { + // If the host and filter are of the same length, + // they should match + return false; // proxy disallowed + } + } + } + } + } + return true; +} + +// kProxyType\* may be referred to externally in +// nsProxyInfo in order to compare by string pointer +const char kProxyType_HTTP[] = "http"; +const char kProxyType_HTTPS[] = "https"; +const char kProxyType_PROXY[] = "proxy"; +const char kProxyType_SOCKS[] = "socks"; +const char kProxyType_SOCKS4[] = "socks4"; +const char kProxyType_SOCKS5[] = "socks5"; +const char kProxyType_DIRECT[] = "direct"; + +const char* nsProtocolProxyService::ExtractProxyInfo(const char* start, + uint32_t aResolveFlags, + nsProxyInfo** result) { + *result = nullptr; + uint32_t flags = 0; + + // see BNF in ProxyAutoConfig.h and notes in nsISystemProxySettings.idl + + // find end of proxy info delimiter + const char* end = start; + while (*end && *end != ';') ++end; + + // find end of proxy type delimiter + const char* sp = start; + while (sp < end && *sp != ' ' && *sp != '\t') ++sp; + + uint32_t len = sp - start; + const char* type = nullptr; + switch (len) { + case 4: + if (nsCRT::strncasecmp(start, kProxyType_HTTP, 4) == 0) { + type = kProxyType_HTTP; + } + break; + case 5: + if (nsCRT::strncasecmp(start, kProxyType_PROXY, 5) == 0) { + type = kProxyType_HTTP; + } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS, 5) == 0) { + type = kProxyType_SOCKS4; // assume v4 for 4x compat + if (StaticPrefs::network_proxy_default_pac_script_socks_version() == + 5) { + type = kProxyType_SOCKS; + } + } else if (nsCRT::strncasecmp(start, kProxyType_HTTPS, 5) == 0) { + type = kProxyType_HTTPS; + } + break; + case 6: + if (nsCRT::strncasecmp(start, kProxyType_DIRECT, 6) == 0) { + type = kProxyType_DIRECT; + } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS4, 6) == 0) { + type = kProxyType_SOCKS4; + } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS5, 6) == 0) { + // map "SOCKS5" to "socks" to match contract-id of registered + // SOCKS-v5 socket provider. + type = kProxyType_SOCKS; + } + break; + } + if (type) { + int32_t port = -1; + + // If it's a SOCKS5 proxy, do name resolution on the server side. + // We could use this with SOCKS4a servers too, but they might not + // support it. + if (type == kProxyType_SOCKS || mSOCKSProxyRemoteDNS) { + flags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST; + } + + // extract host:port + start = sp; + while ((*start == ' ' || *start == '\t') && start < end) start++; + + // port defaults + if (type == kProxyType_HTTP) { + port = 80; + } else if (type == kProxyType_HTTPS) { + port = 443; + } else { + port = 1080; + } + + RefPtr<nsProxyInfo> pi = new nsProxyInfo(); + pi->mType = type; + pi->mFlags = flags; + pi->mResolveFlags = aResolveFlags; + pi->mTimeout = mFailedProxyTimeout; + + // www.foo.com:8080 and http://www.foo.com:8080 + nsDependentCSubstring maybeURL(start, end - start); + nsCOMPtr<nsIURI> pacURI; + + nsAutoCString urlHost; + // First assume the scheme is present, e.g. http://www.example.com:8080 + if (NS_FAILED(NS_NewURI(getter_AddRefs(pacURI), maybeURL)) || + NS_FAILED(pacURI->GetAsciiHost(urlHost)) || urlHost.IsEmpty()) { + // It isn't, assume www.example.com:8080 + maybeURL.Insert("http://", 0); + + if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(pacURI), maybeURL))) { + pacURI->GetAsciiHost(urlHost); + } + } + + if (!urlHost.IsEmpty()) { + pi->mHost = urlHost; + + int32_t tPort; + if (NS_SUCCEEDED(pacURI->GetPort(&tPort)) && tPort != -1) { + port = tPort; + } + pi->mPort = port; + } + + pi.forget(result); + } + + while (*end == ';' || *end == ' ' || *end == '\t') ++end; + return end; +} + +void nsProtocolProxyService::GetProxyKey(nsProxyInfo* pi, nsCString& key) { + key.AssignASCII(pi->mType); + if (!pi->mHost.IsEmpty()) { + key.Append(' '); + key.Append(pi->mHost); + key.Append(':'); + key.AppendInt(pi->mPort); + } +} + +uint32_t nsProtocolProxyService::SecondsSinceSessionStart() { + PRTime now = PR_Now(); + + // get time elapsed since session start + int64_t diff = now - mSessionStart; + + // convert microseconds to seconds + diff /= PR_USEC_PER_SEC; + + // return converted 32 bit value + return uint32_t(diff); +} + +void nsProtocolProxyService::EnableProxy(nsProxyInfo* pi) { + nsAutoCString key; + GetProxyKey(pi, key); + mFailedProxies.Remove(key); +} + +void nsProtocolProxyService::DisableProxy(nsProxyInfo* pi) { + nsAutoCString key; + GetProxyKey(pi, key); + + uint32_t dsec = SecondsSinceSessionStart(); + + // Add timeout to interval (this is the time when the proxy can + // be tried again). + dsec += pi->mTimeout; + + // NOTE: The classic codebase would increase the timeout value + // incrementally each time a subsequent failure occurred. + // We could do the same, but it would require that we not + // remove proxy entries in IsProxyDisabled or otherwise + // change the way we are recording disabled proxies. + // Simpler is probably better for now, and at least the + // user can tune the timeout setting via preferences. + + LOG(("DisableProxy %s %d\n", key.get(), dsec)); + + // If this fails, oh well... means we don't have enough memory + // to remember the failed proxy. + mFailedProxies.InsertOrUpdate(key, dsec); +} + +bool nsProtocolProxyService::IsProxyDisabled(nsProxyInfo* pi) { + nsAutoCString key; + GetProxyKey(pi, key); + + uint32_t val; + if (!mFailedProxies.Get(key, &val)) return false; + + uint32_t dsec = SecondsSinceSessionStart(); + + // if time passed has exceeded interval, then try proxy again. + if (dsec > val) { + mFailedProxies.Remove(key); + return false; + } + + return true; +} + +nsresult nsProtocolProxyService::SetupPACThread( + nsISerialEventTarget* mainThreadEventTarget) { + if (mIsShutdown) { + return NS_ERROR_FAILURE; + } + + if (mPACMan) return NS_OK; + + mPACMan = new nsPACMan(mainThreadEventTarget); + + bool mainThreadOnly; + nsresult rv; + if (mSystemProxySettings && + NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) && + !mainThreadOnly) { + rv = mPACMan->Init(mSystemProxySettings); + } else { + rv = mPACMan->Init(nullptr); + } + if (NS_FAILED(rv)) { + mPACMan->Shutdown(); + mPACMan = nullptr; + } + return rv; +} + +nsresult nsProtocolProxyService::ResetPACThread() { + if (!mPACMan) return NS_OK; + + mPACMan->Shutdown(); + mPACMan = nullptr; + return SetupPACThread(); +} + +nsresult nsProtocolProxyService::ConfigureFromPAC(const nsCString& spec, + bool forceReload) { + nsresult rv = SetupPACThread(); + NS_ENSURE_SUCCESS(rv, rv); + + bool autodetect = spec.IsEmpty(); + if (!forceReload && ((!autodetect && mPACMan->IsPACURI(spec)) || + (autodetect && mPACMan->IsUsingWPAD()))) { + return NS_OK; + } + + mFailedProxies.Clear(); + + mPACMan->SetWPADOverDHCPEnabled(mWPADOverDHCPEnabled); + return mPACMan->LoadPACFromURI(spec); +} + +void nsProtocolProxyService::ProcessPACString(const nsCString& pacString, + uint32_t aResolveFlags, + nsIProxyInfo** result) { + if (pacString.IsEmpty()) { + *result = nullptr; + return; + } + + const char* proxies = pacString.get(); + + nsProxyInfo *pi = nullptr, *first = nullptr, *last = nullptr; + while (*proxies) { + proxies = ExtractProxyInfo(proxies, aResolveFlags, &pi); + if (pi && (pi->mType == kProxyType_HTTPS) && !mProxyOverTLS) { + delete pi; + pi = nullptr; + } + + if (pi) { + if (last) { + NS_ASSERTION(last->mNext == nullptr, "leaking nsProxyInfo"); + last->mNext = pi; + } else { + first = pi; + } + last = pi; + } + } + *result = first; +} + +// nsIProtocolProxyService2 +NS_IMETHODIMP +nsProtocolProxyService::ReloadPAC() { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) return NS_OK; + + int32_t type; + nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type); + if (NS_FAILED(rv)) return NS_OK; + + nsAutoCString pacSpec; + if (type == PROXYCONFIG_PAC) { + prefs->GetCharPref(PROXY_PREF("autoconfig_url"), pacSpec); + } else if (type == PROXYCONFIG_SYSTEM) { + if (mSystemProxySettings) { + AsyncConfigureFromPAC(true, true); + } else { + ResetPACThread(); + } + } + + if (!pacSpec.IsEmpty() || type == PROXYCONFIG_WPAD) { + ConfigureFromPAC(pacSpec, true); + } + return NS_OK; +} + +// When sync interface is removed this can go away too +// The nsPACManCallback portion of this implementation should be run +// off the main thread, because it uses a condvar for signaling and +// the main thread is blocking on that condvar - +// so call nsPACMan::AsyncGetProxyForURI() with +// a false mainThreadResponse parameter. +class nsAsyncBridgeRequest final : public nsPACManCallback { + NS_DECL_THREADSAFE_ISUPPORTS + + nsAsyncBridgeRequest() + : mMutex("nsDeprecatedCallback"), + mCondVar(mMutex, "nsDeprecatedCallback") {} + + void OnQueryComplete(nsresult status, const nsACString& pacString, + const nsACString& newPACURL) override { + MutexAutoLock lock(mMutex); + mCompleted = true; + mStatus = status; + mPACString = pacString; + mPACURL = newPACURL; + mCondVar.Notify(); + } + + void Lock() MOZ_CAPABILITY_ACQUIRE(mMutex) { mMutex.Lock(); } + void Unlock() MOZ_CAPABILITY_RELEASE(mMutex) { mMutex.Unlock(); } + void Wait() { mCondVar.Wait(TimeDuration::FromSeconds(3)); } + + private: + ~nsAsyncBridgeRequest() = default; + + friend class nsProtocolProxyService; + + Mutex mMutex; + CondVar mCondVar; + + nsresult mStatus MOZ_GUARDED_BY(mMutex){NS_OK}; + nsCString mPACString MOZ_GUARDED_BY(mMutex); + nsCString mPACURL MOZ_GUARDED_BY(mMutex); + bool mCompleted MOZ_GUARDED_BY(mMutex){false}; +}; +NS_IMPL_ISUPPORTS0(nsAsyncBridgeRequest) + +nsresult nsProtocolProxyService::AsyncResolveInternal( + nsIChannel* channel, uint32_t flags, nsIProtocolProxyCallback* callback, + nsICancelable** result, bool isSyncOK, + nsISerialEventTarget* mainThreadEventTarget) { + NS_ENSURE_ARG_POINTER(channel); + NS_ENSURE_ARG_POINTER(callback); + + nsCOMPtr<nsIURI> uri; + nsresult rv = GetProxyURI(channel, getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + *result = nullptr; + RefPtr<nsAsyncResolveRequest> ctx = + new nsAsyncResolveRequest(this, channel, flags, callback); + + nsProtocolInfo info; + rv = GetProtocolInfo(uri, &info); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIProxyInfo> pi; + bool usePACThread; + + // adapt to realtime changes in the system proxy service + if (mProxyConfig == PROXYCONFIG_SYSTEM) { + nsCOMPtr<nsISystemProxySettings> sp2 = + do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID); + if (sp2 != mSystemProxySettings) { + mSystemProxySettings = sp2; + ResetPACThread(); + } + } + + rv = SetupPACThread(mainThreadEventTarget); + if (NS_FAILED(rv)) { + return rv; + } + + // SystemProxySettings and PAC files can block the main thread + // but if neither of them are in use, we can just do the work + // right here and directly invoke the callback + + rv = + Resolve_Internal(channel, info, flags, &usePACThread, getter_AddRefs(pi)); + if (NS_FAILED(rv)) return rv; + + if (!usePACThread || !mPACMan) { + // we can do it locally + rv = ctx->ProcessLocally(info, pi, isSyncOK); + if (NS_SUCCEEDED(rv) && !isSyncOK) { + ctx.forget(result); + } + return rv; + } + + // else kick off a PAC thread query + rv = mPACMan->AsyncGetProxyForURI(uri, ctx, flags, true); + if (NS_SUCCEEDED(rv)) ctx.forget(result); + return rv; +} + +// nsIProtocolProxyService +NS_IMETHODIMP +nsProtocolProxyService::AsyncResolve2( + nsIChannel* channel, uint32_t flags, nsIProtocolProxyCallback* callback, + nsISerialEventTarget* mainThreadEventTarget, nsICancelable** result) { + return AsyncResolveInternal(channel, flags, callback, result, true, + mainThreadEventTarget); +} + +NS_IMETHODIMP +nsProtocolProxyService::AsyncResolve( + nsISupports* channelOrURI, uint32_t flags, + nsIProtocolProxyCallback* callback, + nsISerialEventTarget* mainThreadEventTarget, nsICancelable** result) { + nsresult rv; + // Check if we got a channel: + nsCOMPtr<nsIChannel> channel = do_QueryInterface(channelOrURI); + if (!channel) { + nsCOMPtr<nsIURI> uri = do_QueryInterface(channelOrURI); + if (!uri) { + return NS_ERROR_NO_INTERFACE; + } + + // creating a temporary channel from the URI which is not + // used to perform any network loads, hence its safe to + // use systemPrincipal as the loadingPrincipal. + rv = NS_NewChannel(getter_AddRefs(channel), uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + } + + return AsyncResolveInternal(channel, flags, callback, result, false, + mainThreadEventTarget); +} + +NS_IMETHODIMP +nsProtocolProxyService::NewProxyInfo( + const nsACString& aType, const nsACString& aHost, int32_t aPort, + const nsACString& aProxyAuthorizationHeader, + const nsACString& aConnectionIsolationKey, uint32_t aFlags, + uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy, + nsIProxyInfo** aResult) { + return NewProxyInfoWithAuth(aType, aHost, aPort, ""_ns, ""_ns, + aProxyAuthorizationHeader, + aConnectionIsolationKey, aFlags, aFailoverTimeout, + aFailoverProxy, aResult); +} + +NS_IMETHODIMP +nsProtocolProxyService::NewProxyInfoWithAuth( + const nsACString& aType, const nsACString& aHost, int32_t aPort, + const nsACString& aUsername, const nsACString& aPassword, + const nsACString& aProxyAuthorizationHeader, + const nsACString& aConnectionIsolationKey, uint32_t aFlags, + uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy, + nsIProxyInfo** aResult) { + static const char* types[] = {kProxyType_HTTP, kProxyType_HTTPS, + kProxyType_SOCKS, kProxyType_SOCKS4, + kProxyType_DIRECT}; + + // resolve type; this allows us to avoid copying the type string into each + // proxy info instance. we just reference the string literals directly :) + const char* type = nullptr; + for (auto& t : types) { + if (aType.LowerCaseEqualsASCII(t)) { + type = t; + break; + } + } + NS_ENSURE_TRUE(type, NS_ERROR_INVALID_ARG); + + // We have only implemented username/password for SOCKS proxies. + if ((!aUsername.IsEmpty() || !aPassword.IsEmpty()) && + !aType.LowerCaseEqualsASCII(kProxyType_SOCKS) && + !aType.LowerCaseEqualsASCII(kProxyType_SOCKS4)) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + return NewProxyInfo_Internal(type, aHost, aPort, aUsername, aPassword, + aProxyAuthorizationHeader, + aConnectionIsolationKey, aFlags, + aFailoverTimeout, aFailoverProxy, 0, aResult); +} + +NS_IMETHODIMP +nsProtocolProxyService::GetFailoverForProxy(nsIProxyInfo* aProxy, nsIURI* aURI, + nsresult aStatus, + nsIProxyInfo** aResult) { + // Failover is supported through a variety of methods including: + // * PAC scripts (PROXYCONFIG_PAC and PROXYCONFIG_WPAD) + // * System proxy + // * Extensions + // With extensions the mProxyConfig can be any type and the extension + // is still involved in the proxy filtering. It may have also supplied + // any number of failover proxies. We cannot determine what the mix is + // here, so we will attempt to get a failover regardless of the config + // type. MANUAL configuration will not disable a proxy. + + // Verify that |aProxy| is one of our nsProxyInfo objects. + nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy); + NS_ENSURE_ARG(pi); + // OK, the QI checked out. We can proceed. + + // Remember that this proxy is down. If the user has manually configured some + // proxies we do not want to disable them. + if (mProxyConfig != PROXYCONFIG_MANUAL) { + DisableProxy(pi); + } + + // NOTE: At this point, we might want to prompt the user if we have + // not already tried going DIRECT. This is something that the + // classic codebase supported; however, IE6 does not prompt. + + if (!pi->mNext) return NS_ERROR_NOT_AVAILABLE; + + LOG(("PAC failover from %s %s:%d to %s %s:%d\n", pi->mType, pi->mHost.get(), + pi->mPort, pi->mNext->mType, pi->mNext->mHost.get(), pi->mNext->mPort)); + + *aResult = do_AddRef(pi->mNext).take(); + return NS_OK; +} + +namespace { // anon + +class ProxyFilterPositionComparator { + using FilterLinkRef = RefPtr<nsProtocolProxyService::FilterLink>; + + public: + bool Equals(const FilterLinkRef& a, const FilterLinkRef& b) const { + return a->position == b->position; + } + bool LessThan(const FilterLinkRef& a, const FilterLinkRef& b) const { + return a->position < b->position; + } +}; + +class ProxyFilterObjectComparator { + using FilterLinkRef = RefPtr<nsProtocolProxyService::FilterLink>; + + public: + bool Equals(const FilterLinkRef& link, const nsISupports* obj) const { + return obj == nsCOMPtr<nsISupports>(do_QueryInterface(link->filter)) || + obj == nsCOMPtr<nsISupports>(do_QueryInterface(link->channelFilter)); + } +}; + +} // namespace + +nsresult nsProtocolProxyService::InsertFilterLink(RefPtr<FilterLink>&& link) { + LOG(("nsProtocolProxyService::InsertFilterLink filter=%p", link.get())); + + if (mIsShutdown) { + return NS_ERROR_FAILURE; + } + + // If we add a new element with the same position as an existing one, we want + // to preserve the insertion order to avoid surprises. + mFilters.InsertElementSorted(link, ProxyFilterPositionComparator()); + + NotifyProxyConfigChangedInternal(); + + return NS_OK; +} + +NS_IMETHODIMP +nsProtocolProxyService::RegisterFilter(nsIProtocolProxyFilter* filter, + uint32_t position) { + UnregisterFilter(filter); // remove this filter if we already have it + + RefPtr<FilterLink> link = new FilterLink(position, filter); + return InsertFilterLink(std::move(link)); +} + +NS_IMETHODIMP +nsProtocolProxyService::RegisterChannelFilter( + nsIProtocolProxyChannelFilter* channelFilter, uint32_t position) { + UnregisterChannelFilter( + channelFilter); // remove this filter if we already have it + + RefPtr<FilterLink> link = new FilterLink(position, channelFilter); + return InsertFilterLink(std::move(link)); +} + +nsresult nsProtocolProxyService::RemoveFilterLink(nsISupports* givenObject) { + LOG(("nsProtocolProxyService::RemoveFilterLink target=%p", givenObject)); + + nsresult rv = + mFilters.RemoveElement(givenObject, ProxyFilterObjectComparator()) + ? NS_OK + : NS_ERROR_UNEXPECTED; + if (NS_SUCCEEDED(rv)) { + NotifyProxyConfigChangedInternal(); + } + + return rv; +} + +NS_IMETHODIMP +nsProtocolProxyService::UnregisterFilter(nsIProtocolProxyFilter* filter) { + // QI to nsISupports so we can safely test object identity. + nsCOMPtr<nsISupports> givenObject = do_QueryInterface(filter); + return RemoveFilterLink(givenObject); +} + +NS_IMETHODIMP +nsProtocolProxyService::UnregisterChannelFilter( + nsIProtocolProxyChannelFilter* channelFilter) { + // QI to nsISupports so we can safely test object identity. + nsCOMPtr<nsISupports> givenObject = do_QueryInterface(channelFilter); + return RemoveFilterLink(givenObject); +} + +NS_IMETHODIMP +nsProtocolProxyService::GetProxyConfigType(uint32_t* aProxyConfigType) { + *aProxyConfigType = mProxyConfig; + return NS_OK; +} + +void nsProtocolProxyService::LoadHostFilters(const nsACString& aFilters) { + if (mIsShutdown) { + return; + } + + // check to see the owners flag? /!?/ TODO + if (mHostFiltersArray.Length() > 0) { + mHostFiltersArray.Clear(); + } + + // Reset mFilterLocalHosts - will be set to true if "<local>" is in pref + // string + mFilterLocalHosts = false; + + if (aFilters.IsEmpty()) { + return; + } + + // + // filter = ( host | domain | ipaddr ["/" mask] ) [":" port] + // filters = filter *( "," LWS filter) + // + mozilla::Tokenizer t(aFilters); + mozilla::Tokenizer::Token token; + bool eof = false; + // while (*filters) { + while (!eof) { + // skip over spaces and , + t.SkipWhites(); + while (t.CheckChar(',')) { + t.SkipWhites(); + } + + nsAutoCString portStr; + nsAutoCString hostStr; + nsAutoCString maskStr; + t.Record(); + + bool parsingIPv6 = false; + bool parsingPort = false; + bool parsingMask = false; + while (t.Next(token)) { + if (token.Equals(mozilla::Tokenizer::Token::EndOfFile())) { + eof = true; + break; + } + if (token.Equals(mozilla::Tokenizer::Token::Char(',')) || + token.Type() == mozilla::Tokenizer::TOKEN_WS) { + break; + } + + if (token.Equals(mozilla::Tokenizer::Token::Char('['))) { + parsingIPv6 = true; + continue; + } + + if (!parsingIPv6 && token.Equals(mozilla::Tokenizer::Token::Char(':'))) { + // Port is starting. Claim the previous as host. + if (parsingMask) { + t.Claim(maskStr); + } else { + t.Claim(hostStr); + } + t.Record(); + parsingPort = true; + continue; + } + + if (token.Equals(mozilla::Tokenizer::Token::Char('/'))) { + t.Claim(hostStr); + t.Record(); + parsingMask = true; + continue; + } + + if (token.Equals(mozilla::Tokenizer::Token::Char(']'))) { + parsingIPv6 = false; + continue; + } + } + if (!parsingPort && !parsingMask) { + t.Claim(hostStr); + } else if (parsingPort) { + t.Claim(portStr); + } else if (parsingMask) { + t.Claim(maskStr); + } else { + NS_WARNING("Could not parse this rule"); + continue; + } + + if (hostStr.IsEmpty()) { + continue; + } + + // If the current host filter is "<local>", then all local (i.e. + // no dots in the hostname) hosts should bypass the proxy + if (hostStr.EqualsIgnoreCase("<local>")) { + mFilterLocalHosts = true; + LOG( + ("loaded filter for local hosts " + "(plain host names, no dots)\n")); + // Continue to next host filter; + continue; + } + + // For all other host filters, create HostInfo object and add to list + HostInfo* hinfo = new HostInfo(); + nsresult rv = NS_OK; + + int32_t port = portStr.ToInteger(&rv); + if (NS_FAILED(rv)) { + port = 0; + } + hinfo->port = port; + + int32_t maskLen = maskStr.ToInteger(&rv); + if (NS_FAILED(rv)) { + maskLen = 128; + } + + // PR_StringToNetAddr can't parse brackets enclosed IPv6 + nsAutoCString addrString = hostStr; + if (hostStr.First() == '[' && hostStr.Last() == ']') { + addrString = Substring(hostStr, 1, hostStr.Length() - 2); + } + + PRNetAddr addr; + if (PR_StringToNetAddr(addrString.get(), &addr) == PR_SUCCESS) { + hinfo->is_ipaddr = true; + hinfo->ip.family = PR_AF_INET6; // we always store address as IPv6 + hinfo->ip.mask_len = maskLen; + + if (hinfo->ip.mask_len == 0) { + NS_WARNING("invalid mask"); + goto loser; + } + + if (addr.raw.family == PR_AF_INET) { + // convert to IPv4-mapped address + PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &hinfo->ip.addr); + // adjust mask_len accordingly + if (hinfo->ip.mask_len <= 32) hinfo->ip.mask_len += 96; + } else if (addr.raw.family == PR_AF_INET6) { + // copy the address + memcpy(&hinfo->ip.addr, &addr.ipv6.ip, sizeof(PRIPv6Addr)); + } else { + NS_WARNING("unknown address family"); + goto loser; + } + + // apply mask to IPv6 address + proxy_MaskIPv6Addr(hinfo->ip.addr, hinfo->ip.mask_len); + } else { + nsAutoCString host; + if (hostStr.First() == '*') { + host = Substring(hostStr, 1); + } else { + host = hostStr; + } + + if (host.IsEmpty()) { + hinfo->name.host = nullptr; + goto loser; + } + + hinfo->name.host_len = host.Length(); + + hinfo->is_ipaddr = false; + hinfo->name.host = ToNewCString(host, mozilla::fallible); + + if (!hinfo->name.host) goto loser; + } + +// #define DEBUG_DUMP_FILTERS +#ifdef DEBUG_DUMP_FILTERS + printf("loaded filter[%zu]:\n", mHostFiltersArray.Length()); + printf(" is_ipaddr = %u\n", hinfo->is_ipaddr); + printf(" port = %u\n", hinfo->port); + printf(" host = %s\n", hostStr.get()); + if (hinfo->is_ipaddr) { + printf(" ip.family = %x\n", hinfo->ip.family); + printf(" ip.mask_len = %u\n", hinfo->ip.mask_len); + + PRNetAddr netAddr; + PR_SetNetAddr(PR_IpAddrNull, PR_AF_INET6, 0, &netAddr); + memcpy(&netAddr.ipv6.ip, &hinfo->ip.addr, sizeof(hinfo->ip.addr)); + + char buf[256]; + PR_NetAddrToString(&netAddr, buf, sizeof(buf)); + + printf(" ip.addr = %s\n", buf); + } else { + printf(" name.host = %s\n", hinfo->name.host); + } +#endif + + mHostFiltersArray.AppendElement(hinfo); + hinfo = nullptr; + loser: + delete hinfo; + } +} + +nsresult nsProtocolProxyService::GetProtocolInfo(nsIURI* uri, + nsProtocolInfo* info) { + AssertIsOnMainThread(); + MOZ_ASSERT(uri, "URI is null"); + MOZ_ASSERT(info, "info is null"); + + nsresult rv; + + rv = uri->GetScheme(info->scheme); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv); + if (NS_FAILED(rv)) return rv; + + rv = ios->GetDynamicProtocolFlags(uri, &info->flags); + if (NS_FAILED(rv)) return rv; + + rv = ios->GetDefaultPort(info->scheme.get(), &info->defaultPort); + return rv; +} + +nsresult nsProtocolProxyService::NewProxyInfo_Internal( + const char* aType, const nsACString& aHost, int32_t aPort, + const nsACString& aUsername, const nsACString& aPassword, + const nsACString& aProxyAuthorizationHeader, + const nsACString& aConnectionIsolationKey, uint32_t aFlags, + uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy, + uint32_t aResolveFlags, nsIProxyInfo** aResult) { + if (aPort <= 0) aPort = -1; + + nsCOMPtr<nsProxyInfo> failover; + if (aFailoverProxy) { + failover = do_QueryInterface(aFailoverProxy); + NS_ENSURE_ARG(failover); + } + + RefPtr<nsProxyInfo> proxyInfo = new nsProxyInfo(); + + proxyInfo->mType = aType; + proxyInfo->mHost = aHost; + proxyInfo->mPort = aPort; + proxyInfo->mUsername = aUsername; + proxyInfo->mPassword = aPassword; + proxyInfo->mFlags = aFlags; + proxyInfo->mResolveFlags = aResolveFlags; + proxyInfo->mTimeout = + aFailoverTimeout == UINT32_MAX ? mFailedProxyTimeout : aFailoverTimeout; + proxyInfo->mProxyAuthorizationHeader = aProxyAuthorizationHeader; + proxyInfo->mConnectionIsolationKey = aConnectionIsolationKey; + failover.swap(proxyInfo->mNext); + + proxyInfo.forget(aResult); + return NS_OK; +} + +nsresult nsProtocolProxyService::Resolve_Internal(nsIChannel* channel, + const nsProtocolInfo& info, + uint32_t flags, + bool* usePACThread, + nsIProxyInfo** result) { + NS_ENSURE_ARG_POINTER(channel); + + *usePACThread = false; + *result = nullptr; + + if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY)) { + return NS_OK; // Can't proxy this (filters may not override) + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = GetProxyURI(channel, getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + // See bug #586908. + // Avoid endless loop if |uri| is the current PAC-URI. Returning OK + // here means that we will not use a proxy for this connection. + if (mPACMan && mPACMan->IsPACURI(uri)) return NS_OK; + + // if proxies are enabled and this host:port combo is supposed to use a + // proxy, check for a proxy. + if ((mProxyConfig == PROXYCONFIG_DIRECT) || + !CanUseProxy(uri, info.defaultPort)) { + return NS_OK; + } + + bool mainThreadOnly; + if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM && + NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) && + !mainThreadOnly) { + *usePACThread = true; + return NS_OK; + } + + if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM) { + // If the system proxy setting implementation is not threadsafe (e.g + // linux gconf), we'll do it inline here. Such implementations promise + // not to block + // bug 1366133: this block uses GetPACURI & GetProxyForURI, which may + // hang on Windows platform. Fortunately, current implementation on + // Windows is not main thread only, so we are safe here. + + nsAutoCString PACURI; + nsAutoCString pacString; + + if (NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) && + !PACURI.IsEmpty()) { + // There is a PAC URI configured. If it is unchanged, then + // just execute the PAC thread. If it is changed then load + // the new value + + if (mPACMan && mPACMan->IsPACURI(PACURI)) { + // unchanged + *usePACThread = true; + return NS_OK; + } + + ConfigureFromPAC(PACURI, false); + return NS_OK; + } + + nsAutoCString spec; + nsAutoCString host; + nsAutoCString scheme; + int32_t port = -1; + + uri->GetAsciiSpec(spec); + uri->GetAsciiHost(host); + uri->GetScheme(scheme); + uri->GetPort(&port); + + if (flags & RESOLVE_PREFER_SOCKS_PROXY) { + LOG(("Ignoring RESOLVE_PREFER_SOCKS_PROXY for system proxy setting\n")); + } else if (flags & RESOLVE_PREFER_HTTPS_PROXY) { + scheme.AssignLiteral("https"); + } else if (flags & RESOLVE_IGNORE_URI_SCHEME) { + scheme.AssignLiteral("http"); + } + + // now try the system proxy settings for this particular url + if (NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(spec, scheme, host, + port, pacString))) { + nsCOMPtr<nsIProxyInfo> pi; + ProcessPACString(pacString, 0, getter_AddRefs(pi)); + + if (flags & RESOLVE_PREFER_SOCKS_PROXY && + flags & RESOLVE_PREFER_HTTPS_PROXY) { + nsAutoCString type; + pi->GetType(type); + // DIRECT from ProcessPACString indicates that system proxy settings + // are not configured to use SOCKS proxy. Try https proxy as a + // secondary preferrable proxy. This is mainly for websocket whose + // proxy precedence is SOCKS > HTTPS > DIRECT. + if (type.EqualsLiteral(kProxyType_DIRECT)) { + scheme.AssignLiteral(kProxyType_HTTPS); + if (NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI( + spec, scheme, host, port, pacString))) { + ProcessPACString(pacString, 0, getter_AddRefs(pi)); + } + } + } + pi.forget(result); + return NS_OK; + } + } + + // if proxies are enabled and this host:port combo is supposed to use a + // proxy, check for a proxy. + if (mProxyConfig == PROXYCONFIG_DIRECT || + (mProxyConfig == PROXYCONFIG_MANUAL && + !CanUseProxy(uri, info.defaultPort))) { + return NS_OK; + } + + // Proxy auto config magic... + if (mProxyConfig == PROXYCONFIG_PAC || mProxyConfig == PROXYCONFIG_WPAD) { + // Do not query PAC now. + *usePACThread = true; + return NS_OK; + } + + // If we aren't in manual proxy configuration mode then we don't + // want to honor any manual specific prefs that might be still set + if (mProxyConfig != PROXYCONFIG_MANUAL) return NS_OK; + + // proxy info values for manual configuration mode + const char* type = nullptr; + const nsACString* host = nullptr; + int32_t port = -1; + + uint32_t proxyFlags = 0; + + if ((flags & RESOLVE_PREFER_SOCKS_PROXY) && !mSOCKSProxyTarget.IsEmpty() && + (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) { + host = &mSOCKSProxyTarget; + if (mSOCKSProxyVersion == 4) { + type = kProxyType_SOCKS4; + } else { + type = kProxyType_SOCKS; + } + port = mSOCKSProxyPort; + if (mSOCKSProxyRemoteDNS) { + proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST; + } + } else if ((flags & RESOLVE_PREFER_HTTPS_PROXY) && + !mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0) { + host = &mHTTPSProxyHost; + type = kProxyType_HTTP; + port = mHTTPSProxyPort; + } else if (!mHTTPProxyHost.IsEmpty() && mHTTPProxyPort > 0 && + ((flags & RESOLVE_IGNORE_URI_SCHEME) || + info.scheme.EqualsLiteral("http"))) { + host = &mHTTPProxyHost; + type = kProxyType_HTTP; + port = mHTTPProxyPort; + } else if (!mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0 && + !(flags & RESOLVE_IGNORE_URI_SCHEME) && + info.scheme.EqualsLiteral("https")) { + host = &mHTTPSProxyHost; + type = kProxyType_HTTP; + port = mHTTPSProxyPort; + } else if (!mSOCKSProxyTarget.IsEmpty() && + (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) { + host = &mSOCKSProxyTarget; + if (mSOCKSProxyVersion == 4) { + type = kProxyType_SOCKS4; + } else { + type = kProxyType_SOCKS; + } + port = mSOCKSProxyPort; + if (mSOCKSProxyRemoteDNS) { + proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST; + } + } + + if (type) { + rv = NewProxyInfo_Internal(type, *host, port, ""_ns, ""_ns, ""_ns, ""_ns, + proxyFlags, UINT32_MAX, nullptr, flags, result); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + +void nsProtocolProxyService::MaybeDisableDNSPrefetch(nsIProxyInfo* aProxy) { + // Disable Prefetch in the DNS service if a proxy is in use. + if (!aProxy) return; + + nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy); + if (!pi || !pi->mType || pi->mType == kProxyType_DIRECT) return; + + // To avoid getting DNS service recursively, we directly use + // GetXPCOMSingleton(). + nsCOMPtr<nsIDNSService> dns = nsDNSService::GetXPCOMSingleton(); + if (!dns) return; + nsCOMPtr<nsPIDNSService> pdns = do_QueryInterface(dns); + if (!pdns) return; + + // We lose the prefetch optimization for the life of the dns service. + pdns->SetPrefetchEnabled(false); +} + +void nsProtocolProxyService::CopyFilters(nsTArray<RefPtr<FilterLink>>& aCopy) { + MOZ_ASSERT(aCopy.Length() == 0); + aCopy.AppendElements(mFilters); +} + +bool nsProtocolProxyService::ApplyFilter( + FilterLink const* filterLink, nsIChannel* channel, + const nsProtocolInfo& info, nsCOMPtr<nsIProxyInfo> list, + nsIProxyProtocolFilterResult* callback) { + nsresult rv; + + // We prune the proxy list prior to invoking each filter. This may be + // somewhat inefficient, but it seems like a good idea since we want each + // filter to "see" a valid proxy list. + PruneProxyInfo(info, list); + + if (filterLink->filter) { + nsCOMPtr<nsIURI> uri; + Unused << GetProxyURI(channel, getter_AddRefs(uri)); + if (!uri) { + return false; + } + + rv = filterLink->filter->ApplyFilter(uri, list, callback); + return NS_SUCCEEDED(rv); + } + + if (filterLink->channelFilter) { + rv = filterLink->channelFilter->ApplyFilter(channel, list, callback); + return NS_SUCCEEDED(rv); + } + + return false; +} + +void nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo& info, + nsIProxyInfo** list) { + if (!*list) return; + + LOG(("nsProtocolProxyService::PruneProxyInfo ENTER list=%p", *list)); + + nsProxyInfo* head = nullptr; + CallQueryInterface(*list, &head); + if (!head) { + MOZ_ASSERT_UNREACHABLE("nsIProxyInfo must QI to nsProxyInfo"); + return; + } + NS_RELEASE(*list); + + // Pruning of disabled proxies works like this: + // - If all proxies are disabled, return the full list + // - Otherwise, remove the disabled proxies. + // + // Pruning of disallowed proxies works like this: + // - If the protocol handler disallows the proxy, then we disallow it. + + // Start by removing all disallowed proxies if required: + if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY_HTTP)) { + nsProxyInfo *last = nullptr, *iter = head; + while (iter) { + if ((iter->Type() == kProxyType_HTTP) || + (iter->Type() == kProxyType_HTTPS)) { + // reject! + if (last) { + last->mNext = iter->mNext; + } else { + head = iter->mNext; + } + nsProxyInfo* next = iter->mNext; + iter->mNext = nullptr; + iter->Release(); + iter = next; + } else { + last = iter; + iter = iter->mNext; + } + } + if (!head) { + return; + } + } + + // Scan to see if all remaining non-direct proxies are disabled. If so, then + // we'll just bail and return them all. Otherwise, we'll go and prune the + // disabled ones. + + bool allNonDirectProxiesDisabled = true; + + nsProxyInfo* iter; + for (iter = head; iter; iter = iter->mNext) { + if (!IsProxyDisabled(iter) && iter->mType != kProxyType_DIRECT) { + allNonDirectProxiesDisabled = false; + break; + } + } + + if (allNonDirectProxiesDisabled && + StaticPrefs::network_proxy_retry_failed_proxies()) { + LOG(("All proxies are disabled, so trying all again")); + } else { + // remove any disabled proxies. + nsProxyInfo* last = nullptr; + for (iter = head; iter;) { + if (IsProxyDisabled(iter)) { + // reject! + nsProxyInfo* reject = iter; + + iter = iter->mNext; + if (last) { + last->mNext = iter; + } else { + head = iter; + } + + reject->mNext = nullptr; + NS_RELEASE(reject); + continue; + } + + // since we are about to use this proxy, make sure it is not on + // the disabled proxy list. we'll add it back to that list if + // we have to (in GetFailoverForProxy). + // + // XXX(darin): It might be better to do this as a final pass. + // + EnableProxy(iter); + + last = iter; + iter = iter->mNext; + } + } + + // if only DIRECT was specified then return no proxy info, and we're done. + if (head && !head->mNext && head->mType == kProxyType_DIRECT) { + NS_RELEASE(head); + } + + *list = head; // Transfer ownership + + LOG(("nsProtocolProxyService::PruneProxyInfo LEAVE list=%p", *list)); +} + +bool nsProtocolProxyService::GetIsPACLoading() { + return mPACMan && mPACMan->IsLoading(); +} + +NS_IMETHODIMP +nsProtocolProxyService::AddProxyConfigCallback( + nsIProxyConfigChangedCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + if (!aCallback) { + return NS_ERROR_INVALID_ARG; + } + + mProxyConfigChangedCallbacks.AppendElement(aCallback); + return NS_OK; +} + +NS_IMETHODIMP +nsProtocolProxyService::RemoveProxyConfigCallback( + nsIProxyConfigChangedCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + mProxyConfigChangedCallbacks.RemoveElement(aCallback); + return NS_OK; +} + +NS_IMETHODIMP +nsProtocolProxyService::NotifyProxyConfigChangedInternal() { + LOG(("nsProtocolProxyService::NotifyProxyConfigChangedInternal")); + MOZ_ASSERT(NS_IsMainThread()); + + for (const auto& callback : mProxyConfigChangedCallbacks) { + callback->OnProxyConfigChanged(); + } + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsProtocolProxyService.h b/netwerk/base/nsProtocolProxyService.h new file mode 100644 index 0000000000..394450ca6e --- /dev/null +++ b/netwerk/base/nsProtocolProxyService.h @@ -0,0 +1,420 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsProtocolProxyService_h__ +#define nsProtocolProxyService_h__ + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsIProtocolProxyService2.h" +#include "nsIProtocolProxyFilter.h" +#include "nsIProxyInfo.h" +#include "nsIObserver.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsITimer.h" +#include "prio.h" +#include "mozilla/Attributes.h" + +class nsIPrefBranch; +class nsISystemProxySettings; + +namespace mozilla { +namespace net { + +using nsFailedProxyTable = nsTHashMap<nsCStringHashKey, uint32_t>; + +class nsPACMan; +class nsProxyInfo; +struct nsProtocolInfo; + +// CID for the nsProtocolProxyService class +// 091eedd8-8bae-4fe3-ad62-0c87351e640d +#define NS_PROTOCOL_PROXY_SERVICE_IMPL_CID \ + { \ + 0x091eedd8, 0x8bae, 0x4fe3, { \ + 0xad, 0x62, 0x0c, 0x87, 0x35, 0x1e, 0x64, 0x0d \ + } \ + } + +class nsProtocolProxyService final : public nsIProtocolProxyService2, + public nsIObserver, + public nsITimerCallback, + public nsINamed { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROTOCOLPROXYSERVICE2 + NS_DECL_NSIPROTOCOLPROXYSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROTOCOL_PROXY_SERVICE_IMPL_CID) + + nsProtocolProxyService(); + + nsresult Init(); + + public: + // An instance of this struct is allocated for each registered + // nsIProtocolProxyFilter and each nsIProtocolProxyChannelFilter. + class FilterLink { + public: + NS_INLINE_DECL_REFCOUNTING(FilterLink) + + uint32_t position; + nsCOMPtr<nsIProtocolProxyFilter> filter; + nsCOMPtr<nsIProtocolProxyChannelFilter> channelFilter; + + FilterLink(uint32_t p, nsIProtocolProxyFilter* f); + FilterLink(uint32_t p, nsIProtocolProxyChannelFilter* cf); + + private: + ~FilterLink(); + }; + + protected: + friend class nsAsyncResolveRequest; + friend class TestProtocolProxyService_LoadHostFilters_Test; // for gtest + + ~nsProtocolProxyService(); + + /** + * This method is called whenever a preference may have changed or + * to initialize all preferences. + * + * @param prefs + * This must be a pointer to the root pref branch. + * @param name + * This can be the name of a fully-qualified preference, or it can + * be null, in which case all preferences will be initialized. + */ + void PrefsChanged(nsIPrefBranch* prefBranch, const char* pref); + + /** + * This method is called to create a nsProxyInfo instance from the given + * PAC-style proxy string. It parses up to the end of the string, or to + * the next ';' character. + * + * @param proxy + * The PAC-style proxy string to parse. This must not be null. + * @param aResolveFlags + * The flags passed to Resolve or AsyncResolve that are stored in + * proxyInfo. + * @param result + * Upon return this points to a newly allocated nsProxyInfo or null + * if the proxy string was invalid. + * + * @return A pointer beyond the parsed proxy string (never null). + */ + const char* ExtractProxyInfo(const char* start, uint32_t aResolveFlags, + nsProxyInfo** result); + + /** + * Load the specified PAC file. + * + * @param pacURI + * The URI spec of the PAC file to load. + */ + nsresult ConfigureFromPAC(const nsCString& spec, bool forceReload); + + /** + * This method builds a list of nsProxyInfo objects from the given PAC- + * style string. + * + * @param pacString + * The PAC-style proxy string to parse. This may be empty. + * @param aResolveFlags + * The flags passed to Resolve or AsyncResolve that are stored in + * proxyInfo. + * @param result + * The resulting list of proxy info objects. + */ + void ProcessPACString(const nsCString& pacString, uint32_t aResolveFlags, + nsIProxyInfo** result); + + /** + * This method generates a string valued identifier for the given + * nsProxyInfo object. + * + * @param pi + * The nsProxyInfo object from which to generate the key. + * @param result + * Upon return, this parameter holds the generated key. + */ + void GetProxyKey(nsProxyInfo* pi, nsCString& key); + + /** + * @return Seconds since start of session. + */ + uint32_t SecondsSinceSessionStart(); + + /** + * This method removes the specified proxy from the disabled list. + * + * @param pi + * The nsProxyInfo object identifying the proxy to enable. + */ + void EnableProxy(nsProxyInfo* pi); + + /** + * This method adds the specified proxy to the disabled list. + * + * @param pi + * The nsProxyInfo object identifying the proxy to disable. + */ + void DisableProxy(nsProxyInfo* pi); + + /** + * This method tests to see if the given proxy is disabled. + * + * @param pi + * The nsProxyInfo object identifying the proxy to test. + * + * @return True if the specified proxy is disabled. + */ + bool IsProxyDisabled(nsProxyInfo* pi); + + /** + * This method queries the protocol handler for the given scheme to check + * for the protocol flags and default port. + * + * @param uri + * The URI to query. + * @param info + * Holds information about the protocol upon return. Pass address + * of structure when you call this method. This parameter must not + * be null. + */ + nsresult GetProtocolInfo(nsIURI* uri, nsProtocolInfo* info); + + /** + * This method is an internal version nsIProtocolProxyService::newProxyInfo + * that expects a string literal for the type. + * + * @param type + * The proxy type. + * @param host + * The proxy host name (UTF-8 ok). + * @param port + * The proxy port number. + * @param username + * The username for the proxy (ASCII). May be "", but not null. + * @param password + * The password for the proxy (ASCII). May be "", but not null. + * @param flags + * The proxy flags (nsIProxyInfo::flags). + * @param timeout + * The failover timeout for this proxy. + * @param next + * The next proxy to try if this one fails. + * @param aResolveFlags + * The flags passed to resolve (from nsIProtocolProxyService). + * @param result + * The resulting nsIProxyInfo object. + */ + nsresult NewProxyInfo_Internal(const char* type, const nsACString& host, + int32_t port, const nsACString& username, + const nsACString& password, + const nsACString& aProxyAuthorizationHeader, + const nsACString& aConnectionIsolationKey, + uint32_t flags, uint32_t timeout, + nsIProxyInfo* aFailoverProxy, + uint32_t aResolveFlags, nsIProxyInfo** result); + + /** + * This method is an internal version of Resolve that does not query PAC. + * It performs all of the built-in processing, and reports back to the + * caller with either the proxy info result or a flag to instruct the + * caller to use PAC instead. + * + * @param channel + * The channel to test. + * @param info + * Information about the URI's protocol. + * @param flags + * The flags passed to either the resolve or the asyncResolve method. + * @param usePAC + * If this flag is set upon return, then PAC should be queried to + * resolve the proxy info. + * @param result + * The resulting proxy info or null. + */ + nsresult Resolve_Internal(nsIChannel* channel, const nsProtocolInfo& info, + uint32_t flags, bool* usePAC, + nsIProxyInfo** result); + + /** + * Shallow copy of the current list of registered filters so that + * we can safely let them asynchronously process a single proxy + * resolution request. + */ + void CopyFilters(nsTArray<RefPtr<FilterLink>>& aCopy); + + /** + * This method applies the provided filter to the given proxy info + * list, and expects |callback| be called on (synchronously or + * asynchronously) to provide the updated proxyinfo list. + */ + bool ApplyFilter(FilterLink const* filterLink, nsIChannel* channel, + const nsProtocolInfo& info, nsCOMPtr<nsIProxyInfo> list, + nsIProxyProtocolFilterResult* callback); + + /** + * This method prunes out disabled and disallowed proxies from a given + * proxy info list. + * + * @param info + * Information about the URI's protocol. + * @param proxyInfo + * The proxy info list to be modified. This is an inout param. + */ + void PruneProxyInfo(const nsProtocolInfo& info, nsIProxyInfo** list); + + /** + * This method is a simple wrapper around PruneProxyInfo that takes the + * proxy info list inout param as a nsCOMPtr. + */ + void PruneProxyInfo(const nsProtocolInfo& info, + nsCOMPtr<nsIProxyInfo>& proxyInfo) { + nsIProxyInfo* pi = nullptr; + proxyInfo.swap(pi); + PruneProxyInfo(info, &pi); + proxyInfo.swap(pi); + } + + /** + * This method populates mHostFiltersArray from the given string. + * + * @param hostFilters + * A "no-proxy-for" exclusion list. + */ + void LoadHostFilters(const nsACString& aFilters); + + /** + * This method checks the given URI against mHostFiltersArray. + * + * @param uri + * The URI to test. + * @param defaultPort + * The default port for the given URI. + * + * @return True if the URI can use the specified proxy. + */ + bool CanUseProxy(nsIURI* uri, int32_t defaultPort); + + /** + * Disable Prefetch in the DNS service if a proxy is in use. + * + * @param aProxy + * The proxy information + */ + void MaybeDisableDNSPrefetch(nsIProxyInfo* aProxy); + + private: + nsresult SetupPACThread( + nsISerialEventTarget* mainThreadEventTarget = nullptr); + nsresult ResetPACThread(); + nsresult ReloadNetworkPAC(); + + nsresult AsyncConfigureFromPAC(bool aForceReload, bool aResetPACThread); + nsresult OnAsyncGetPACURI(bool aForceReload, bool aResetPACThread, + nsresult aResult, const nsACString& aUri); + + public: + // The Sun Forte compiler and others implement older versions of the + // C++ standard's rules on access and nested classes. These structs + // need to be public in order to deal with those compilers. + + struct HostInfoIP { + uint16_t family; + uint16_t mask_len; + PRIPv6Addr addr; // possibly IPv4-mapped address + }; + + struct HostInfoName { + char* host; + uint32_t host_len; + }; + + protected: + // simplified array of filters defined by this struct + struct HostInfo { + bool is_ipaddr{false}; + int32_t port{0}; + // other members intentionally uninitialized + union { + HostInfoIP ip; + HostInfoName name; + }; + + HostInfo() = default; + ~HostInfo() { + if (!is_ipaddr && name.host) { + free(name.host); + } + } + }; + + private: + // Private methods to insert and remove FilterLinks from the FilterLink chain. + nsresult InsertFilterLink(RefPtr<FilterLink>&& link); + nsresult RemoveFilterLink(nsISupports* givenObject); + + protected: + // Indicates if local hosts (plain hostnames, no dots) should use the proxy + bool mFilterLocalHosts{false}; + + // Holds an array of HostInfo objects + nsTArray<UniquePtr<HostInfo>> mHostFiltersArray; + + // Filters, always sorted by the position. + nsTArray<RefPtr<FilterLink>> mFilters; + + nsTArray<nsCOMPtr<nsIProxyConfigChangedCallback>> + mProxyConfigChangedCallbacks; + + uint32_t mProxyConfig{PROXYCONFIG_DIRECT}; + + nsCString mHTTPProxyHost; + int32_t mHTTPProxyPort{-1}; + + nsCString mHTTPSProxyHost; + int32_t mHTTPSProxyPort{-1}; + + // mSOCKSProxyTarget could be a host, a domain socket path, + // or a named-pipe name. + nsCString mSOCKSProxyTarget; + int32_t mSOCKSProxyPort{-1}; + int32_t mSOCKSProxyVersion{4}; + bool mSOCKSProxyRemoteDNS{false}; + bool mProxyOverTLS{true}; + bool mWPADOverDHCPEnabled{false}; + + RefPtr<nsPACMan> mPACMan; // non-null if we are using PAC + nsCOMPtr<nsISystemProxySettings> mSystemProxySettings; + + PRTime mSessionStart; + nsFailedProxyTable mFailedProxies; + // 30 minute default + int32_t mFailedProxyTimeout{30 * 60}; + + private: + nsresult AsyncResolveInternal(nsIChannel* channel, uint32_t flags, + nsIProtocolProxyCallback* callback, + nsICancelable** result, bool isSyncOK, + nsISerialEventTarget* mainThreadEventTarget); + bool mIsShutdown{false}; + nsCOMPtr<nsITimer> mReloadPACTimer; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsProtocolProxyService, + NS_PROTOCOL_PROXY_SERVICE_IMPL_CID) + +} // namespace net +} // namespace mozilla + +#endif // !nsProtocolProxyService_h__ diff --git a/netwerk/base/nsProxyInfo.cpp b/netwerk/base/nsProxyInfo.cpp new file mode 100644 index 0000000000..6859845739 --- /dev/null +++ b/netwerk/base/nsProxyInfo.cpp @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsProxyInfo.h" + +#include "mozilla/net/NeckoChannelParams.h" +#include "nsCOMPtr.h" + +namespace mozilla { +namespace net { + +// Yes, we support QI to nsProxyInfo +NS_IMPL_ISUPPORTS(nsProxyInfo, nsProxyInfo, nsIProxyInfo) + +// These pointers are declared in nsProtocolProxyService.cpp and +// comparison of mType by string pointer is valid within necko +extern const char kProxyType_HTTP[]; +extern const char kProxyType_HTTPS[]; +extern const char kProxyType_SOCKS[]; +extern const char kProxyType_SOCKS4[]; +extern const char kProxyType_SOCKS5[]; +extern const char kProxyType_DIRECT[]; +extern const char kProxyType_PROXY[]; + +nsProxyInfo::nsProxyInfo(const nsACString& aType, const nsACString& aHost, + int32_t aPort, const nsACString& aUsername, + const nsACString& aPassword, uint32_t aFlags, + uint32_t aTimeout, uint32_t aResolveFlags, + const nsACString& aProxyAuthorizationHeader, + const nsACString& aConnectionIsolationKey) + : mHost(aHost), + mUsername(aUsername), + mPassword(aPassword), + mProxyAuthorizationHeader(aProxyAuthorizationHeader), + mConnectionIsolationKey(aConnectionIsolationKey), + mPort(aPort), + mFlags(aFlags), + mResolveFlags(aResolveFlags), + mTimeout(aTimeout), + mNext(nullptr) { + if (aType.EqualsASCII(kProxyType_HTTP)) { + mType = kProxyType_HTTP; + } else if (aType.EqualsASCII(kProxyType_HTTPS)) { + mType = kProxyType_HTTPS; + } else if (aType.EqualsASCII(kProxyType_SOCKS)) { + mType = kProxyType_SOCKS; + } else if (aType.EqualsASCII(kProxyType_SOCKS4)) { + mType = kProxyType_SOCKS4; + } else if (aType.EqualsASCII(kProxyType_SOCKS5)) { + mType = kProxyType_SOCKS5; + } else if (aType.EqualsASCII(kProxyType_PROXY)) { + mType = kProxyType_PROXY; + } else { + mType = kProxyType_DIRECT; + } +} + +NS_IMETHODIMP +nsProxyInfo::GetHost(nsACString& result) { + result = mHost; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetPort(int32_t* result) { + *result = mPort; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetType(nsACString& result) { + result = mType; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetFlags(uint32_t* result) { + *result = mFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetResolveFlags(uint32_t* result) { + *result = mResolveFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetUsername(nsACString& result) { + result = mUsername; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetPassword(nsACString& result) { + result = mPassword; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetProxyAuthorizationHeader(nsACString& result) { + result = mProxyAuthorizationHeader; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetConnectionIsolationKey(nsACString& result) { + result = mConnectionIsolationKey; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetFailoverTimeout(uint32_t* result) { + *result = mTimeout; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetFailoverProxy(nsIProxyInfo** result) { + NS_IF_ADDREF(*result = mNext); + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::SetFailoverProxy(nsIProxyInfo* proxy) { + nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(proxy); + NS_ENSURE_ARG(pi); + + pi.swap(mNext); + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::GetSourceId(nsACString& result) { + result = mSourceId; + return NS_OK; +} + +NS_IMETHODIMP +nsProxyInfo::SetSourceId(const nsACString& sourceId) { + mSourceId = sourceId; + return NS_OK; +} + +bool nsProxyInfo::IsDirect() { + if (!mType) return true; + return mType == kProxyType_DIRECT; +} + +bool nsProxyInfo::IsHTTP() { return mType == kProxyType_HTTP; } + +bool nsProxyInfo::IsHTTPS() { return mType == kProxyType_HTTPS; } + +bool nsProxyInfo::IsSOCKS() { + return mType == kProxyType_SOCKS || mType == kProxyType_SOCKS4 || + mType == kProxyType_SOCKS5; +} + +/* static */ +void nsProxyInfo::SerializeProxyInfo(nsProxyInfo* aProxyInfo, + nsTArray<ProxyInfoCloneArgs>& aResult) { + for (nsProxyInfo* iter = aProxyInfo; iter; iter = iter->mNext) { + ProxyInfoCloneArgs* arg = aResult.AppendElement(); + arg->type() = nsCString(iter->Type()); + arg->host() = iter->Host(); + arg->port() = iter->Port(); + arg->username() = iter->Username(); + arg->password() = iter->Password(); + arg->proxyAuthorizationHeader() = iter->ProxyAuthorizationHeader(); + arg->connectionIsolationKey() = iter->ConnectionIsolationKey(); + arg->flags() = iter->Flags(); + arg->timeout() = iter->Timeout(); + arg->resolveFlags() = iter->ResolveFlags(); + } +} + +/* static */ +nsProxyInfo* nsProxyInfo::DeserializeProxyInfo( + const nsTArray<ProxyInfoCloneArgs>& aArgs) { + nsProxyInfo *pi = nullptr, *first = nullptr, *last = nullptr; + for (const ProxyInfoCloneArgs& info : aArgs) { + pi = new nsProxyInfo(info.type(), info.host(), info.port(), info.username(), + info.password(), info.flags(), info.timeout(), + info.resolveFlags(), info.proxyAuthorizationHeader(), + info.connectionIsolationKey()); + if (last) { + last->mNext = pi; + // |mNext| will be released in |last|'s destructor. + NS_IF_ADDREF(last->mNext); + } else { + first = pi; + } + last = pi; + } + + return first; +} + +already_AddRefed<nsProxyInfo> nsProxyInfo::CloneProxyInfoWithNewResolveFlags( + uint32_t aResolveFlags) { + nsTArray<ProxyInfoCloneArgs> args; + SerializeProxyInfo(this, args); + + for (auto& arg : args) { + arg.resolveFlags() = aResolveFlags; + } + + RefPtr<nsProxyInfo> result = DeserializeProxyInfo(args); + return result.forget(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsProxyInfo.h b/netwerk/base/nsProxyInfo.h new file mode 100644 index 0000000000..ca8602edc0 --- /dev/null +++ b/netwerk/base/nsProxyInfo.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsProxyInfo_h__ +#define nsProxyInfo_h__ + +#include "nsIProxyInfo.h" +#include "nsString.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" + +// Use to support QI nsIProxyInfo to nsProxyInfo +#define NS_PROXYINFO_IID \ + { /* ed42f751-825e-4cc2-abeb-3670711a8b85 */ \ + 0xed42f751, 0x825e, 0x4cc2, { \ + 0xab, 0xeb, 0x36, 0x70, 0x71, 0x1a, 0x8b, 0x85 \ + } \ + } + +namespace mozilla { +namespace net { + +class ProxyInfoCloneArgs; + +// This class is exposed to other classes inside Necko for fast access +// to the nsIProxyInfo attributes. +class nsProxyInfo final : public nsIProxyInfo { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROXYINFO_IID) + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROXYINFO + + // Cheap accessors for use within Necko + const nsCString& Host() const { return mHost; } + int32_t Port() const { return mPort; } + const char* Type() const { return mType; } + uint32_t Flags() const { return mFlags; } + const nsCString& Username() const { return mUsername; } + const nsCString& Password() const { return mPassword; } + uint32_t Timeout() { return mTimeout; } + uint32_t ResolveFlags() { return mResolveFlags; } + const nsCString& ProxyAuthorizationHeader() const { + return mProxyAuthorizationHeader; + } + const nsCString& ConnectionIsolationKey() const { + return mConnectionIsolationKey; + } + + bool IsDirect(); + bool IsHTTP(); + bool IsHTTPS(); + bool IsSOCKS(); + + static void SerializeProxyInfo(nsProxyInfo* aProxyInfo, + nsTArray<ProxyInfoCloneArgs>& aResult); + static nsProxyInfo* DeserializeProxyInfo( + const nsTArray<ProxyInfoCloneArgs>& aArgs); + + already_AddRefed<nsProxyInfo> CloneProxyInfoWithNewResolveFlags( + uint32_t aResolveFlags); + + private: + friend class nsProtocolProxyService; + + explicit nsProxyInfo(const char* type = nullptr) : mType(type) {} + + nsProxyInfo(const nsACString& aType, const nsACString& aHost, int32_t aPort, + const nsACString& aUsername, const nsACString& aPassword, + uint32_t aFlags, uint32_t aTimeout, uint32_t aResolveFlags, + const nsACString& aProxyAuthorizationHeader, + const nsACString& aConnectionIsolationKey); + + ~nsProxyInfo() { NS_IF_RELEASE(mNext); } + + const char* mType; // pointer to statically allocated value + nsCString mHost; + nsCString mUsername; + nsCString mPassword; + nsCString mProxyAuthorizationHeader; + nsCString mConnectionIsolationKey; + nsCString mSourceId; + int32_t mPort{-1}; + uint32_t mFlags{0}; + // We need to read on multiple threads, but don't need to sync on anything + // else + Atomic<uint32_t, Relaxed> mResolveFlags{0}; + uint32_t mTimeout{UINT32_MAX}; + nsProxyInfo* mNext{nullptr}; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsProxyInfo, NS_PROXYINFO_IID) + +} // namespace net +} // namespace mozilla + +#endif // nsProxyInfo_h__ diff --git a/netwerk/base/nsReadLine.h b/netwerk/base/nsReadLine.h new file mode 100644 index 0000000000..f1e4693f92 --- /dev/null +++ b/netwerk/base/nsReadLine.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsReadLine_h__ +#define nsReadLine_h__ + +#include "nsIInputStream.h" +#include "mozilla/Likely.h" + +/** + * @file + * Functions to read complete lines from an input stream. + * + * To properly use the helper function in here (NS_ReadLine) the caller should + * create a nsLineBuffer<T> with new, and pass it to NS_ReadLine every time it + * wants a line out. + * + * When done, the object should be deleted. + */ + +/** + * @internal + * Buffer size. This many bytes will be buffered. If a line is longer than this, + * the partial line will be appended to the out parameter of NS_ReadLine and the + * buffer will be emptied. + * Note: if you change this constant, please update the regression test in + * netwerk/test/unit/test_readline.js accordingly (bug 397850). + */ +#define kLineBufferSize 4096 + +/** + * @internal + * Line buffer structure, buffers data from an input stream. + * The buffer is empty when |start| == |end|. + * Invariant: |start| <= |end| + */ +template <typename CharT> +class nsLineBuffer { + public: + nsLineBuffer() : start(buf), end(buf) {} + + CharT buf[kLineBufferSize + 1]; + CharT* start; + CharT* end; +}; + +/** + * Read a line from an input stream. Lines are separated by '\r' (0x0D) or '\n' + * (0x0A), or "\r\n" or "\n\r". + * + * @param aStream + * The stream to read from + * @param aBuffer + * The line buffer to use. A single line buffer must not be used with + * different input streams. + * @param aLine [out] + * The string where the line will be stored. + * @param more [out] + * Whether more data is available in the buffer. If true, NS_ReadLine may + * be called again to read further lines. Otherwise, further calls to + * NS_ReadLine will return an error. + * + * @retval NS_OK + * Read successful + * @retval error + * Input stream returned an error upon read. See + * nsIInputStream::read. + */ +template <typename CharT, class StreamType, class StringType> +nsresult NS_ReadLine(StreamType* aStream, nsLineBuffer<CharT>* aBuffer, + StringType& aLine, bool* more) { + CharT eolchar = 0; // the first eol char or 1 after \r\n or \n\r is found + + aLine.Truncate(); + + while (true) { // will be returning out of this loop on eol or eof + if (aBuffer->start == aBuffer->end) { // buffer is empty. Read into it. + uint32_t bytesRead; + nsresult rv = aStream->Read(aBuffer->buf, kLineBufferSize, &bytesRead); + if (NS_FAILED(rv) || MOZ_UNLIKELY(bytesRead == 0)) { + *more = false; + return rv; + } + aBuffer->start = aBuffer->buf; + aBuffer->end = aBuffer->buf + bytesRead; + *(aBuffer->end) = '\0'; + } + + /* + * Walk the buffer looking for an end-of-line. + * There are 3 cases to consider: + * 1. the eol char is the last char in the buffer + * 2. the eol char + one more char at the end of the buffer + * 3. the eol char + two or more chars at the end of the buffer + * we need at least one char after the first eol char to determine if + * it's a \r\n or \n\r sequence (and skip over it), and we need one + * more char after the end-of-line to set |more| correctly. + */ + CharT* current = aBuffer->start; + if (MOZ_LIKELY(eolchar == 0)) { + for (; current < aBuffer->end; ++current) { + if (*current == '\n' || *current == '\r') { + eolchar = *current; + *current++ = '\0'; + aLine.Append(aBuffer->start); + break; + } + } + } + if (MOZ_LIKELY(eolchar != 0)) { + for (; current < aBuffer->end; ++current) { + if ((eolchar == '\r' && *current == '\n') || + (eolchar == '\n' && *current == '\r')) { + eolchar = 1; + continue; + } + aBuffer->start = current; + *more = true; + return NS_OK; + } + } + + if (eolchar == 0) aLine.Append(aBuffer->start); + aBuffer->start = aBuffer->end; // mark the buffer empty + } +} + +#endif // nsReadLine_h__ diff --git a/netwerk/base/nsRedirectHistoryEntry.cpp b/netwerk/base/nsRedirectHistoryEntry.cpp new file mode 100644 index 0000000000..da1a18cb95 --- /dev/null +++ b/netwerk/base/nsRedirectHistoryEntry.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsRedirectHistoryEntry.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" +#include "nsIPrincipal.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(nsRedirectHistoryEntry, nsIRedirectHistoryEntry) + +nsRedirectHistoryEntry::nsRedirectHistoryEntry(nsIPrincipal* aPrincipal, + nsIURI* aReferrer, + const nsACString& aRemoteAddress) + : mPrincipal(aPrincipal), + mReferrer(aReferrer), + mRemoteAddress(aRemoteAddress) {} + +NS_IMETHODIMP +nsRedirectHistoryEntry::GetRemoteAddress(nsACString& result) { + result = mRemoteAddress; + return NS_OK; +} + +NS_IMETHODIMP +nsRedirectHistoryEntry::GetReferrerURI(nsIURI** referrer) { + *referrer = do_AddRef(mReferrer).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsRedirectHistoryEntry::GetPrincipal(nsIPrincipal** principal) { + *principal = do_AddRef(mPrincipal).take(); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsRedirectHistoryEntry.h b/netwerk/base/nsRedirectHistoryEntry.h new file mode 100644 index 0000000000..bc916062a8 --- /dev/null +++ b/netwerk/base/nsRedirectHistoryEntry.h @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsRedirectHistoryEntry_h__ +#define nsRedirectHistoryEntry_h__ + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIRedirectHistoryEntry.h" + +class nsIURI; +class nsIPrincipal; + +namespace mozilla { +namespace net { + +class nsRedirectHistoryEntry final : public nsIRedirectHistoryEntry { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREDIRECTHISTORYENTRY + + nsRedirectHistoryEntry(nsIPrincipal* aPrincipal, nsIURI* aReferrer, + const nsACString& aRemoteAddress); + + private: + ~nsRedirectHistoryEntry() = default; + + nsCOMPtr<nsIPrincipal> mPrincipal; + nsCOMPtr<nsIURI> mReferrer; + nsCString mRemoteAddress; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsRedirectHistoryEntry_h__ diff --git a/netwerk/base/nsRequestObserverProxy.cpp b/netwerk/base/nsRequestObserverProxy.cpp new file mode 100644 index 0000000000..fda235488a --- /dev/null +++ b/netwerk/base/nsRequestObserverProxy.cpp @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" + +#include "nscore.h" +#include "nsRequestObserverProxy.h" +#include "nsIRequest.h" +#include "mozilla/Logging.h" +#include "mozilla/IntegerPrintfMacros.h" + +namespace mozilla { +namespace net { + +static LazyLogModule gRequestObserverProxyLog("nsRequestObserverProxy"); + +#undef LOG +#define LOG(args) MOZ_LOG(gRequestObserverProxyLog, LogLevel::Debug, args) + +//----------------------------------------------------------------------------- +// nsARequestObserverEvent internal class... +//----------------------------------------------------------------------------- + +nsARequestObserverEvent::nsARequestObserverEvent(nsIRequest* request) + : Runnable("net::nsARequestObserverEvent"), mRequest(request) { + MOZ_ASSERT(mRequest, "null pointer"); +} + +//----------------------------------------------------------------------------- +// nsOnStartRequestEvent internal class... +//----------------------------------------------------------------------------- + +class nsOnStartRequestEvent : public nsARequestObserverEvent { + RefPtr<nsRequestObserverProxy> mProxy; + + public: + nsOnStartRequestEvent(nsRequestObserverProxy* proxy, nsIRequest* request) + : nsARequestObserverEvent(request), mProxy(proxy) { + MOZ_ASSERT(mProxy, "null pointer"); + } + + NS_IMETHOD Run() override { + LOG(("nsOnStartRequestEvent::HandleEvent [req=%p]\n", mRequest.get())); + + if (!mProxy->mObserver) { + MOZ_ASSERT_UNREACHABLE( + "already handled onStopRequest event " + "(observer is null)"); + return NS_OK; + } + + LOG(("handle startevent=%p\n", this)); + nsresult rv = mProxy->mObserver->OnStartRequest(mRequest); + if (NS_FAILED(rv)) { + LOG(("OnStartRequest failed [rv=%" PRIx32 "] canceling request!\n", + static_cast<uint32_t>(rv))); + rv = mRequest->Cancel(rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed for request!"); + } + + return NS_OK; + } + + private: + virtual ~nsOnStartRequestEvent() = default; +}; + +//----------------------------------------------------------------------------- +// nsOnStopRequestEvent internal class... +//----------------------------------------------------------------------------- + +class nsOnStopRequestEvent : public nsARequestObserverEvent { + RefPtr<nsRequestObserverProxy> mProxy; + + public: + nsOnStopRequestEvent(nsRequestObserverProxy* proxy, nsIRequest* request) + : nsARequestObserverEvent(request), mProxy(proxy) { + MOZ_ASSERT(mProxy, "null pointer"); + } + + NS_IMETHOD Run() override { + LOG(("nsOnStopRequestEvent::HandleEvent [req=%p]\n", mRequest.get())); + + nsMainThreadPtrHandle<nsIRequestObserver> observer = mProxy->mObserver; + if (!observer) { + MOZ_ASSERT_UNREACHABLE( + "already handled onStopRequest event " + "(observer is null)"); + return NS_OK; + } + // Do not allow any more events to be handled after OnStopRequest + mProxy->mObserver = nullptr; + + nsresult status = NS_OK; + DebugOnly<nsresult> rv = mRequest->GetStatus(&status); + NS_ASSERTION(NS_SUCCEEDED(rv), "GetStatus failed for request!"); + + LOG(("handle stopevent=%p\n", this)); + (void)observer->OnStopRequest(mRequest, status); + + return NS_OK; + } + + private: + virtual ~nsOnStopRequestEvent() = default; +}; + +//----------------------------------------------------------------------------- +// nsRequestObserverProxy::nsISupports implementation... +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsRequestObserverProxy, nsIRequestObserver, + nsIRequestObserverProxy) + +//----------------------------------------------------------------------------- +// nsRequestObserverProxy::nsIRequestObserver implementation... +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsRequestObserverProxy::OnStartRequest(nsIRequest* request) { + LOG(("nsRequestObserverProxy::OnStartRequest [this=%p req=%p]\n", this, + request)); + + RefPtr<nsOnStartRequestEvent> ev = new nsOnStartRequestEvent(this, request); + + LOG(("post startevent=%p\n", ev.get())); + return FireEvent(ev); +} + +NS_IMETHODIMP +nsRequestObserverProxy::OnStopRequest(nsIRequest* request, nsresult status) { + LOG(("nsRequestObserverProxy: OnStopRequest [this=%p req=%p status=%" PRIx32 + "]\n", + this, request, static_cast<uint32_t>(status))); + + // The status argument is ignored because, by the time the OnStopRequestEvent + // is actually processed, the status of the request may have changed :-( + // To make sure that an accurate status code is always used, GetStatus() is + // called when the OnStopRequestEvent is actually processed (see above). + + RefPtr<nsOnStopRequestEvent> ev = new nsOnStopRequestEvent(this, request); + + LOG(("post stopevent=%p\n", ev.get())); + return FireEvent(ev); +} + +//----------------------------------------------------------------------------- +// nsRequestObserverProxy::nsIRequestObserverProxy implementation... +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsRequestObserverProxy::Init(nsIRequestObserver* observer, + nsISupports* context) { + NS_ENSURE_ARG_POINTER(observer); + mObserver = new nsMainThreadPtrHolder<nsIRequestObserver>( + "nsRequestObserverProxy::mObserver", observer); + mContext = new nsMainThreadPtrHolder<nsISupports>( + "nsRequestObserverProxy::mContext", context); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsRequestObserverProxy implementation... +//----------------------------------------------------------------------------- + +nsresult nsRequestObserverProxy::FireEvent(nsARequestObserverEvent* event) { + nsCOMPtr<nsIEventTarget> mainThread(GetMainThreadSerialEventTarget()); + return mainThread->Dispatch(event, NS_DISPATCH_NORMAL); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsRequestObserverProxy.h b/netwerk/base/nsRequestObserverProxy.h new file mode 100644 index 0000000000..e1ccec0c4c --- /dev/null +++ b/netwerk/base/nsRequestObserverProxy.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsRequestObserverProxy_h__ +#define nsRequestObserverProxy_h__ + +#include "nsIRequestObserver.h" +#include "nsIRequestObserverProxy.h" +#include "nsIRequest.h" +#include "nsThreadUtils.h" +#include "nsCOMPtr.h" +#include "nsProxyRelease.h" + +namespace mozilla { +namespace net { + +class nsARequestObserverEvent; + +class nsRequestObserverProxy final : public nsIRequestObserverProxy { + ~nsRequestObserverProxy() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIREQUESTOBSERVERPROXY + + nsRequestObserverProxy() = default; + + nsIRequestObserver* Observer() { return mObserver; } + + nsresult FireEvent(nsARequestObserverEvent*); + + protected: + nsMainThreadPtrHandle<nsIRequestObserver> mObserver; + nsMainThreadPtrHandle<nsISupports> mContext; + + friend class nsOnStartRequestEvent; + friend class nsOnStopRequestEvent; +}; + +class nsARequestObserverEvent : public Runnable { + public: + explicit nsARequestObserverEvent(nsIRequest*); + + protected: + virtual ~nsARequestObserverEvent() = default; + + nsCOMPtr<nsIRequest> mRequest; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsRequestObserverProxy_h__ diff --git a/netwerk/base/nsSerializationHelper.cpp b/netwerk/base/nsSerializationHelper.cpp new file mode 100644 index 0000000000..6e2efb7c57 --- /dev/null +++ b/netwerk/base/nsSerializationHelper.cpp @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSerializationHelper.h" + +#include "mozilla/Base64.h" +#include "nsISerializable.h" +#include "nsIObjectOutputStream.h" +#include "nsIObjectInputStream.h" +#include "nsString.h" +#include "nsBase64Encoder.h" +#include "nsComponentManagerUtils.h" +#include "nsStringStream.h" + +using namespace mozilla; + +nsresult NS_SerializeToString(nsISerializable* obj, nsACString& str) { + RefPtr<nsBase64Encoder> stream(new nsBase64Encoder()); + if (!stream) return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsIObjectOutputStream> objstream = NS_NewObjectOutputStream(stream); + nsresult rv = + objstream->WriteCompoundObject(obj, NS_GET_IID(nsISupports), true); + NS_ENSURE_SUCCESS(rv, rv); + return stream->Finish(str); +} + +nsresult NS_DeserializeObject(const nsACString& str, nsISupports** obj) { + nsCString decodedData; + nsresult rv = Base64Decode(str, decodedData); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewCStringInputStream(getter_AddRefs(stream), std::move(decodedData)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIObjectInputStream> objstream = NS_NewObjectInputStream(stream); + return objstream->ReadObject(true, obj); +} + +NS_IMPL_ISUPPORTS(nsSerializationHelper, nsISerializationHelper) + +NS_IMETHODIMP +nsSerializationHelper::SerializeToString(nsISerializable* serializable, + nsACString& _retval) { + return NS_SerializeToString(serializable, _retval); +} + +NS_IMETHODIMP +nsSerializationHelper::DeserializeObject(const nsACString& input, + nsISupports** _retval) { + return NS_DeserializeObject(input, _retval); +} diff --git a/netwerk/base/nsSerializationHelper.h b/netwerk/base/nsSerializationHelper.h new file mode 100644 index 0000000000..4e21a4c2df --- /dev/null +++ b/netwerk/base/nsSerializationHelper.h @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** @file + * Helper functions for (de)serializing objects to/from ASCII strings. + */ + +#ifndef NSSERIALIZATIONHELPER_H_ +#define NSSERIALIZATIONHELPER_H_ + +#include "nsStringFwd.h" +#include "nsISerializationHelper.h" +#include "mozilla/Attributes.h" + +class nsISerializable; + +/** + * Serialize an object to an ASCII string. + */ +nsresult NS_SerializeToString(nsISerializable* obj, nsACString& str); + +/** + * Deserialize an object. + */ +nsresult NS_DeserializeObject(const nsACString& str, nsISupports** obj); + +class nsSerializationHelper final : public nsISerializationHelper { + ~nsSerializationHelper() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSISERIALIZATIONHELPER +}; + +#endif diff --git a/netwerk/base/nsServerSocket.cpp b/netwerk/base/nsServerSocket.cpp new file mode 100644 index 0000000000..cf8fc7b619 --- /dev/null +++ b/netwerk/base/nsServerSocket.cpp @@ -0,0 +1,582 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSocketTransport2.h" +#include "nsServerSocket.h" +#include "nsProxyRelease.h" +#include "nsError.h" +#include "nsNetCID.h" +#include "prnetdb.h" +#include "prio.h" +#include "nsThreadUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/net/DNS.h" +#include "mozilla/Unused.h" +#include "nsServiceManagerUtils.h" +#include "nsIFile.h" +#if defined(XP_WIN) +# include "private/pprio.h" +# include <winsock2.h> +# include <mstcpip.h> + +# ifndef IPV6_V6ONLY +# define IPV6_V6ONLY 27 +# endif + +#endif + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- + +using nsServerSocketFunc = void (nsServerSocket::*)(); + +static nsresult PostEvent(nsServerSocket* s, nsServerSocketFunc func) { + nsCOMPtr<nsIRunnable> ev = NewRunnableMethod("net::PostEvent", s, func); + if (!gSocketTransportService) return NS_ERROR_FAILURE; + + return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL); +} + +//----------------------------------------------------------------------------- +// nsServerSocket +//----------------------------------------------------------------------------- + +nsServerSocket::nsServerSocket() { + // we want to be able to access the STS directly, and it may not have been + // constructed yet. the STS constructor sets gSocketTransportService. + if (!gSocketTransportService) { + // This call can fail if we're offline, for example. + nsCOMPtr<nsISocketTransportService> sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + } + // make sure the STS sticks around as long as we do + NS_IF_ADDREF(gSocketTransportService); +} + +nsServerSocket::~nsServerSocket() { + Close(); // just in case :) + + // release our reference to the STS + nsSocketTransportService* serv = gSocketTransportService; + NS_IF_RELEASE(serv); +} + +void nsServerSocket::OnMsgClose() { + SOCKET_LOG(("nsServerSocket::OnMsgClose [this=%p]\n", this)); + + if (NS_FAILED(mCondition)) return; + + // tear down socket. this signals the STS to detach our socket handler. + mCondition = NS_BINDING_ABORTED; + + // if we are attached, then we'll close the socket in our OnSocketDetached. + // otherwise, call OnSocketDetached from here. + if (!mAttached) OnSocketDetached(mFD); +} + +void nsServerSocket::OnMsgAttach() { + SOCKET_LOG(("nsServerSocket::OnMsgAttach [this=%p]\n", this)); + + if (NS_FAILED(mCondition)) return; + + mCondition = TryAttach(); + + // if we hit an error while trying to attach then bail... + if (NS_FAILED(mCondition)) { + NS_ASSERTION(!mAttached, "should not be attached already"); + OnSocketDetached(mFD); + } +} + +nsresult nsServerSocket::TryAttach() { + nsresult rv; + + if (!gSocketTransportService) return NS_ERROR_FAILURE; + + // + // find out if it is going to be ok to attach another socket to the STS. + // if not then we have to wait for the STS to tell us that it is ok. + // the notification is asynchronous, which means that when we could be + // in a race to call AttachSocket once notified. for this reason, when + // we get notified, we just re-enter this function. as a result, we are + // sure to ask again before calling AttachSocket. in this way we deal + // with the race condition. though it isn't the most elegant solution, + // it is far simpler than trying to build a system that would guarantee + // FIFO ordering (which wouldn't even be that valuable IMO). see bug + // 194402 for more info. + // + if (!gSocketTransportService->CanAttachSocket()) { + nsCOMPtr<nsIRunnable> event = NewRunnableMethod( + "net::nsServerSocket::OnMsgAttach", this, &nsServerSocket::OnMsgAttach); + if (!event) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event); + if (NS_FAILED(rv)) return rv; + } + + // + // ok, we can now attach our socket to the STS for polling + // + rv = gSocketTransportService->AttachSocket(mFD, this); + if (NS_FAILED(rv)) return rv; + + mAttached = true; + + // + // now, configure our poll flags for listening... + // + mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT); + return NS_OK; +} + +void nsServerSocket::CreateClientTransport(PRFileDesc* aClientFD, + const NetAddr& aClientAddr) { + RefPtr<nsSocketTransport> trans = new nsSocketTransport; + if (NS_WARN_IF(!trans)) { + mCondition = NS_ERROR_OUT_OF_MEMORY; + return; + } + + nsresult rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr); + if (NS_WARN_IF(NS_FAILED(rv))) { + mCondition = rv; + return; + } + + mListener->OnSocketAccepted(this, trans); +} + +//----------------------------------------------------------------------------- +// nsServerSocket::nsASocketHandler +//----------------------------------------------------------------------------- + +void nsServerSocket::OnSocketReady(PRFileDesc* fd, int16_t outFlags) { + NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops"); + NS_ASSERTION(mFD == fd, "wrong file descriptor"); + NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached"); + + if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL)) { + NS_WARNING("error polling on listening socket"); + mCondition = NS_ERROR_UNEXPECTED; + return; + } + + PRFileDesc* clientFD; + PRNetAddr prClientAddr; + + // NSPR doesn't tell us the peer address's length (as provided by the + // 'accept' system call), so we can't distinguish between named, + // unnamed, and abstract peer addresses. Clear prClientAddr first, so + // that the path will at least be reliably empty for unnamed and + // abstract addresses, and not garbage when the peer is unnamed. + memset(&prClientAddr, 0, sizeof(prClientAddr)); + + clientFD = PR_Accept(mFD, &prClientAddr, PR_INTERVAL_NO_WAIT); + if (!clientFD) { + NS_WARNING("PR_Accept failed"); + mCondition = NS_ERROR_UNEXPECTED; + return; + } + PR_SetFDInheritable(clientFD, false); + + NetAddr clientAddr(&prClientAddr); + // Accept succeeded, create socket transport and notify consumer + CreateClientTransport(clientFD, clientAddr); +} + +void nsServerSocket::OnSocketDetached(PRFileDesc* fd) { + // force a failure condition if none set; maybe the STS is shutting down :-/ + if (NS_SUCCEEDED(mCondition)) mCondition = NS_ERROR_ABORT; + + if (mFD) { + NS_ASSERTION(mFD == fd, "wrong file descriptor"); + PR_Close(mFD); + mFD = nullptr; + } + + if (mListener) { + mListener->OnStopListening(this, mCondition); + + // need to atomically clear mListener. see our Close() method. + RefPtr<nsIServerSocketListener> listener = nullptr; + { + MutexAutoLock lock(mLock); + listener = ToRefPtr(std::move(mListener)); + } + + // XXX we need to proxy the release to the listener's target thread to work + // around bug 337492. + if (listener) { + NS_ProxyRelease("nsServerSocket::mListener", mListenerTarget, + listener.forget()); + } + } +} + +void nsServerSocket::IsLocal(bool* aIsLocal) { +#if defined(XP_UNIX) + // Unix-domain sockets are always local. + if (mAddr.raw.family == PR_AF_LOCAL) { + *aIsLocal = true; + return; + } +#endif + + // If bound to loopback, this server socket only accepts local connections. + *aIsLocal = PR_IsNetAddrType(&mAddr, PR_IpAddrLoopback); +} + +void nsServerSocket::KeepWhenOffline(bool* aKeepWhenOffline) { + *aKeepWhenOffline = mKeepWhenOffline; +} + +//----------------------------------------------------------------------------- +// nsServerSocket::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsServerSocket, nsIServerSocket) + +//----------------------------------------------------------------------------- +// nsServerSocket::nsIServerSocket +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsServerSocket::Init(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog) { + return InitSpecialConnection(aPort, aLoopbackOnly ? LoopbackOnly : 0, + aBackLog); +} + +NS_IMETHODIMP +nsServerSocket::InitIPv6(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog) { + PRNetAddrValue val; + PRNetAddr addr; + + if (aPort < 0) { + aPort = 0; + } + if (aLoopbackOnly) { + val = PR_IpAddrLoopback; + } else { + val = PR_IpAddrAny; + } + PR_SetNetAddr(val, PR_AF_INET6, aPort, &addr); + + mKeepWhenOffline = false; + return InitWithAddress(&addr, aBackLog); +} + +NS_IMETHODIMP +nsServerSocket::InitDualStack(int32_t aPort, int32_t aBackLog) { + if (aPort < 0) { + aPort = 0; + } + PRNetAddr addr; + PR_SetNetAddr(PR_IpAddrAny, PR_AF_INET6, aPort, &addr); + return InitWithAddressInternal(&addr, aBackLog, true); +} + +NS_IMETHODIMP +nsServerSocket::InitWithFilename(nsIFile* aPath, uint32_t aPermissions, + int32_t aBacklog) { +#if defined(XP_UNIX) + nsresult rv; + + nsAutoCString path; + rv = aPath->GetNativePath(path); + if (NS_FAILED(rv)) return rv; + + // Create a Unix domain PRNetAddr referring to the given path. + PRNetAddr addr; + if (path.Length() > sizeof(addr.local.path) - 1) { + return NS_ERROR_FILE_NAME_TOO_LONG; + } + addr.local.family = PR_AF_LOCAL; + memcpy(addr.local.path, path.get(), path.Length()); + addr.local.path[path.Length()] = '\0'; + + rv = InitWithAddress(&addr, aBacklog); + if (NS_FAILED(rv)) return rv; + + return aPath->SetPermissions(aPermissions); +#else + return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; +#endif +} + +NS_IMETHODIMP +nsServerSocket::InitWithAbstractAddress(const nsACString& aName, + int32_t aBacklog) { + // Abstract socket address is supported on Linux and Android only. + // If not Linux, we should return error. +#if defined(XP_LINUX) + // Create an abstract socket address PRNetAddr referring to the name + PRNetAddr addr; + if (aName.Length() > sizeof(addr.local.path) - 2) { + return NS_ERROR_FILE_NAME_TOO_LONG; + } + addr.local.family = PR_AF_LOCAL; + addr.local.path[0] = 0; + memcpy(addr.local.path + 1, aName.BeginReading(), aName.Length()); + addr.local.path[aName.Length() + 1] = 0; + + return InitWithAddress(&addr, aBacklog); +#else + return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; +#endif +} + +NS_IMETHODIMP +nsServerSocket::InitSpecialConnection(int32_t aPort, nsServerSocketFlag aFlags, + int32_t aBackLog) { + PRNetAddrValue val; + PRNetAddr addr; + + if (aPort < 0) aPort = 0; + if (aFlags & nsIServerSocket::LoopbackOnly) { + val = PR_IpAddrLoopback; + } else { + val = PR_IpAddrAny; + } + PR_SetNetAddr(val, PR_AF_INET, aPort, &addr); + + mKeepWhenOffline = ((aFlags & nsIServerSocket::KeepWhenOffline) != 0); + return InitWithAddress(&addr, aBackLog); +} + +NS_IMETHODIMP +nsServerSocket::InitWithAddress(const PRNetAddr* aAddr, int32_t aBackLog) { + return InitWithAddressInternal(aAddr, aBackLog); +} + +nsresult nsServerSocket::InitWithAddressInternal(const PRNetAddr* aAddr, + int32_t aBackLog, + bool aDualStack) { + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + nsresult rv; + + // + // configure listening socket... + // + + mFD = PR_OpenTCPSocket(aAddr->raw.family); + if (!mFD) { + NS_WARNING("unable to create server socket"); + return ErrorAccordingToNSPR(PR_GetError()); + } + +#if defined(XP_WIN) + // https://docs.microsoft.com/en-us/windows/win32/winsock/dual-stack-sockets + // To create a Dual-Stack Socket, we have to disable IPV6_V6ONLY. + if (aDualStack) { + PROsfd osfd = PR_FileDesc2NativeHandle(mFD); + if (osfd != -1) { + int disable = 0; + setsockopt(osfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&disable, + sizeof(disable)); + } + } +#else + mozilla::Unused << aDualStack; +#endif + + PR_SetFDInheritable(mFD, false); + + PRSocketOptionData opt; + + opt.option = PR_SockOpt_Reuseaddr; + opt.value.reuse_addr = true; + PR_SetSocketOption(mFD, &opt); + + opt.option = PR_SockOpt_Nonblocking; + opt.value.non_blocking = true; + PR_SetSocketOption(mFD, &opt); + + if (PR_Bind(mFD, aAddr) != PR_SUCCESS) { + NS_WARNING("failed to bind socket"); + goto fail; + } + + if (aBackLog < 0) aBackLog = 5; // seems like a reasonable default + + if (PR_Listen(mFD, aBackLog) != PR_SUCCESS) { + NS_WARNING("cannot listen on socket"); + goto fail; + } + + // get the resulting socket address, which may be different than what + // we passed to bind. + if (PR_GetSockName(mFD, &mAddr) != PR_SUCCESS) { + NS_WARNING("cannot get socket name"); + goto fail; + } + + // Set any additional socket defaults needed by child classes + rv = SetSocketDefaults(); + if (NS_WARN_IF(NS_FAILED(rv))) { + goto fail; + } + + // wait until AsyncListen is called before polling the socket for + // client connections. + return NS_OK; + +fail: + rv = ErrorAccordingToNSPR(PR_GetError()); + Close(); + return rv; +} + +NS_IMETHODIMP +nsServerSocket::Close() { + { + MutexAutoLock lock(mLock); + // we want to proxy the close operation to the socket thread if a listener + // has been set. otherwise, we should just close the socket here... + if (!mListener) { + if (mFD) { + PR_Close(mFD); + mFD = nullptr; + } + return NS_OK; + } + } + return PostEvent(this, &nsServerSocket::OnMsgClose); +} + +namespace { + +class ServerSocketListenerProxy final : public nsIServerSocketListener { + ~ServerSocketListenerProxy() = default; + + public: + explicit ServerSocketListenerProxy(nsIServerSocketListener* aListener) + : mListener(new nsMainThreadPtrHolder<nsIServerSocketListener>( + "ServerSocketListenerProxy::mListener", aListener)), + mTarget(GetCurrentSerialEventTarget()) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISERVERSOCKETLISTENER + + class OnSocketAcceptedRunnable : public Runnable { + public: + OnSocketAcceptedRunnable( + const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener, + nsIServerSocket* aServ, nsISocketTransport* aTransport) + : Runnable("net::ServerSocketListenerProxy::OnSocketAcceptedRunnable"), + mListener(aListener), + mServ(aServ), + mTransport(aTransport) {} + + NS_DECL_NSIRUNNABLE + + private: + nsMainThreadPtrHandle<nsIServerSocketListener> mListener; + nsCOMPtr<nsIServerSocket> mServ; + nsCOMPtr<nsISocketTransport> mTransport; + }; + + class OnStopListeningRunnable : public Runnable { + public: + OnStopListeningRunnable( + const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener, + nsIServerSocket* aServ, nsresult aStatus) + : Runnable("net::ServerSocketListenerProxy::OnStopListeningRunnable"), + mListener(aListener), + mServ(aServ), + mStatus(aStatus) {} + + NS_DECL_NSIRUNNABLE + + private: + nsMainThreadPtrHandle<nsIServerSocketListener> mListener; + nsCOMPtr<nsIServerSocket> mServ; + nsresult mStatus; + }; + + private: + nsMainThreadPtrHandle<nsIServerSocketListener> mListener; + nsCOMPtr<nsIEventTarget> mTarget; +}; + +NS_IMPL_ISUPPORTS(ServerSocketListenerProxy, nsIServerSocketListener) + +NS_IMETHODIMP +ServerSocketListenerProxy::OnSocketAccepted(nsIServerSocket* aServ, + nsISocketTransport* aTransport) { + RefPtr<OnSocketAcceptedRunnable> r = + new OnSocketAcceptedRunnable(mListener, aServ, aTransport); + return mTarget->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +ServerSocketListenerProxy::OnStopListening(nsIServerSocket* aServ, + nsresult aStatus) { + RefPtr<OnStopListeningRunnable> r = + new OnStopListeningRunnable(mListener, aServ, aStatus); + return mTarget->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +ServerSocketListenerProxy::OnSocketAcceptedRunnable::Run() { + mListener->OnSocketAccepted(mServ, mTransport); + return NS_OK; +} + +NS_IMETHODIMP +ServerSocketListenerProxy::OnStopListeningRunnable::Run() { + mListener->OnStopListening(mServ, mStatus); + return NS_OK; +} + +} // namespace + +NS_IMETHODIMP +nsServerSocket::AsyncListen(nsIServerSocketListener* aListener) { + // ensuring mFD implies ensuring mLock + NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS); + { + MutexAutoLock lock(mLock); + mListener = new ServerSocketListenerProxy(aListener); + mListenerTarget = GetCurrentSerialEventTarget(); + } + + // Child classes may need to do additional setup just before listening begins + nsresult rv = OnSocketListen(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return PostEvent(this, &nsServerSocket::OnMsgAttach); +} + +NS_IMETHODIMP +nsServerSocket::GetPort(int32_t* aResult) { + // no need to enter the lock here + uint16_t port; + if (mAddr.raw.family == PR_AF_INET) { + port = mAddr.inet.port; + } else if (mAddr.raw.family == PR_AF_INET6) { + port = mAddr.ipv6.port; + } else { + return NS_ERROR_FAILURE; + } + + *aResult = static_cast<int32_t>(NetworkEndian::readUint16(&port)); + return NS_OK; +} + +NS_IMETHODIMP +nsServerSocket::GetAddress(PRNetAddr* aResult) { + // no need to enter the lock here + memcpy(aResult, &mAddr, sizeof(mAddr)); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsServerSocket.h b/netwerk/base/nsServerSocket.h new file mode 100644 index 0000000000..76908697fe --- /dev/null +++ b/netwerk/base/nsServerSocket.h @@ -0,0 +1,70 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsServerSocket_h__ +#define nsServerSocket_h__ + +#include "prio.h" +#include "nsASocketHandler.h" +#include "nsCOMPtr.h" +#include "nsIServerSocket.h" +#include "mozilla/Mutex.h" + +//----------------------------------------------------------------------------- + +class nsIEventTarget; +namespace mozilla { +namespace net { +union NetAddr; + +class nsServerSocket : public nsASocketHandler, public nsIServerSocket { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISERVERSOCKET + + // nsASocketHandler methods: + virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override; + virtual void OnSocketDetached(PRFileDesc* fd) override; + virtual void IsLocal(bool* aIsLocal) override; + virtual void KeepWhenOffline(bool* aKeepWhenOffline) override; + + virtual uint64_t ByteCountSent() override { return 0; } + virtual uint64_t ByteCountReceived() override { return 0; } + nsServerSocket(); + + virtual void CreateClientTransport(PRFileDesc* clientFD, + const mozilla::net::NetAddr& clientAddr); + virtual nsresult SetSocketDefaults() { return NS_OK; } + virtual nsresult OnSocketListen() { return NS_OK; } + + protected: + virtual ~nsServerSocket(); + PRFileDesc* mFD{nullptr}; + nsCOMPtr<nsIServerSocketListener> mListener; + + private: + void OnMsgClose(); + void OnMsgAttach(); + + // try attaching our socket (mFD) to the STS's poll list. + nsresult TryAttach(); + + nsresult InitWithAddressInternal(const PRNetAddr* aAddr, int32_t aBackLog, + bool aDualStack = false); + + // lock protects access to mListener; so it is not cleared while being used. + mozilla::Mutex mLock MOZ_UNANNOTATED{"nsServerSocket.mLock"}; + PRNetAddr mAddr = {.raw = {0, {0}}}; + nsCOMPtr<nsIEventTarget> mListenerTarget; + bool mAttached{false}; + bool mKeepWhenOffline{false}; +}; + +} // namespace net +} // namespace mozilla + +//----------------------------------------------------------------------------- + +#endif // nsServerSocket_h__ diff --git a/netwerk/base/nsSimpleNestedURI.cpp b/netwerk/base/nsSimpleNestedURI.cpp new file mode 100644 index 0000000000..54f57b4625 --- /dev/null +++ b/netwerk/base/nsSimpleNestedURI.cpp @@ -0,0 +1,228 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" + +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIClassInfoImpl.h" +#include "nsSimpleNestedURI.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" + +#include "mozilla/ipc/URIUtils.h" + +namespace mozilla { +namespace net { + +NS_IMPL_CLASSINFO(nsSimpleNestedURI, nullptr, nsIClassInfo::THREADSAFE, + NS_SIMPLENESTEDURI_CID) +// Empty CI getter. We only need nsIClassInfo for Serialization +NS_IMPL_CI_INTERFACE_GETTER0(nsSimpleNestedURI) + +NS_IMPL_ADDREF_INHERITED(nsSimpleNestedURI, nsSimpleURI) +NS_IMPL_RELEASE_INHERITED(nsSimpleNestedURI, nsSimpleURI) +NS_IMPL_QUERY_INTERFACE_CI_INHERITED(nsSimpleNestedURI, nsSimpleURI, + nsINestedURI) + +nsSimpleNestedURI::nsSimpleNestedURI(nsIURI* innerURI) : mInnerURI(innerURI) { + NS_ASSERTION(innerURI, "Must have inner URI"); +} + +nsresult nsSimpleNestedURI::SetPathQueryRef(const nsACString& aPathQueryRef) { + NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsIURI> inner; + nsresult rv = + NS_MutateURI(mInnerURI).SetPathQueryRef(aPathQueryRef).Finalize(inner); + NS_ENSURE_SUCCESS(rv, rv); + rv = nsSimpleURI::SetPathQueryRef(aPathQueryRef); + NS_ENSURE_SUCCESS(rv, rv); + // If the regular SetPathQueryRef worked, also set it on the inner URI + mInnerURI = inner; + return NS_OK; +} + +nsresult nsSimpleNestedURI::SetQuery(const nsACString& aQuery) { + NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsIURI> inner; + nsresult rv = NS_MutateURI(mInnerURI).SetQuery(aQuery).Finalize(inner); + NS_ENSURE_SUCCESS(rv, rv); + rv = nsSimpleURI::SetQuery(aQuery); + NS_ENSURE_SUCCESS(rv, rv); + // If the regular SetQuery worked, also set it on the inner URI + mInnerURI = inner; + return NS_OK; +} + +nsresult nsSimpleNestedURI::SetRef(const nsACString& aRef) { + NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsIURI> inner; + nsresult rv = NS_MutateURI(mInnerURI).SetRef(aRef).Finalize(inner); + NS_ENSURE_SUCCESS(rv, rv); + rv = nsSimpleURI::SetRef(aRef); + NS_ENSURE_SUCCESS(rv, rv); + // If the regular SetRef worked, also set it on the inner URI + mInnerURI = inner; + return NS_OK; +} + +// nsISerializable + +NS_IMETHODIMP +nsSimpleNestedURI::Read(nsIObjectInputStream* aStream) { + MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsSimpleNestedURI::ReadPrivate(nsIObjectInputStream* aStream) { + nsresult rv = nsSimpleURI::ReadPrivate(aStream); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsISupports> supports; + rv = aStream->ReadObject(true, getter_AddRefs(supports)); + if (NS_FAILED(rv)) return rv; + + mInnerURI = do_QueryInterface(supports, &rv); + if (NS_FAILED(rv)) return rv; + + return rv; +} + +NS_IMETHODIMP +nsSimpleNestedURI::Write(nsIObjectOutputStream* aStream) { + nsCOMPtr<nsISerializable> serializable = do_QueryInterface(mInnerURI); + if (!serializable) { + // We can't serialize ourselves + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = nsSimpleURI::Write(aStream); + if (NS_FAILED(rv)) return rv; + + rv = aStream->WriteCompoundObject(mInnerURI, NS_GET_IID(nsIURI), true); + return rv; +} + +NS_IMETHODIMP_(void) +nsSimpleNestedURI::Serialize(mozilla::ipc::URIParams& aParams) { + using namespace mozilla::ipc; + + SimpleNestedURIParams params; + URIParams simpleParams; + + nsSimpleURI::Serialize(simpleParams); + params.simpleParams() = simpleParams; + + SerializeURI(mInnerURI, params.innerURI()); + + aParams = params; +} + +bool nsSimpleNestedURI::Deserialize(const mozilla::ipc::URIParams& aParams) { + using namespace mozilla::ipc; + + if (aParams.type() != URIParams::TSimpleNestedURIParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const SimpleNestedURIParams& params = aParams.get_SimpleNestedURIParams(); + if (!nsSimpleURI::Deserialize(params.simpleParams())) return false; + + mInnerURI = DeserializeURI(params.innerURI()); + return true; +} + +// nsINestedURI + +NS_IMETHODIMP +nsSimpleNestedURI::GetInnerURI(nsIURI** aURI) { + NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsIURI> uri = mInnerURI; + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleNestedURI::GetInnermostURI(nsIURI** uri) { + return NS_ImplGetInnermostURI(this, uri); +} + +// nsSimpleURI overrides +/* virtual */ +nsresult nsSimpleNestedURI::EqualsInternal( + nsIURI* other, nsSimpleURI::RefHandlingEnum refHandlingMode, bool* result) { + *result = false; + NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED); + + if (other) { + bool correctScheme; + nsresult rv = other->SchemeIs(mScheme.get(), &correctScheme); + NS_ENSURE_SUCCESS(rv, rv); + + if (correctScheme) { + nsCOMPtr<nsINestedURI> nest = do_QueryInterface(other); + if (nest) { + nsCOMPtr<nsIURI> otherInner; + rv = nest->GetInnerURI(getter_AddRefs(otherInner)); + NS_ENSURE_SUCCESS(rv, rv); + + return (refHandlingMode == eHonorRef) + ? otherInner->Equals(mInnerURI, result) + : otherInner->EqualsExceptRef(mInnerURI, result); + } + } + } + + return NS_OK; +} + +/* virtual */ +nsSimpleURI* nsSimpleNestedURI::StartClone( + nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef) { + NS_ENSURE_TRUE(mInnerURI, nullptr); + + nsCOMPtr<nsIURI> innerClone; + nsresult rv = NS_OK; + if (refHandlingMode == eHonorRef) { + innerClone = mInnerURI; + } else if (refHandlingMode == eReplaceRef) { + rv = NS_GetURIWithNewRef(mInnerURI, newRef, getter_AddRefs(innerClone)); + } else { + rv = NS_GetURIWithoutRef(mInnerURI, getter_AddRefs(innerClone)); + } + + if (NS_FAILED(rv)) { + return nullptr; + } + + nsSimpleNestedURI* url = new nsSimpleNestedURI(innerClone); + SetRefOnClone(url, refHandlingMode, newRef); + + return url; +} + +// Queries this list of interfaces. If none match, it queries mURI. +NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsSimpleNestedURI::Mutator, nsIURISetters, + nsIURIMutator, nsISerializable, + nsINestedURIMutator) + +NS_IMETHODIMP +nsSimpleNestedURI::Mutate(nsIURIMutator** aMutator) { + RefPtr<nsSimpleNestedURI::Mutator> mutator = new nsSimpleNestedURI::Mutator(); + nsresult rv = mutator->InitFromURI(this); + if (NS_FAILED(rv)) { + return rv; + } + mutator.forget(aMutator); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsSimpleNestedURI.h b/netwerk/base/nsSimpleNestedURI.h new file mode 100644 index 0000000000..0887431421 --- /dev/null +++ b/netwerk/base/nsSimpleNestedURI.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * URI class to be used for cases when a simple URI actually resolves to some + * other sort of URI, with the latter being what's loaded when the load + * happens. + */ + +#ifndef nsSimpleNestedURI_h__ +#define nsSimpleNestedURI_h__ + +#include "nsCOMPtr.h" +#include "nsSimpleURI.h" +#include "nsINestedURI.h" +#include "nsIURIMutator.h" + +namespace mozilla { +namespace net { + +class nsSimpleNestedURI : public nsSimpleURI, public nsINestedURI { + protected: + nsSimpleNestedURI() = default; + explicit nsSimpleNestedURI(nsIURI* innerURI); + + ~nsSimpleNestedURI() = default; + + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSINESTEDURI + + // Overrides for various methods nsSimpleURI implements follow. + + // nsSimpleURI overrides + virtual nsresult EqualsInternal(nsIURI* other, + RefHandlingEnum refHandlingMode, + bool* result) override; + virtual nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode, + const nsACString& newRef) override; + NS_IMETHOD Mutate(nsIURIMutator** _retval) override; + NS_IMETHOD_(void) Serialize(ipc::URIParams& aParams) override; + + // nsISerializable overrides + NS_IMETHOD Read(nsIObjectInputStream* aStream) override; + NS_IMETHOD Write(nsIObjectOutputStream* aStream) override; + + protected: + nsCOMPtr<nsIURI> mInnerURI; + + nsresult SetPathQueryRef(const nsACString& aPathQueryRef) override; + nsresult SetQuery(const nsACString& aQuery) override; + nsresult SetRef(const nsACString& aRef) override; + bool Deserialize(const mozilla::ipc::URIParams&); + nsresult ReadPrivate(nsIObjectInputStream* stream); + + public: + class Mutator final : public nsIURIMutator, + public BaseURIMutator<nsSimpleNestedURI>, + public nsISerializable, + public nsINestedURIMutator { + NS_DECL_ISUPPORTS + NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI) + + explicit Mutator() = default; + + private: + virtual ~Mutator() = default; + + [[nodiscard]] NS_IMETHOD Deserialize( + const mozilla::ipc::URIParams& aParams) override { + return InitFromIPCParams(aParams); + } + + NS_IMETHOD + Write(nsIObjectOutputStream* aOutputStream) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override { + return InitFromInputStream(aStream); + } + + [[nodiscard]] NS_IMETHOD Finalize(nsIURI** aURI) override { + mURI.forget(aURI); + return NS_OK; + } + + [[nodiscard]] NS_IMETHOD SetSpec(const nsACString& aSpec, + nsIURIMutator** aMutator) override { + if (aMutator) { + NS_ADDREF(*aMutator = this); + } + return InitFromSpec(aSpec); + } + + [[nodiscard]] NS_IMETHOD Init(nsIURI* innerURI) override { + mURI = new nsSimpleNestedURI(innerURI); + return NS_OK; + } + + friend class nsSimpleNestedURI; + }; + + friend BaseURIMutator<nsSimpleNestedURI>; +}; + +} // namespace net +} // namespace mozilla + +#endif /* nsSimpleNestedURI_h__ */ diff --git a/netwerk/base/nsSimpleStreamListener.cpp b/netwerk/base/nsSimpleStreamListener.cpp new file mode 100644 index 0000000000..beed8a9f40 --- /dev/null +++ b/netwerk/base/nsSimpleStreamListener.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSimpleStreamListener.h" + +namespace mozilla { +namespace net { + +// +//---------------------------------------------------------------------------- +// nsISupports implementation... +//---------------------------------------------------------------------------- +// +NS_IMPL_ISUPPORTS(nsSimpleStreamListener, nsISimpleStreamListener, + nsIStreamListener, nsIRequestObserver) + +// +//---------------------------------------------------------------------------- +// nsIRequestObserver implementation... +//---------------------------------------------------------------------------- +// +NS_IMETHODIMP +nsSimpleStreamListener::OnStartRequest(nsIRequest* aRequest) { + return mObserver ? mObserver->OnStartRequest(aRequest) : NS_OK; +} + +NS_IMETHODIMP +nsSimpleStreamListener::OnStopRequest(nsIRequest* request, nsresult aStatus) { + return mObserver ? mObserver->OnStopRequest(request, aStatus) : NS_OK; +} + +// +//---------------------------------------------------------------------------- +// nsIStreamListener implementation... +//---------------------------------------------------------------------------- +// +NS_IMETHODIMP +nsSimpleStreamListener::OnDataAvailable(nsIRequest* request, + nsIInputStream* aSource, + uint64_t aOffset, uint32_t aCount) { + uint32_t writeCount; + nsresult rv = mSink->WriteFrom(aSource, aCount, &writeCount); + // + // Equate zero bytes read and NS_SUCCEEDED to stopping the read. + // + if (NS_SUCCEEDED(rv) && (writeCount == 0)) return NS_BASE_STREAM_CLOSED; + return rv; +} + +// +//---------------------------------------------------------------------------- +// nsISimpleStreamListener implementation... +//---------------------------------------------------------------------------- +// +NS_IMETHODIMP +nsSimpleStreamListener::Init(nsIOutputStream* aSink, + nsIRequestObserver* aObserver) { + MOZ_ASSERT(aSink, "null output stream"); + + mSink = aSink; + mObserver = aObserver; + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsSimpleStreamListener.h b/netwerk/base/nsSimpleStreamListener.h new file mode 100644 index 0000000000..890b49f61b --- /dev/null +++ b/netwerk/base/nsSimpleStreamListener.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSimpleStreamListener_h__ +#define nsSimpleStreamListener_h__ + +#include "nsISimpleStreamListener.h" +#include "nsIOutputStream.h" +#include "nsCOMPtr.h" + +namespace mozilla { +namespace net { + +class nsSimpleStreamListener : public nsISimpleStreamListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSISIMPLESTREAMLISTENER + + nsSimpleStreamListener() = default; + + protected: + virtual ~nsSimpleStreamListener() = default; + + nsCOMPtr<nsIOutputStream> mSink; + nsCOMPtr<nsIRequestObserver> mObserver; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/nsSimpleURI.cpp b/netwerk/base/nsSimpleURI.cpp new file mode 100644 index 0000000000..56e3300ffe --- /dev/null +++ b/netwerk/base/nsSimpleURI.cpp @@ -0,0 +1,794 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" + +#undef LOG +#include "ipc/IPCMessageUtils.h" + +#include "nsSimpleURI.h" +#include "nscore.h" +#include "nsCRT.h" +#include "nsString.h" +#include "nsURLHelper.h" +#include "nsNetCID.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsEscape.h" +#include "nsError.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/TextUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "nsIClassInfoImpl.h" +#include "nsIURIMutator.h" +#include "mozilla/net/MozURL.h" +#include "mozilla/StaticPrefs_network.h" + +using namespace mozilla::ipc; + +namespace mozilla { +namespace net { + +static NS_DEFINE_CID(kThisSimpleURIImplementationCID, + NS_THIS_SIMPLEURI_IMPLEMENTATION_CID); + +/* static */ +already_AddRefed<nsSimpleURI> nsSimpleURI::From(nsIURI* aURI) { + RefPtr<nsSimpleURI> uri; + nsresult rv = aURI->QueryInterface(kThisSimpleURIImplementationCID, + getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return uri.forget(); +} + +NS_IMPL_CLASSINFO(nsSimpleURI, nullptr, nsIClassInfo::THREADSAFE, + NS_SIMPLEURI_CID) +// Empty CI getter. We only need nsIClassInfo for Serialization +NS_IMPL_CI_INTERFACE_GETTER0(nsSimpleURI) + +//////////////////////////////////////////////////////////////////////////////// +// nsSimpleURI methods: + +NS_IMPL_ADDREF(nsSimpleURI) +NS_IMPL_RELEASE(nsSimpleURI) +NS_INTERFACE_TABLE_HEAD(nsSimpleURI) + NS_INTERFACE_TABLE(nsSimpleURI, nsIURI, nsISerializable) + NS_INTERFACE_TABLE_TO_MAP_SEGUE + NS_IMPL_QUERY_CLASSINFO(nsSimpleURI) + if (aIID.Equals(kThisSimpleURIImplementationCID)) { + foundInterface = static_cast<nsIURI*>(this); + } else + NS_INTERFACE_MAP_ENTRY(nsISizeOf) +NS_INTERFACE_MAP_END + +//////////////////////////////////////////////////////////////////////////////// +// nsISerializable methods: + +NS_IMETHODIMP +nsSimpleURI::Read(nsIObjectInputStream* aStream) { + MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsSimpleURI::ReadPrivate(nsIObjectInputStream* aStream) { + nsresult rv; + + bool isMutable; + rv = aStream->ReadBoolean(&isMutable); + if (NS_FAILED(rv)) return rv; + Unused << isMutable; + + rv = aStream->ReadCString(mScheme); + if (NS_FAILED(rv)) return rv; + + rv = aStream->ReadCString(mPath); + if (NS_FAILED(rv)) return rv; + + bool isRefValid; + rv = aStream->ReadBoolean(&isRefValid); + if (NS_FAILED(rv)) return rv; + mIsRefValid = isRefValid; + + if (isRefValid) { + rv = aStream->ReadCString(mRef); + if (NS_FAILED(rv)) return rv; + } else { + mRef.Truncate(); // invariant: mRef should be empty when it's not valid + } + + bool isQueryValid; + rv = aStream->ReadBoolean(&isQueryValid); + if (NS_FAILED(rv)) return rv; + mIsQueryValid = isQueryValid; + + if (isQueryValid) { + rv = aStream->ReadCString(mQuery); + if (NS_FAILED(rv)) return rv; + } else { + mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::Write(nsIObjectOutputStream* aStream) { + nsresult rv; + + rv = aStream->WriteBoolean(false); // former mMutable + if (NS_FAILED(rv)) return rv; + + rv = aStream->WriteStringZ(mScheme.get()); + if (NS_FAILED(rv)) return rv; + + rv = aStream->WriteStringZ(mPath.get()); + if (NS_FAILED(rv)) return rv; + + rv = aStream->WriteBoolean(mIsRefValid); + if (NS_FAILED(rv)) return rv; + + if (mIsRefValid) { + rv = aStream->WriteStringZ(mRef.get()); + if (NS_FAILED(rv)) return rv; + } + + rv = aStream->WriteBoolean(mIsQueryValid); + if (NS_FAILED(rv)) return rv; + + if (mIsQueryValid) { + rv = aStream->WriteStringZ(mQuery.get()); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + +void nsSimpleURI::Serialize(URIParams& aParams) { + SimpleURIParams params; + + params.scheme() = mScheme; + params.path() = mPath; + + if (mIsRefValid) { + params.ref() = mRef; + } else { + params.ref().SetIsVoid(true); + } + + if (mIsQueryValid) { + params.query() = mQuery; + } else { + params.query().SetIsVoid(true); + } + + aParams = params; +} + +bool nsSimpleURI::Deserialize(const URIParams& aParams) { + if (aParams.type() != URIParams::TSimpleURIParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const SimpleURIParams& params = aParams.get_SimpleURIParams(); + + mScheme = params.scheme(); + mPath = params.path(); + + if (params.ref().IsVoid()) { + mRef.Truncate(); + mIsRefValid = false; + } else { + mRef = params.ref(); + mIsRefValid = true; + } + + if (params.query().IsVoid()) { + mQuery.Truncate(); + mIsQueryValid = false; + } else { + mQuery = params.query(); + mIsQueryValid = true; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIURI methods: + +NS_IMETHODIMP +nsSimpleURI::GetSpec(nsACString& result) { + if (!result.Assign(mScheme, fallible) || !result.Append(":"_ns, fallible) || + !result.Append(mPath, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (mIsQueryValid) { + if (!result.Append("?"_ns, fallible) || !result.Append(mQuery, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } else { + MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken"); + } + + if (mIsRefValid) { + if (!result.Append("#"_ns, fallible) || !result.Append(mRef, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } else { + MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken"); + } + + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsSimpleURI::GetSpecIgnoringRef(nsACString& result) { + result = mScheme + ":"_ns + mPath; + if (mIsQueryValid) { + result += "?"_ns + mQuery; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetDisplaySpec(nsACString& aUnicodeSpec) { + return GetSpec(aUnicodeSpec); +} + +NS_IMETHODIMP +nsSimpleURI::GetDisplayHostPort(nsACString& aUnicodeHostPort) { + return GetHostPort(aUnicodeHostPort); +} + +NS_IMETHODIMP +nsSimpleURI::GetDisplayHost(nsACString& aUnicodeHost) { + return GetHost(aUnicodeHost); +} + +NS_IMETHODIMP +nsSimpleURI::GetDisplayPrePath(nsACString& aPrePath) { + return GetPrePath(aPrePath); +} + +NS_IMETHODIMP +nsSimpleURI::GetHasRef(bool* result) { + *result = mIsRefValid; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetHasUserPass(bool* result) { + *result = false; + return NS_OK; +} + +nsresult nsSimpleURI::SetSpecInternal(const nsACString& aSpec, + bool aStripWhitespace) { + if (StaticPrefs::network_url_max_length() && + aSpec.Length() > StaticPrefs::network_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + nsresult rv = net_ExtractURLScheme(aSpec, mScheme); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoCString spec; + rv = net_FilterAndEscapeURI( + aSpec, esc_OnlyNonASCII, + aStripWhitespace ? ASCIIMask::MaskWhitespace() : ASCIIMask::MaskCRLFTab(), + spec); + if (NS_FAILED(rv)) { + return rv; + } + + int32_t colonPos = spec.FindChar(':'); + MOZ_ASSERT(colonPos != kNotFound, "A colon should be in this string"); + // This sets mPath, mQuery and mRef. + return SetPathQueryRefInternal(Substring(spec, colonPos + 1)); +} + +NS_IMETHODIMP +nsSimpleURI::GetScheme(nsACString& result) { + result = mScheme; + return NS_OK; +} + +nsresult nsSimpleURI::SetScheme(const nsACString& input) { + // Strip tabs, newlines, carriage returns from input + nsAutoCString scheme(input); + scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab()); + + if (!net_IsValidScheme(scheme)) { + NS_WARNING("the given url scheme contains invalid characters"); + return NS_ERROR_MALFORMED_URI; + } + + mScheme = scheme; + ToLowerCase(mScheme); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetPrePath(nsACString& result) { + result = mScheme + ":"_ns; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetUserPass(nsACString& result) { return NS_ERROR_FAILURE; } + +nsresult nsSimpleURI::SetUserPass(const nsACString& userPass) { + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::GetUsername(nsACString& result) { return NS_ERROR_FAILURE; } + +nsresult nsSimpleURI::SetUsername(const nsACString& userName) { + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::GetPassword(nsACString& result) { return NS_ERROR_FAILURE; } + +nsresult nsSimpleURI::SetPassword(const nsACString& password) { + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::GetHostPort(nsACString& result) { + // Note: Audit all callers before changing this to return an empty + // string -- CAPS and UI code may depend on this throwing. + // Note: If this is changed, change GetAsciiHostPort as well. + return NS_ERROR_FAILURE; +} + +nsresult nsSimpleURI::SetHostPort(const nsACString& aValue) { + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::GetHost(nsACString& result) { + // Note: Audit all callers before changing this to return an empty + // string -- CAPS and UI code depend on this throwing. + return NS_ERROR_FAILURE; +} + +nsresult nsSimpleURI::SetHost(const nsACString& host) { + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::GetPort(int32_t* result) { + // Note: Audit all callers before changing this to return an empty + // string -- CAPS and UI code may depend on this throwing. + return NS_ERROR_FAILURE; +} + +nsresult nsSimpleURI::SetPort(int32_t port) { return NS_ERROR_FAILURE; } + +NS_IMETHODIMP +nsSimpleURI::GetPathQueryRef(nsACString& result) { + result = mPath; + if (mIsQueryValid) { + result += "?"_ns + mQuery; + } + if (mIsRefValid) { + result += "#"_ns + mRef; + } + + return NS_OK; +} + +nsresult nsSimpleURI::SetPathQueryRef(const nsACString& aPath) { + if (StaticPrefs::network_url_max_length() && + aPath.Length() > StaticPrefs::network_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + nsAutoCString path; + nsresult rv = NS_EscapeURL(aPath, esc_OnlyNonASCII, path, fallible); + if (NS_FAILED(rv)) { + return rv; + } + return SetPathQueryRefInternal(path); +} + +nsresult nsSimpleURI::SetPathQueryRefInternal(const nsACString& aPath) { + nsresult rv; + const auto* start = aPath.BeginReading(); + const auto* end = aPath.EndReading(); + + // Find the first instance of ? or # that marks the end of the path. + auto hashOrQueryFilter = [](char c) { return c == '?' || c == '#'; }; + const auto* pathEnd = std::find_if(start, end, hashOrQueryFilter); + + mIsQueryValid = false; + mQuery.Truncate(); + + mIsRefValid = false; + mRef.Truncate(); + + // The path + if (!mPath.Assign(Substring(start, pathEnd), fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (pathEnd == end) { + return NS_OK; + } + + const auto* queryEnd = + std::find_if(pathEnd, end, [](char c) { return c == '#'; }); + + rv = SetQuery(Substring(pathEnd, queryEnd)); + if (NS_FAILED(rv)) { + return rv; + } + + if (queryEnd == end) { + return NS_OK; + } + + return SetRef(Substring(queryEnd, end)); +} + +NS_IMETHODIMP +nsSimpleURI::GetRef(nsACString& result) { + if (!mIsRefValid) { + MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken"); + result.Truncate(); + } else { + result = mRef; + } + + return NS_OK; +} + +// NOTE: SetRef("") removes our ref, whereas SetRef("#") sets it to the empty +// string (and will result in .spec and .path having a terminal #). +nsresult nsSimpleURI::SetRef(const nsACString& aRef) { + if (StaticPrefs::network_url_max_length() && + aRef.Length() > StaticPrefs::network_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + nsAutoCString ref; + nsresult rv = + NS_EscapeURL(aRef, esc_OnlyNonASCII | esc_Spaces, ref, fallible); + if (NS_FAILED(rv)) { + return rv; + } + + if (ref.IsEmpty()) { + // Empty string means to remove ref completely. + mIsRefValid = false; + mRef.Truncate(); // invariant: mRef should be empty when it's not valid + + // Trim trailing invalid chars when ref and query are removed + if (mScheme != "javascript" && mRef.IsEmpty() && mQuery.IsEmpty()) { + TrimTrailingCharactersFromPath(); + } + + return NS_OK; + } + + mIsRefValid = true; + + // Gracefully skip initial hash + if (ref[0] == '#') { + mRef = Substring(ref, 1); + } else { + mRef = ref; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::Equals(nsIURI* other, bool* result) { + return EqualsInternal(other, eHonorRef, result); +} + +NS_IMETHODIMP +nsSimpleURI::EqualsExceptRef(nsIURI* other, bool* result) { + return EqualsInternal(other, eIgnoreRef, result); +} + +/* virtual */ +nsresult nsSimpleURI::EqualsInternal( + nsIURI* other, nsSimpleURI::RefHandlingEnum refHandlingMode, bool* result) { + NS_ENSURE_ARG_POINTER(other); + MOZ_ASSERT(result, "null pointer"); + + RefPtr<nsSimpleURI> otherUri; + nsresult rv = other->QueryInterface(kThisSimpleURIImplementationCID, + getter_AddRefs(otherUri)); + if (NS_FAILED(rv)) { + *result = false; + return NS_OK; + } + + *result = EqualsInternal(otherUri, refHandlingMode); + return NS_OK; +} + +bool nsSimpleURI::EqualsInternal(nsSimpleURI* otherUri, + RefHandlingEnum refHandlingMode) { + bool result = (mScheme == otherUri->mScheme && mPath == otherUri->mPath); + + if (result) { + result = (mIsQueryValid == otherUri->mIsQueryValid && + (!mIsQueryValid || mQuery == otherUri->mQuery)); + } + + if (result && refHandlingMode == eHonorRef) { + result = (mIsRefValid == otherUri->mIsRefValid && + (!mIsRefValid || mRef == otherUri->mRef)); + } + + return result; +} + +NS_IMETHODIMP +nsSimpleURI::SchemeIs(const char* i_Scheme, bool* o_Equals) { + MOZ_ASSERT(o_Equals, "null pointer"); + if (!i_Scheme) { + *o_Equals = false; + return NS_OK; + } + + const char* this_scheme = mScheme.get(); + + // mScheme is guaranteed to be lower case. + if (*i_Scheme == *this_scheme || *i_Scheme == (*this_scheme - ('a' - 'A'))) { + *o_Equals = nsCRT::strcasecmp(this_scheme, i_Scheme) == 0; + } else { + *o_Equals = false; + } + + return NS_OK; +} + +/* virtual */ nsSimpleURI* nsSimpleURI::StartClone( + nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef) { + nsSimpleURI* url = new nsSimpleURI(); + SetRefOnClone(url, refHandlingMode, newRef); + return url; +} + +/* virtual */ +void nsSimpleURI::SetRefOnClone(nsSimpleURI* url, + nsSimpleURI::RefHandlingEnum refHandlingMode, + const nsACString& newRef) { + if (refHandlingMode == eHonorRef) { + url->mRef = mRef; + url->mIsRefValid = mIsRefValid; + } else if (refHandlingMode == eReplaceRef) { + url->SetRef(newRef); + } +} + +nsresult nsSimpleURI::Clone(nsIURI** result) { + return CloneInternal(eHonorRef, ""_ns, result); +} + +nsresult nsSimpleURI::CloneInternal( + nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef, + nsIURI** result) { + RefPtr<nsSimpleURI> url = StartClone(refHandlingMode, newRef); + if (!url) return NS_ERROR_OUT_OF_MEMORY; + + url->mScheme = mScheme; + url->mPath = mPath; + + url->mIsQueryValid = mIsQueryValid; + if (url->mIsQueryValid) { + url->mQuery = mQuery; + } + + url.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::Resolve(const nsACString& relativePath, nsACString& result) { + nsAutoCString scheme; + nsresult rv = net_ExtractURLScheme(relativePath, scheme); + if (NS_SUCCEEDED(rv)) { + result = relativePath; + return NS_OK; + } + + nsAutoCString spec; + rv = GetAsciiSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If getting the spec fails for some reason, preserve behaviour and just + // return the relative path. + result = relativePath; + return NS_OK; + } + + RefPtr<MozURL> url; + rv = MozURL::Init(getter_AddRefs(url), spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If parsing the current url fails, we revert to the previous behaviour + // and just return the relative path. + result = relativePath; + return NS_OK; + } + + RefPtr<MozURL> url2; + rv = MozURL::Init(getter_AddRefs(url2), relativePath, url); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If parsing the relative url fails, we revert to the previous behaviour + // and just return the relative path. + result = relativePath; + return NS_OK; + } + + result = url2->Spec(); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetAsciiSpec(nsACString& aResult) { + nsresult rv = GetSpec(aResult); + if (NS_FAILED(rv)) return rv; + MOZ_ASSERT(IsAscii(aResult), "The spec should be ASCII"); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetAsciiHostPort(nsACString& result) { + // XXX This behavior mimics GetHostPort. + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::GetAsciiHost(nsACString& result) { + result.Truncate(); + return NS_OK; +} + +//---------------------------------------------------------------------------- +// nsSimpleURI::nsISizeOf +//---------------------------------------------------------------------------- + +size_t nsSimpleURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mScheme.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mQuery.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mRef.SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +size_t nsSimpleURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +NS_IMETHODIMP +nsSimpleURI::GetFilePath(nsACString& aFilePath) { + aFilePath = mPath; + return NS_OK; +} + +nsresult nsSimpleURI::SetFilePath(const nsACString& aFilePath) { + if (mPath.IsEmpty() || mPath.First() != '/') { + // cannot-be-a-base + return NS_ERROR_MALFORMED_URI; + } + const char* current = aFilePath.BeginReading(); + const char* end = aFilePath.EndReading(); + + // Only go up to the first ? or # symbol + for (; current < end; ++current) { + if (*current == '?' || *current == '#') { + break; + } + } + return SetPathQueryRef( + nsDependentCSubstring(aFilePath.BeginReading(), current)); +} + +NS_IMETHODIMP +nsSimpleURI::GetQuery(nsACString& aQuery) { + if (!mIsQueryValid) { + MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken"); + aQuery.Truncate(); + } else { + aQuery = mQuery; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetHasQuery(bool* result) { + *result = mIsQueryValid; + return NS_OK; +} + +nsresult nsSimpleURI::SetQuery(const nsACString& aQuery) { + if (StaticPrefs::network_url_max_length() && + aQuery.Length() > StaticPrefs::network_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + nsAutoCString query; + nsresult rv = NS_EscapeURL(aQuery, esc_OnlyNonASCII, query, fallible); + if (NS_FAILED(rv)) { + return rv; + } + + if (query.IsEmpty()) { + // Empty string means to remove query completely. + mIsQueryValid = false; + mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid + + // Trim trailing invalid chars when ref and query are removed + if (mScheme != "javascript" && mRef.IsEmpty() && mQuery.IsEmpty()) { + TrimTrailingCharactersFromPath(); + } + + return NS_OK; + } + + mIsQueryValid = true; + + // Gracefully skip initial question mark + if (query[0] == '?') { + mQuery = Substring(query, 1); + } else { + mQuery = query; + } + + return NS_OK; +} + +nsresult nsSimpleURI::SetQueryWithEncoding(const nsACString& aQuery, + const Encoding* aEncoding) { + return SetQuery(aQuery); +} + +void nsSimpleURI::TrimTrailingCharactersFromPath() { + const auto* start = mPath.BeginReading(); + const auto* end = mPath.EndReading(); + + auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; }; + const auto* newEnd = + std::find_if(std::reverse_iterator<decltype(end)>(end), + std::reverse_iterator<decltype(start)>(start), charFilter) + .base(); + + auto trailCount = std::distance(newEnd, end); + if (trailCount) { + mPath.Truncate(mPath.Length() - trailCount); + } +} + +// Queries this list of interfaces. If none match, it queries mURI. +NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsSimpleURI::Mutator, nsIURISetters, + nsIURIMutator, nsISerializable, + nsISimpleURIMutator) + +NS_IMETHODIMP +nsSimpleURI::Mutate(nsIURIMutator** aMutator) { + RefPtr<nsSimpleURI::Mutator> mutator = new nsSimpleURI::Mutator(); + nsresult rv = mutator->InitFromURI(this); + if (NS_FAILED(rv)) { + return rv; + } + mutator.forget(aMutator); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsSimpleURI.h b/netwerk/base/nsSimpleURI.h new file mode 100644 index 0000000000..560dd56040 --- /dev/null +++ b/netwerk/base/nsSimpleURI.h @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSimpleURI_h__ +#define nsSimpleURI_h__ + +#include "mozilla/MemoryReporting.h" +#include "nsIURI.h" +#include "nsISerializable.h" +#include "nsString.h" +#include "nsIClassInfo.h" +#include "nsISizeOf.h" +#include "nsIURIMutator.h" +#include "nsISimpleURIMutator.h" + +namespace mozilla { +namespace net { + +#define NS_THIS_SIMPLEURI_IMPLEMENTATION_CID \ + { /* 0b9bb0c2-fee6-470b-b9b9-9fd9462b5e19 */ \ + 0x0b9bb0c2, 0xfee6, 0x470b, { \ + 0xb9, 0xb9, 0x9f, 0xd9, 0x46, 0x2b, 0x5e, 0x19 \ + } \ + } + +class nsSimpleURI : public nsIURI, public nsISerializable, public nsISizeOf { + protected: + nsSimpleURI() = default; + virtual ~nsSimpleURI() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSISERIALIZABLE + + static already_AddRefed<nsSimpleURI> From(nsIURI* aURI); + + // nsSimpleURI methods: + + bool Equals(nsSimpleURI* aOther) { return EqualsInternal(aOther, eHonorRef); } + + // nsISizeOf + // Among the sub-classes that inherit (directly or indirectly) from + // nsSimpleURI, measurement of the following members may be added later if + // DMD finds it is worthwhile: + // - nsJSURI: mBaseURI + // - nsSimpleNestedURI: mInnerURI + // - nsBlobURI: mPrincipal + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override; + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override; + + protected: + // enum used in a few places to specify how .ref attribute should be handled + enum RefHandlingEnum { eIgnoreRef, eHonorRef, eReplaceRef }; + + virtual nsresult Clone(nsIURI** result); + virtual nsresult SetSpecInternal(const nsACString& aSpec, + bool aStripWhitespace = false); + virtual nsresult SetScheme(const nsACString& input); + virtual nsresult SetUserPass(const nsACString& input); + nsresult SetUsername(const nsACString& input); + virtual nsresult SetPassword(const nsACString& input); + virtual nsresult SetHostPort(const nsACString& aValue); + virtual nsresult SetHost(const nsACString& input); + virtual nsresult SetPort(int32_t port); + virtual nsresult SetPathQueryRef(const nsACString& aPath); + virtual nsresult SetRef(const nsACString& aRef); + virtual nsresult SetFilePath(const nsACString& aFilePath); + virtual nsresult SetQuery(const nsACString& aQuery); + virtual nsresult SetQueryWithEncoding(const nsACString& aQuery, + const Encoding* encoding); + nsresult ReadPrivate(nsIObjectInputStream* stream); + + // Helper to share code between Equals methods. + virtual nsresult EqualsInternal(nsIURI* other, + RefHandlingEnum refHandlingMode, + bool* result); + + // Helper to be used by inherited classes who want to test + // equality given an assumed nsSimpleURI. This must NOT check + // the passed-in other for QI to our CID. + bool EqualsInternal(nsSimpleURI* otherUri, RefHandlingEnum refHandlingMode); + + // Used by StartClone (and versions of StartClone in subclasses) to + // handle the ref in the right way for clones. + void SetRefOnClone(nsSimpleURI* url, RefHandlingEnum refHandlingMode, + const nsACString& newRef); + + // NOTE: This takes the refHandlingMode as an argument because + // nsSimpleNestedURI's specialized version needs to know how to clone + // its inner URI. + virtual nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode, + const nsACString& newRef); + + // Helper to share code between Clone methods. + virtual nsresult CloneInternal(RefHandlingEnum refHandlingMode, + const nsACString& newRef, nsIURI** result); + + void TrimTrailingCharactersFromPath(); + nsresult EscapeAndSetPathQueryRef(const nsACString& aPath); + nsresult SetPathQueryRefInternal(const nsACString& aPath); + + bool Deserialize(const mozilla::ipc::URIParams&); + + nsCString mScheme; + nsCString mPath; // NOTE: mPath does not include ref, as an optimization + nsCString mRef; // so that URIs with different refs can share string data. + nsCString + mQuery; // so that URLs with different querys can share string data. + bool mIsRefValid{false}; // To distinguish between empty-ref and no-ref. + // To distinguish between empty-query and no-query. + bool mIsQueryValid{false}; + + public: + class Mutator final : public nsIURIMutator, + public BaseURIMutator<nsSimpleURI>, + public nsISimpleURIMutator, + public nsISerializable { + NS_DECL_ISUPPORTS + NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI) + NS_DEFINE_NSIMUTATOR_COMMON + + NS_IMETHOD + Write(nsIObjectOutputStream* aOutputStream) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override { + return InitFromInputStream(aStream); + } + + [[nodiscard]] NS_IMETHOD SetSpecAndFilterWhitespace( + const nsACString& aSpec, nsIURIMutator** aMutator) override { + if (aMutator) { + *aMutator = do_AddRef(this).take(); + } + + nsresult rv = NS_OK; + RefPtr<nsSimpleURI> uri = new nsSimpleURI(); + rv = uri->SetSpecInternal(aSpec, /* filterWhitespace */ true); + if (NS_FAILED(rv)) { + return rv; + } + mURI = std::move(uri); + return NS_OK; + } + + explicit Mutator() = default; + + private: + virtual ~Mutator() = default; + + friend class nsSimpleURI; + }; + + friend BaseURIMutator<nsSimpleURI>; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsSimpleURI_h__ diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp new file mode 100644 index 0000000000..e12a376788 --- /dev/null +++ b/netwerk/base/nsSocketTransport2.cpp @@ -0,0 +1,3411 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> + +#include "nsSocketTransport2.h" + +#include "IOActivityMonitor.h" +#include "NSSErrorsService.h" +#include "NetworkDataCountLayer.h" +#include "QuicSocketControl.h" +#include "mozilla/Attributes.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/Telemetry.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/SSLTokensCache.h" +#include "mozilla/ProfilerBandwidthCounter.h" +#include "nsCOMPtr.h" +#include "nsICancelable.h" +#include "nsIClassInfoImpl.h" +#include "nsIDNSByTypeRecord.h" +#include "nsIDNSRecord.h" +#include "nsIDNSService.h" +#include "nsIOService.h" +#include "nsIPipe.h" +#include "nsISocketProvider.h" +#include "nsITLSSocketControl.h" +#include "nsNetAddr.h" +#include "nsNetCID.h" +#include "nsNetSegmentUtils.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "nsProxyInfo.h" +#include "nsSocketProviderService.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "nsTransportUtils.h" +#include "nsURLHelper.h" +#include "prerr.h" +#include "sslexp.h" +#include "xpcpublic.h" + +#if defined(FUZZING) +# include "FuzzyLayer.h" +# include "FuzzySocketControl.h" +# include "mozilla/StaticPrefs_fuzzing.h" +#endif + +#if defined(XP_WIN) +# include "ShutdownLayer.h" +#endif + +/* Following inclusions required for keepalive config not supported by NSPR. */ +#include "private/pprio.h" +#if defined(XP_WIN) +# include <winsock2.h> +# include <mstcpip.h> +#elif defined(XP_UNIX) +# include <errno.h> +# include <netinet/tcp.h> +#endif +/* End keepalive config inclusions. */ + +#define SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 0 +#define UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 1 +#define SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 2 +#define UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 3 + +//----------------------------------------------------------------------------- + +static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID); + +//----------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +class nsSocketEvent : public Runnable { + public: + nsSocketEvent(nsSocketTransport* transport, uint32_t type, + nsresult status = NS_OK, nsISupports* param = nullptr, + std::function<void()>&& task = nullptr) + : Runnable("net::nsSocketEvent"), + mTransport(transport), + mType(type), + mStatus(status), + mParam(param), + mTask(std::move(task)) {} + + NS_IMETHOD Run() override { + mTransport->OnSocketEvent(mType, mStatus, mParam, std::move(mTask)); + return NS_OK; + } + + private: + RefPtr<nsSocketTransport> mTransport; + + uint32_t mType; + nsresult mStatus; + nsCOMPtr<nsISupports> mParam; + std::function<void()> mTask; +}; + +//----------------------------------------------------------------------------- + +// #define TEST_CONNECT_ERRORS +#ifdef TEST_CONNECT_ERRORS +# include <stdlib.h> +static PRErrorCode RandomizeConnectError(PRErrorCode code) { + // + // To test out these errors, load http://www.yahoo.com/. It should load + // correctly despite the random occurrence of these errors. + // + int n = rand(); + if (n > RAND_MAX / 2) { + struct { + PRErrorCode err_code; + const char* err_name; + } errors[] = { + // + // These errors should be recoverable provided there is another + // IP address in mDNSRecord. + // + {PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR"}, + {PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR"}, + // + // This error will cause this socket transport to error out; + // however, if the consumer is HTTP, then the HTTP transaction + // should be restarted when this error occurs. + // + {PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR"}, + }; + n = n % (sizeof(errors) / sizeof(errors[0])); + code = errors[n].err_code; + SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name)); + } + return code; +} +#endif + +//----------------------------------------------------------------------------- + +nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) { + nsresult rv = NS_ERROR_FAILURE; + switch (errorCode) { + case PR_WOULD_BLOCK_ERROR: + rv = NS_BASE_STREAM_WOULD_BLOCK; + break; + case PR_CONNECT_ABORTED_ERROR: + case PR_CONNECT_RESET_ERROR: + rv = NS_ERROR_NET_RESET; + break; + case PR_END_OF_FILE_ERROR: // XXX document this correlation + rv = NS_ERROR_NET_INTERRUPT; + break; + case PR_CONNECT_REFUSED_ERROR: + // We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We + // could get better diagnostics by adding distinct XPCOM error codes for + // each of these, but there are a lot of places in Gecko that check + // specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to + // be checked. + case PR_NETWORK_UNREACHABLE_ERROR: + case PR_HOST_UNREACHABLE_ERROR: + case PR_ADDRESS_NOT_AVAILABLE_ERROR: + // Treat EACCES as a soft error since (at least on Linux) connect() returns + // EACCES when an IPv6 connection is blocked by a firewall. See bug 270784. + case PR_NO_ACCESS_RIGHTS_ERROR: + rv = NS_ERROR_CONNECTION_REFUSED; + break; + case PR_ADDRESS_NOT_SUPPORTED_ERROR: + rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; + break; + case PR_IO_TIMEOUT_ERROR: + case PR_CONNECT_TIMEOUT_ERROR: + rv = NS_ERROR_NET_TIMEOUT; + break; + case PR_OUT_OF_MEMORY_ERROR: + // These really indicate that the descriptor table filled up, or that the + // kernel ran out of network buffers - but nobody really cares which part of + // the system ran out of memory. + case PR_PROC_DESC_TABLE_FULL_ERROR: + case PR_SYS_DESC_TABLE_FULL_ERROR: + case PR_INSUFFICIENT_RESOURCES_ERROR: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + case PR_ADDRESS_IN_USE_ERROR: + rv = NS_ERROR_SOCKET_ADDRESS_IN_USE; + break; + // These filename-related errors can arise when using Unix-domain sockets. + case PR_FILE_NOT_FOUND_ERROR: + rv = NS_ERROR_FILE_NOT_FOUND; + break; + case PR_IS_DIRECTORY_ERROR: + rv = NS_ERROR_FILE_IS_DIRECTORY; + break; + case PR_LOOP_ERROR: + rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; + break; + case PR_NAME_TOO_LONG_ERROR: + rv = NS_ERROR_FILE_NAME_TOO_LONG; + break; + case PR_NO_DEVICE_SPACE_ERROR: + rv = NS_ERROR_FILE_NO_DEVICE_SPACE; + break; + case PR_NOT_DIRECTORY_ERROR: + rv = NS_ERROR_FILE_NOT_DIRECTORY; + break; + case PR_READ_ONLY_FILESYSTEM_ERROR: + rv = NS_ERROR_FILE_READ_ONLY; + break; + case PR_BAD_ADDRESS_ERROR: + rv = NS_ERROR_UNKNOWN_HOST; + break; + default: + if (psm::IsNSSErrorCode(errorCode)) { + rv = psm::GetXPCOMFromNSSError(errorCode); + } + break; + + // NSPR's socket code can return these, but they're not worth breaking out + // into their own error codes, distinct from NS_ERROR_FAILURE: + // + // PR_BAD_DESCRIPTOR_ERROR + // PR_INVALID_ARGUMENT_ERROR + // PR_NOT_SOCKET_ERROR + // PR_NOT_TCP_SOCKET_ERROR + // These would indicate a bug internal to the component. + // + // PR_PROTOCOL_NOT_SUPPORTED_ERROR + // This means that we can't use the given "protocol" (like + // IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As + // above, this indicates an internal bug. + // + // PR_IS_CONNECTED_ERROR + // This indicates that we've applied a system call like 'bind' or + // 'connect' to a socket that is already connected. The socket + // components manage each file descriptor's state, and in some cases + // handle this error result internally. We shouldn't be returning + // this to our callers. + // + // PR_IO_ERROR + // This is so vague that NS_ERROR_FAILURE is just as good. + } + SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%" PRIx32 "]\n", errorCode, + static_cast<uint32_t>(rv))); + return rv; +} + +//----------------------------------------------------------------------------- +// socket input stream impl +//----------------------------------------------------------------------------- + +nsSocketInputStream::nsSocketInputStream(nsSocketTransport* trans) + : mTransport(trans) {} + +// called on the socket transport thread... +// +// condition : failure code if socket has been closed +// +void nsSocketInputStream::OnSocketReady(nsresult condition) { + SOCKET_LOG(("nsSocketInputStream::OnSocketReady [this=%p cond=%" PRIx32 "]\n", + this, static_cast<uint32_t>(condition))); + + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + nsCOMPtr<nsIInputStreamCallback> callback; + { + MutexAutoLock lock(mTransport->mLock); + + // update condition, but be careful not to erase an already + // existing error condition. + if (NS_SUCCEEDED(mCondition)) mCondition = condition; + + // ignore event if only waiting for closure and not closed. + if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) { + callback = std::move(mCallback); + mCallbackFlags = 0; + } + } + + if (callback) callback->OnInputStreamReady(this); +} + +NS_IMPL_QUERY_INTERFACE(nsSocketInputStream, nsIInputStream, + nsIAsyncInputStream) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsSocketInputStream::AddRef() { + ++mReaderRefCnt; + return mTransport->AddRef(); +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsSocketInputStream::Release() { + if (--mReaderRefCnt == 0) Close(); + return mTransport->Release(); +} + +NS_IMETHODIMP +nsSocketInputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); } + +NS_IMETHODIMP +nsSocketInputStream::Available(uint64_t* avail) { + SOCKET_LOG(("nsSocketInputStream::Available [this=%p]\n", this)); + + *avail = 0; + + PRFileDesc* fd; + { + MutexAutoLock lock(mTransport->mLock); + + if (NS_FAILED(mCondition)) return mCondition; + + fd = mTransport->GetFD_Locked(); + if (!fd) return NS_OK; + } + + // cannot hold lock while calling NSPR. (worried about the fact that PSM + // synchronously proxies notifications over to the UI thread, which could + // mistakenly try to re-enter this code.) + int32_t n = PR_Available(fd); + + // PSM does not implement PR_Available() so do a best approximation of it + // with MSG_PEEK + if ((n == -1) && (PR_GetError() == PR_NOT_IMPLEMENTED_ERROR)) { + char c; + + n = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0); + SOCKET_LOG( + ("nsSocketInputStream::Available [this=%p] " + "using PEEK backup n=%d]\n", + this, n)); + } + + nsresult rv; + { + MutexAutoLock lock(mTransport->mLock); + + mTransport->ReleaseFD_Locked(fd); + + if (n >= 0) { + *avail = n; + } else { + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) return NS_OK; + mCondition = ErrorAccordingToNSPR(code); + } + rv = mCondition; + } + if (NS_FAILED(rv)) mTransport->OnInputClosed(rv); + return rv; +} + +NS_IMETHODIMP +nsSocketInputStream::StreamStatus() { + SOCKET_LOG(("nsSocketInputStream::StreamStatus [this=%p]\n", this)); + + MutexAutoLock lock(mTransport->mLock); + return mCondition; +} + +NS_IMETHODIMP +nsSocketInputStream::Read(char* buf, uint32_t count, uint32_t* countRead) { + SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n", this, count)); + + *countRead = 0; + + PRFileDesc* fd = nullptr; + { + MutexAutoLock lock(mTransport->mLock); + + if (NS_FAILED(mCondition)) { + return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition; + } + + fd = mTransport->GetFD_Locked(); + if (!fd) return NS_BASE_STREAM_WOULD_BLOCK; + } + + SOCKET_LOG((" calling PR_Read [count=%u]\n", count)); + + // cannot hold lock while calling NSPR. (worried about the fact that PSM + // synchronously proxies notifications over to the UI thread, which could + // mistakenly try to re-enter this code.) + int32_t n = PR_Read(fd, buf, count); + + SOCKET_LOG((" PR_Read returned [n=%d]\n", n)); + + nsresult rv = NS_OK; + { + MutexAutoLock lock(mTransport->mLock); + +#ifdef ENABLE_SOCKET_TRACING + if (n > 0) mTransport->TraceInBuf(buf, n); +#endif + + mTransport->ReleaseFD_Locked(fd); + + if (n > 0) { + mByteCount += (*countRead = n); + profiler_count_bandwidth_read_bytes(n); + } else if (n < 0) { + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK; + mCondition = ErrorAccordingToNSPR(code); + } + rv = mCondition; + } + if (NS_FAILED(rv)) mTransport->OnInputClosed(rv); + + // only send this notification if we have indeed read some data. + // see bug 196827 for an example of why this is important. + if (n > 0) mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM); + return rv; +} + +NS_IMETHODIMP +nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t count, uint32_t* countRead) { + // socket stream is unbuffered + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsSocketInputStream::IsNonBlocking(bool* nonblocking) { + *nonblocking = true; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketInputStream::CloseWithStatus(nsresult reason) { + SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%" PRIx32 + "]\n", + this, static_cast<uint32_t>(reason))); + + // may be called from any thread + + nsresult rv; + { + MutexAutoLock lock(mTransport->mLock); + + if (NS_SUCCEEDED(mCondition)) { + rv = mCondition = reason; + } else { + rv = NS_OK; + } + } + if (NS_FAILED(rv)) mTransport->OnInputClosed(rv); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketInputStream::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags, + uint32_t amount, nsIEventTarget* target) { + SOCKET_LOG(("nsSocketInputStream::AsyncWait [this=%p]\n", this)); + + bool hasError = false; + { + MutexAutoLock lock(mTransport->mLock); + + if (callback && target) { + // + // build event proxy + // + mCallback = NS_NewInputStreamReadyEvent("nsSocketInputStream::AsyncWait", + callback, target); + } else { + mCallback = callback; + } + mCallbackFlags = flags; + + hasError = NS_FAILED(mCondition); + } // unlock mTransport->mLock + + if (hasError) { + // OnSocketEvent will call OnInputStreamReady with an error code after + // going through the event loop. We do this because most socket callers + // do not expect AsyncWait() to synchronously execute the OnInputStreamReady + // callback. + mTransport->PostEvent(nsSocketTransport::MSG_INPUT_PENDING); + } else { + mTransport->OnInputPending(); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// socket output stream impl +//----------------------------------------------------------------------------- + +nsSocketOutputStream::nsSocketOutputStream(nsSocketTransport* trans) + : mTransport(trans) {} + +// called on the socket transport thread... +// +// condition : failure code if socket has been closed +// +void nsSocketOutputStream::OnSocketReady(nsresult condition) { + SOCKET_LOG(("nsSocketOutputStream::OnSocketReady [this=%p cond=%" PRIx32 + "]\n", + this, static_cast<uint32_t>(condition))); + + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + nsCOMPtr<nsIOutputStreamCallback> callback; + { + MutexAutoLock lock(mTransport->mLock); + + // update condition, but be careful not to erase an already + // existing error condition. + if (NS_SUCCEEDED(mCondition)) mCondition = condition; + + // ignore event if only waiting for closure and not closed. + if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) { + callback = std::move(mCallback); + mCallbackFlags = 0; + } + } + + if (callback) callback->OnOutputStreamReady(this); +} + +NS_IMPL_QUERY_INTERFACE(nsSocketOutputStream, nsIOutputStream, + nsIAsyncOutputStream) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsSocketOutputStream::AddRef() { + ++mWriterRefCnt; + return mTransport->AddRef(); +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsSocketOutputStream::Release() { + if (--mWriterRefCnt == 0) Close(); + return mTransport->Release(); +} + +NS_IMETHODIMP +nsSocketOutputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); } + +NS_IMETHODIMP +nsSocketOutputStream::Flush() { return NS_OK; } + +NS_IMETHODIMP +nsSocketOutputStream::StreamStatus() { + MutexAutoLock lock(mTransport->mLock); + return mCondition; +} + +NS_IMETHODIMP +nsSocketOutputStream::Write(const char* buf, uint32_t count, + uint32_t* countWritten) { + SOCKET_LOG(("nsSocketOutputStream::Write [this=%p count=%u]\n", this, count)); + + *countWritten = 0; + + // A write of 0 bytes can be used to force the initial SSL handshake, so do + // not reject that. + + PRFileDesc* fd = nullptr; + { + MutexAutoLock lock(mTransport->mLock); + + if (NS_FAILED(mCondition)) return mCondition; + + fd = mTransport->GetFD_Locked(); + if (!fd) return NS_BASE_STREAM_WOULD_BLOCK; + } + + SOCKET_LOG((" calling PR_Write [count=%u]\n", count)); + + // cannot hold lock while calling NSPR. (worried about the fact that PSM + // synchronously proxies notifications over to the UI thread, which could + // mistakenly try to re-enter this code.) + int32_t n = PR_Write(fd, buf, count); + + SOCKET_LOG((" PR_Write returned [n=%d]\n", n)); + + nsresult rv = NS_OK; + { + MutexAutoLock lock(mTransport->mLock); + +#ifdef ENABLE_SOCKET_TRACING + if (n > 0) mTransport->TraceOutBuf(buf, n); +#endif + + mTransport->ReleaseFD_Locked(fd); + + if (n > 0) { + mByteCount += (*countWritten = n); + profiler_count_bandwidth_written_bytes(n); + } else if (n < 0) { + PRErrorCode code = PR_GetError(); + if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK; + mCondition = ErrorAccordingToNSPR(code); + } + rv = mCondition; + } + if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv); + + // only send this notification if we have indeed written some data. + // see bug 196827 for an example of why this is important. + if ((n > 0)) { + mTransport->SendStatus(NS_NET_STATUS_SENDING_TO); + } + + return rv; +} + +NS_IMETHODIMP +nsSocketOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure, + uint32_t count, uint32_t* countRead) { + // socket stream is unbuffered + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsSocketOutputStream::WriteFromSegments( + nsIInputStream* input, void* closure, const char* fromSegment, + uint32_t offset, uint32_t count, uint32_t* countRead) { + nsSocketOutputStream* self = (nsSocketOutputStream*)closure; + return self->Write(fromSegment, count, countRead); +} + +NS_IMETHODIMP +nsSocketOutputStream::WriteFrom(nsIInputStream* stream, uint32_t count, + uint32_t* countRead) { + return stream->ReadSegments(WriteFromSegments, this, count, countRead); +} + +NS_IMETHODIMP +nsSocketOutputStream::IsNonBlocking(bool* nonblocking) { + *nonblocking = true; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketOutputStream::CloseWithStatus(nsresult reason) { + SOCKET_LOG(("nsSocketOutputStream::CloseWithStatus [this=%p reason=%" PRIx32 + "]\n", + this, static_cast<uint32_t>(reason))); + + // may be called from any thread + + nsresult rv; + { + MutexAutoLock lock(mTransport->mLock); + + if (NS_SUCCEEDED(mCondition)) { + rv = mCondition = reason; + } else { + rv = NS_OK; + } + } + if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketOutputStream::AsyncWait(nsIOutputStreamCallback* callback, + uint32_t flags, uint32_t amount, + nsIEventTarget* target) { + SOCKET_LOG(("nsSocketOutputStream::AsyncWait [this=%p]\n", this)); + + { + MutexAutoLock lock(mTransport->mLock); + + if (callback && target) { + // + // build event proxy + // + mCallback = NS_NewOutputStreamReadyEvent(callback, target); + } else { + mCallback = callback; + } + + mCallbackFlags = flags; + } + mTransport->OnOutputPending(); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// socket transport impl +//----------------------------------------------------------------------------- + +nsSocketTransport::nsSocketTransport() + : mFD(this), + mSocketTransportService(gSocketTransportService), + mInput(new nsSocketInputStream(this)), + mOutput(new nsSocketOutputStream(this)) { + SOCKET_LOG(("creating nsSocketTransport @%p\n", this)); + + mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX; // no timeout + mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout +} + +nsSocketTransport::~nsSocketTransport() { + MOZ_RELEASE_ASSERT(!mAttached); + SOCKET_LOG(("destroying nsSocketTransport @%p\n", this)); +} + +nsresult nsSocketTransport::Init(const nsTArray<nsCString>& types, + const nsACString& host, uint16_t port, + const nsACString& hostRoute, + uint16_t portRoute, + nsIProxyInfo* givenProxyInfo, + nsIDNSRecord* dnsRecord) { + nsCOMPtr<nsProxyInfo> proxyInfo; + if (givenProxyInfo) { + proxyInfo = do_QueryInterface(givenProxyInfo); + NS_ENSURE_ARG(proxyInfo); + } + + if (dnsRecord) { + mExternalDNSResolution = true; + mDNSRecord = do_QueryInterface(dnsRecord); + mDNSRecord->IsTRR(&mResolvedByTRR); + mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode); + mDNSRecord->GetTrrSkipReason(&mTRRSkipReason); + } + + // init socket type info + + mOriginHost = host; + mOriginPort = port; + if (!hostRoute.IsEmpty()) { + mHost = hostRoute; + mPort = portRoute; + } else { + mHost = host; + mPort = port; + } + + // A subtle check we don't enter this method more than once for the socket + // transport lifetime. Disable on TSan builds to prevent race checking, we + // don't want an atomic here for perf reasons! +#ifndef MOZ_TSAN + MOZ_ASSERT(!mPortRemappingApplied); +#endif // !MOZ_TSAN + + if (proxyInfo) { + mHttpsProxy = proxyInfo->IsHTTPS(); + } + + const char* proxyType = nullptr; + mProxyInfo = proxyInfo; + if (proxyInfo) { + mProxyPort = proxyInfo->Port(); + mProxyHost = proxyInfo->Host(); + // grab proxy type (looking for "socks" for example) + proxyType = proxyInfo->Type(); + if (proxyType && (proxyInfo->IsHTTP() || proxyInfo->IsHTTPS() || + proxyInfo->IsDirect() || !strcmp(proxyType, "unknown"))) { + proxyType = nullptr; + } + } + + SOCKET_LOG1( + ("nsSocketTransport::Init [this=%p host=%s:%hu origin=%s:%d " + "proxy=%s:%hu]\n", + this, mHost.get(), mPort, mOriginHost.get(), mOriginPort, + mProxyHost.get(), mProxyPort)); + + // include proxy type as a socket type if proxy type is not "http" + uint32_t typeCount = types.Length() + (proxyType != nullptr); + if (!typeCount) return NS_OK; + + // if we have socket types, then the socket provider service had + // better exist! + nsresult rv; + nsCOMPtr<nsISocketProviderService> spserv = + nsSocketProviderService::GetOrCreate(); + + if (!mTypes.SetCapacity(typeCount, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // now verify that each socket type has a registered socket provider. + for (uint32_t i = 0, type = 0; i < typeCount; ++i) { + // store socket types + if (i == 0 && proxyType) { + mTypes.AppendElement(proxyType); + } else { + mTypes.AppendElement(types[type++]); + } + + nsCOMPtr<nsISocketProvider> provider; + rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider)); + if (NS_FAILED(rv)) { + NS_WARNING("no registered socket provider"); + return rv; + } + + // note if socket type corresponds to a transparent proxy + // XXX don't hardcode SOCKS here (use proxy info's flags instead). + if (mTypes[i].EqualsLiteral("socks") || mTypes[i].EqualsLiteral("socks4")) { + mProxyTransparent = true; + + if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) { + // we want the SOCKS layer to send the hostname + // and port to the proxy and let it do the DNS. + mProxyTransparentResolvesHost = true; + } + } + } + + return NS_OK; +} + +#if defined(XP_UNIX) +nsresult nsSocketTransport::InitWithFilename(const char* filename) { + return InitWithName(filename, strlen(filename)); +} + +nsresult nsSocketTransport::InitWithName(const char* name, size_t length) { + if (length > sizeof(mNetAddr.local.path) - 1) { + return NS_ERROR_FILE_NAME_TOO_LONG; + } + + if (!name[0] && length > 1) { + // name is abstract address name that is supported on Linux only +# if defined(XP_LINUX) + mHost.Assign(name + 1, length - 1); +# else + return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; +# endif + } else { + // The name isn't abstract socket address. So this is Unix domain + // socket that has file path. + mHost.Assign(name, length); + } + mPort = 0; + + mNetAddr.local.family = AF_LOCAL; + memcpy(mNetAddr.local.path, name, length); + mNetAddr.local.path[length] = '\0'; + mNetAddrIsSet = true; + + return NS_OK; +} +#endif + +nsresult nsSocketTransport::InitWithConnectedSocket(PRFileDesc* fd, + const NetAddr* addr) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + char buf[kNetAddrMaxCStrBufSize]; + addr->ToStringBuffer(buf, sizeof(buf)); + mHost.Assign(buf); + + uint16_t port; + if (addr->raw.family == AF_INET) { + port = addr->inet.port; + } else if (addr->raw.family == AF_INET6) { + port = addr->inet6.port; + } else { + port = 0; + } + mPort = ntohs(port); + + memcpy(&mNetAddr, addr, sizeof(NetAddr)); + + mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT); + mState = STATE_TRANSFERRING; + SetSocketName(fd); + mNetAddrIsSet = true; + + { + MutexAutoLock lock(mLock); + NS_ASSERTION(!mFD.IsInitialized(), "already initialized"); + mFD = fd; + mFDref = 1; + mFDconnected = true; + mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; + } + + // make sure new socket is non-blocking + PRSocketOptionData opt; + opt.option = PR_SockOpt_Nonblocking; + opt.value.non_blocking = true; + PR_SetSocketOption(fd, &opt); + + SOCKET_LOG( + ("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n", + this, mHost.get(), mPort)); + + // jump to InitiateSocket to get ourselves attached to the STS poll list. + return PostEvent(MSG_RETRY_INIT_SOCKET); +} + +nsresult nsSocketTransport::InitWithConnectedSocket( + PRFileDesc* aFD, const NetAddr* aAddr, nsIInterfaceRequestor* aCallbacks) { + { + MutexAutoLock lock(mLock); + mCallbacks = aCallbacks; + } + return InitWithConnectedSocket(aFD, aAddr); +} + +nsresult nsSocketTransport::PostEvent(uint32_t type, nsresult status, + nsISupports* param, + std::function<void()>&& task) { + SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%" PRIx32 + " param=%p]\n", + this, type, static_cast<uint32_t>(status), param)); + + nsCOMPtr<nsIRunnable> event = + new nsSocketEvent(this, type, status, param, std::move(task)); + if (!event) return NS_ERROR_OUT_OF_MEMORY; + + return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL); +} + +void nsSocketTransport::SendStatus(nsresult status) { + SOCKET_LOG1(("nsSocketTransport::SendStatus [this=%p status=%" PRIx32 "]\n", + this, static_cast<uint32_t>(status))); + + nsCOMPtr<nsITransportEventSink> sink; + uint64_t progress; + { + MutexAutoLock lock(mLock); + sink = mEventSink; + switch (status) { + case NS_NET_STATUS_SENDING_TO: + progress = mOutput->ByteCount(lock); + break; + case NS_NET_STATUS_RECEIVING_FROM: + progress = mInput->ByteCount(lock); + break; + default: + progress = 0; + break; + } + } + if (sink) { + sink->OnTransportStatus(this, status, progress, -1); + } +} + +nsresult nsSocketTransport::ResolveHost() { + SOCKET_LOG(( + "nsSocketTransport::ResolveHost [this=%p %s:%d%s] " + "mProxyTransparentResolvesHost=%d\n", + this, SocketHost().get(), SocketPort(), + mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? " bypass cache" : "", + mProxyTransparentResolvesHost)); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + nsresult rv; + + if (!mProxyHost.IsEmpty()) { + if (!mProxyTransparent || mProxyTransparentResolvesHost) { +#if defined(XP_UNIX) + MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL, + "Unix domain sockets can't be used with proxies"); +#endif + // When not resolving mHost locally, we still want to ensure that + // it only contains valid characters. See bug 304904 for details. + // Sometimes the end host is not yet known and mHost is * + if (!net_IsValidHostName(mHost) && !mHost.EqualsLiteral("*")) { + SOCKET_LOG((" invalid hostname %s\n", mHost.get())); + return NS_ERROR_UNKNOWN_HOST; + } + } + if (mProxyTransparentResolvesHost) { + // Name resolution is done on the server side. Just pretend + // client resolution is complete, this will get picked up later. + // since we don't need to do DNS now, we bypass the resolving + // step by initializing mNetAddr to an empty address, but we + // must keep the port. The SOCKS IO layer will use the hostname + // we send it when it's created, rather than the empty address + // we send with the connect call. + mState = STATE_RESOLVING; + mNetAddr.raw.family = AF_INET; + mNetAddr.inet.port = htons(SocketPort()); + mNetAddr.inet.ip = htonl(INADDR_ANY); + return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr); + } + } + + if (mExternalDNSResolution) { + MOZ_ASSERT(mDNSRecord); + mState = STATE_RESOLVING; + return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr); + } + + nsCOMPtr<nsIDNSService> dns = nullptr; + auto initTask = [&dns]() { dns = do_GetService(kDNSServiceCID); }; + if (!NS_IsMainThread()) { + // Forward to the main thread synchronously. + RefPtr<nsIThread> mainThread = do_GetMainThread(); + if (!mainThread) { + return NS_ERROR_FAILURE; + } + + SyncRunnable::DispatchToThread( + mainThread, + NS_NewRunnableFunction("nsSocketTransport::ResolveHost->GetDNSService", + initTask)); + } else { + initTask(); + } + if (!dns) { + return NS_ERROR_FAILURE; + } + + mResolving = true; + + nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS; + if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE) { + dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE; + } + if (mConnectionFlags & nsSocketTransport::REFRESH_CACHE) { + dnsFlags = nsIDNSService::RESOLVE_REFRESH_CACHE; + } + if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6) { + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6; + } + if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) { + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4; + } + if (mConnectionFlags & nsSocketTransport::DISABLE_TRR) { + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR; + } + + if (mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) { + dnsFlags |= nsIDNSService::RESOLVE_IP_HINT; + } + + dnsFlags |= nsIDNSService::GetFlagsFromTRRMode( + nsISocketTransport::GetTRRModeFromFlags(mConnectionFlags)); + + // When we get here, we are not resolving using any configured proxy likely + // because of individual proxy setting on the request or because the host is + // excluded from proxying. Hence, force resolution despite global proxy-DNS + // configuration. + dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS; + + NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) || + !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4), + "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4"); + + SendStatus(NS_NET_STATUS_RESOLVING_HOST); + + if (!SocketHost().Equals(mOriginHost)) { + SOCKET_LOG(("nsSocketTransport %p origin %s doing dns for %s\n", this, + mOriginHost.get(), SocketHost().get())); + } + rv = + dns->AsyncResolveNative(SocketHost(), nsIDNSService::RESOLVE_TYPE_DEFAULT, + dnsFlags, nullptr, this, mSocketTransportService, + mOriginAttributes, getter_AddRefs(mDNSRequest)); + + if (NS_SUCCEEDED(rv)) { + SOCKET_LOG((" advancing to STATE_RESOLVING\n")); + mState = STATE_RESOLVING; + } + return rv; +} + +nsresult nsSocketTransport::BuildSocket(PRFileDesc*& fd, bool& proxyTransparent, + bool& usingSSL) { + SOCKET_LOG(("nsSocketTransport::BuildSocket [this=%p]\n", this)); + + nsresult rv = NS_OK; + + proxyTransparent = false; + usingSSL = false; + + if (mTypes.IsEmpty()) { + fd = PR_OpenTCPSocket(mNetAddr.raw.family); + if (!fd) { + SOCKET_LOG((" error creating TCP nspr socket [rv=%" PRIx32 "]\n", + static_cast<uint32_t>(rv))); + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; + } + +#if defined(XP_UNIX) + MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL, + "Unix domain sockets can't be used with socket types"); +#endif + + fd = nullptr; + + uint32_t controlFlags = 0; + if (mProxyTransparentResolvesHost) { + controlFlags |= nsISocketProvider::PROXY_RESOLVES_HOST; + } + + if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT) { + controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT; + } + + if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE) { + controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE; + } + + if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE) { + controlFlags |= nsISocketProvider::BE_CONSERVATIVE; + } + + if (mConnectionFlags & nsISocketTransport::DONT_TRY_ECH) { + controlFlags |= nsISocketProvider::DONT_TRY_ECH; + } + + if (mConnectionFlags & nsISocketTransport::IS_RETRY) { + controlFlags |= nsISocketProvider::IS_RETRY; + } + + if (mConnectionFlags & + nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) { + controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT; + } + + if (mConnectionFlags & nsISocketTransport::IS_SPECULATIVE_CONNECTION) { + controlFlags |= nsISocketProvider::IS_SPECULATIVE_CONNECTION; + } + + if (mResolvedByTRR) { + controlFlags |= nsISocketProvider::USED_PRIVATE_DNS; + } + + // by setting host to mOriginHost, instead of mHost we send the + // SocketProvider (e.g. PSM) the origin hostname but can still do DNS + // on an explicit alternate service host name + const char* host = mOriginHost.get(); + int32_t port = (int32_t)mOriginPort; + + nsCOMPtr<nsISocketProviderService> spserv = + nsSocketProviderService::GetOrCreate(); + nsCOMPtr<nsIProxyInfo> proxyInfo = mProxyInfo; + + uint32_t i; + for (i = 0; i < mTypes.Length(); ++i) { + nsCOMPtr<nsISocketProvider> provider; + + SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i].get())); + + rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider)); + if (NS_FAILED(rv)) break; + + nsCOMPtr<nsITLSSocketControl> tlsSocketControl; + if (i == 0) { + // if this is the first type, we'll want the + // service to allocate a new socket + + // Most layers _ESPECIALLY_ PSM want the origin name here as they + // will use it for secure checks, etc.. and any connection management + // differences between the origin name and the routed name can be + // taken care of via DNS. However, SOCKS is a special case as there is + // no DNS. in the case of SOCKS and PSM the PSM is a separate layer + // and receives the origin name. + const char* socketProviderHost = host; + int32_t socketProviderPort = port; + if (mProxyTransparentResolvesHost && + (mTypes[0].EqualsLiteral("socks") || + mTypes[0].EqualsLiteral("socks4"))) { + SOCKET_LOG(("SOCKS %d Host/Route override: %s:%d -> %s:%d\n", + mHttpsProxy, socketProviderHost, socketProviderPort, + mHost.get(), mPort)); + socketProviderHost = mHost.get(); + socketProviderPort = mPort; + } + + // when https proxying we want to just connect to the proxy as if + // it were the end host (i.e. expect the proxy's cert) + + rv = provider->NewSocket( + mNetAddr.raw.family, + mHttpsProxy ? mProxyHost.get() : socketProviderHost, + mHttpsProxy ? mProxyPort : socketProviderPort, proxyInfo, + mOriginAttributes, controlFlags, mTlsFlags, &fd, + getter_AddRefs(tlsSocketControl)); + + if (NS_SUCCEEDED(rv) && !fd) { + MOZ_ASSERT_UNREACHABLE( + "NewSocket succeeded but failed to " + "create a PRFileDesc"); + rv = NS_ERROR_UNEXPECTED; + } + } else { + // the socket has already been allocated, + // so we just want the service to add itself + // to the stack (such as pushing an io layer) + rv = provider->AddToSocket(mNetAddr.raw.family, host, port, proxyInfo, + mOriginAttributes, controlFlags, mTlsFlags, fd, + getter_AddRefs(tlsSocketControl)); + } + + // controlFlags = 0; not used below this point... + if (NS_FAILED(rv)) break; + + // if the service was ssl or starttls, we want to hold onto the socket + // info + bool isSSL = mTypes[i].EqualsLiteral("ssl"); + if (isSSL || mTypes[i].EqualsLiteral("starttls")) { + // remember security info + { + MutexAutoLock lock(mLock); + mTLSSocketControl = tlsSocketControl; + SOCKET_LOG((" [tlsSocketControl=%p callbacks=%p]\n", + mTLSSocketControl.get(), mCallbacks.get())); + } + // remember if socket type is SSL so we can ProxyStartSSL if need be. + usingSSL = isSSL; + } else if (mTypes[i].EqualsLiteral("socks") || + mTypes[i].EqualsLiteral("socks4")) { + // since socks is transparent, any layers above + // it do not have to worry about proxy stuff + proxyInfo = nullptr; + proxyTransparent = true; + } + } + + if (NS_FAILED(rv)) { + SOCKET_LOG((" error pushing io layer [%u:%s rv=%" PRIx32 "]\n", i, + mTypes[i].get(), static_cast<uint32_t>(rv))); + if (fd) { + CloseSocket( + fd, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()); + } + } + return rv; +} + +nsresult nsSocketTransport::InitiateSocket() { + SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this)); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + nsresult rv; + bool isLocal; + IsLocal(&isLocal); + + if (gIOService->IsNetTearingDown()) { + return NS_ERROR_ABORT; + } + if (gIOService->IsOffline()) { + if (StaticPrefs::network_disable_localhost_when_offline() || !isLocal) { + return NS_ERROR_OFFLINE; + } + } else if (!isLocal) { +#ifdef DEBUG + // all IP networking has to be done from the parent + if (NS_SUCCEEDED(mCondition) && ((mNetAddr.raw.family == AF_INET) || + (mNetAddr.raw.family == AF_INET6))) { + MOZ_ASSERT(!IsNeckoChild()); + } +#endif + + if (NS_SUCCEEDED(mCondition) && xpc::AreNonLocalConnectionsDisabled() && + !(mNetAddr.IsIPAddrAny() || mNetAddr.IsIPAddrLocal() || + mNetAddr.IsIPAddrShared())) { + nsAutoCString ipaddr; + RefPtr<nsNetAddr> netaddr = new nsNetAddr(&mNetAddr); + netaddr->GetAddress(ipaddr); + fprintf_stderr( + stderr, + "FATAL ERROR: Non-local network connections are disabled and a " + "connection " + "attempt to %s (%s) was made.\nYou should only access hostnames " + "available via the test networking proxy (if running mochitests) " + "or from a test-specific httpd.js server (if running xpcshell " + "tests). " + "Browser services should be disabled or redirected to a local " + "server.\n", + mHost.get(), ipaddr.get()); + return NS_ERROR_NON_LOCAL_CONNECTION_REFUSED; + } + } + + // Hosts/Proxy Hosts that are Local IP Literals should not be speculatively + // connected - Bug 853423. + if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 && + mNetAddr.IsIPAddrLocal()) { + if (SOCKET_LOG_ENABLED()) { + nsAutoCString netAddrCString; + netAddrCString.SetLength(kIPv6CStrBufSize); + if (!mNetAddr.ToStringBuffer(netAddrCString.BeginWriting(), + kIPv6CStrBufSize)) { + netAddrCString = "<IP-to-string failed>"_ns; + } + SOCKET_LOG( + ("nsSocketTransport::InitiateSocket skipping " + "speculative connection for host [%s:%d] proxy " + "[%s:%d] with Local IP address [%s]", + mHost.get(), mPort, mProxyHost.get(), mProxyPort, + netAddrCString.get())); + } + mCondition = NS_ERROR_CONNECTION_REFUSED; + OnSocketDetached(nullptr); + return mCondition; + } + + // + // find out if it is going to be ok to attach another socket to the STS. + // if not then we have to wait for the STS to tell us that it is ok. + // the notification is asynchronous, which means that when we could be + // in a race to call AttachSocket once notified. for this reason, when + // we get notified, we just re-enter this function. as a result, we are + // sure to ask again before calling AttachSocket. in this way we deal + // with the race condition. though it isn't the most elegant solution, + // it is far simpler than trying to build a system that would guarantee + // FIFO ordering (which wouldn't even be that valuable IMO). see bug + // 194402 for more info. + // + if (!mSocketTransportService->CanAttachSocket()) { + nsCOMPtr<nsIRunnable> event = + new nsSocketEvent(this, MSG_RETRY_INIT_SOCKET); + if (!event) return NS_ERROR_OUT_OF_MEMORY; + return mSocketTransportService->NotifyWhenCanAttachSocket(event); + } + + // + // if we already have a connected socket, then just attach and return. + // + { + MutexAutoLock lock(mLock); + if (mFD.IsInitialized()) { + rv = mSocketTransportService->AttachSocket(mFD, this); + if (NS_SUCCEEDED(rv)) mAttached = true; + return rv; + } + } + + // + // create new socket fd, push io layers, etc. + // + PRFileDesc* fd; + bool proxyTransparent; + bool usingSSL; + + rv = BuildSocket(fd, proxyTransparent, usingSSL); + if (NS_FAILED(rv)) { + SOCKET_LOG( + (" BuildSocket failed [rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv))); + return rv; + } + + // create proxy via IOActivityMonitor + IOActivityMonitor::MonitorSocket(fd); + +#ifdef FUZZING + if (StaticPrefs::fuzzing_necko_enabled()) { + rv = AttachFuzzyIOLayer(fd); + if (NS_FAILED(rv)) { + SOCKET_LOG(("Failed to attach fuzzing IOLayer [rv=%" PRIx32 "].\n", + static_cast<uint32_t>(rv))); + return rv; + } + SOCKET_LOG(("Successfully attached fuzzing IOLayer.\n")); + + if (usingSSL) { + mTLSSocketControl = new FuzzySocketControl(); + } + } +#endif + + PRStatus status; + + // Make the socket non-blocking... + PRSocketOptionData opt; + opt.option = PR_SockOpt_Nonblocking; + opt.value.non_blocking = true; + status = PR_SetSocketOption(fd, &opt); + NS_ASSERTION(status == PR_SUCCESS, "unable to make socket non-blocking"); + + if (mReuseAddrPort) { + SOCKET_LOG((" Setting port/addr reuse socket options\n")); + + // Set ReuseAddr for TCP sockets to enable having several + // sockets bound to same local IP and port + PRSocketOptionData opt_reuseaddr; + opt_reuseaddr.option = PR_SockOpt_Reuseaddr; + opt_reuseaddr.value.reuse_addr = PR_TRUE; + status = PR_SetSocketOption(fd, &opt_reuseaddr); + if (status != PR_SUCCESS) { + SOCKET_LOG((" Couldn't set reuse addr socket option: %d\n", status)); + } + + // And also set ReusePort for platforms supporting this socket option + PRSocketOptionData opt_reuseport; + opt_reuseport.option = PR_SockOpt_Reuseport; + opt_reuseport.value.reuse_port = PR_TRUE; + status = PR_SetSocketOption(fd, &opt_reuseport); + if (status != PR_SUCCESS && + PR_GetError() != PR_OPERATION_NOT_SUPPORTED_ERROR) { + SOCKET_LOG((" Couldn't set reuse port socket option: %d\n", status)); + } + } + + // disable the nagle algorithm - if we rely on it to coalesce writes into + // full packets the final packet of a multi segment POST/PUT or pipeline + // sequence is delayed a full rtt + opt.option = PR_SockOpt_NoDelay; + opt.value.no_delay = true; + PR_SetSocketOption(fd, &opt); + + // if the network.tcp.sendbuffer preference is set, use it to size SO_SNDBUF + // The Windows default of 8KB is too small and as of vista sp1, autotuning + // only applies to receive window + int32_t sndBufferSize; + mSocketTransportService->GetSendBufferSize(&sndBufferSize); + if (sndBufferSize > 0) { + opt.option = PR_SockOpt_SendBufferSize; + opt.value.send_buffer_size = sndBufferSize; + PR_SetSocketOption(fd, &opt); + } + + if (mQoSBits) { + opt.option = PR_SockOpt_IpTypeOfService; + opt.value.tos = mQoSBits; + PR_SetSocketOption(fd, &opt); + } + +#if defined(XP_WIN) + // The linger is turned off by default. This is not a hard close, but + // closesocket should return immediately and operating system tries to send + // remaining data for certain, implementation specific, amount of time. + // https://msdn.microsoft.com/en-us/library/ms739165.aspx + // + // Turn the linger option on an set the interval to 0. This will cause hard + // close of the socket. + opt.option = PR_SockOpt_Linger; + opt.value.linger.polarity = 1; + opt.value.linger.linger = 0; + PR_SetSocketOption(fd, &opt); +#endif + + // up to here, mFD will only be accessed by us + + // assign mFD so that we can properly handle OnSocketDetached before we've + // established a connection. + { + MutexAutoLock lock(mLock); + // inform socket transport about this newly created socket... + rv = mSocketTransportService->AttachSocket(fd, this); + if (NS_FAILED(rv)) { + CloseSocket( + fd, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()); + return rv; + } + mAttached = true; + + mFD = fd; + mFDref = 1; + mFDconnected = false; + mPollTimeout = mTimeouts[TIMEOUT_CONNECT]; + } + + SOCKET_LOG((" advancing to STATE_CONNECTING\n")); + mState = STATE_CONNECTING; + SendStatus(NS_NET_STATUS_CONNECTING_TO); + + if (SOCKET_LOG_ENABLED()) { + char buf[kNetAddrMaxCStrBufSize]; + mNetAddr.ToStringBuffer(buf, sizeof(buf)); + SOCKET_LOG((" trying address: %s\n", buf)); + } + + // + // Initiate the connect() to the host... + // + PRNetAddr prAddr; + memset(&prAddr, 0, sizeof(prAddr)); + { + if (mBindAddr) { + MutexAutoLock lock(mLock); + NetAddrToPRNetAddr(mBindAddr.get(), &prAddr); + status = PR_Bind(fd, &prAddr); + if (status != PR_SUCCESS) { + return NS_ERROR_FAILURE; + } + mBindAddr = nullptr; + } + } + + NetAddrToPRNetAddr(&mNetAddr, &prAddr); + +#ifdef XP_WIN + // Find the real tcp socket and set non-blocking once again! + // Bug 1158189. + PRFileDesc* bottom = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER); + if (bottom) { + PROsfd osfd = PR_FileDesc2NativeHandle(bottom); + u_long nonblocking = 1; + if (ioctlsocket(osfd, FIONBIO, &nonblocking) != 0) { + NS_WARNING("Socket could not be set non-blocking!"); + return NS_ERROR_FAILURE; + } + } +#endif + + if (mTLSSocketControl) { + if (!mEchConfig.IsEmpty() && + !(mConnectionFlags & (DONT_TRY_ECH | BE_CONSERVATIVE))) { + SOCKET_LOG(("nsSocketTransport::InitiateSocket set echconfig.")); + rv = mTLSSocketControl->SetEchConfig(mEchConfig); + if (NS_FAILED(rv)) { + return rv; + } + mEchConfigUsed = true; + } + } + + // We use PRIntervalTime here because we need + // nsIOService::LastOfflineStateChange time and + // nsIOService::LastConectivityChange time to be atomic. + PRIntervalTime connectStarted = 0; + if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + connectStarted = PR_IntervalNow(); + } + + if (Telemetry::CanRecordPrereleaseData() || + Telemetry::CanRecordReleaseData()) { + if (NS_FAILED(AttachNetworkDataCountLayer(fd))) { + SOCKET_LOG( + ("nsSocketTransport::InitiateSocket " + "AttachNetworkDataCountLayer failed [this=%p]\n", + this)); + } + } + + bool connectCalled = true; // This is only needed for telemetry. + status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT); + PRErrorCode code = PR_GetError(); + if (status == PR_SUCCESS) { + PR_SetFDInheritable(fd, false); + } + + if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() && + connectStarted && connectCalled) { + SendPRBlockingTelemetry( + connectStarted, Telemetry::PRCONNECT_BLOCKING_TIME_NORMAL, + Telemetry::PRCONNECT_BLOCKING_TIME_SHUTDOWN, + Telemetry::PRCONNECT_BLOCKING_TIME_CONNECTIVITY_CHANGE, + Telemetry::PRCONNECT_BLOCKING_TIME_LINK_CHANGE, + Telemetry::PRCONNECT_BLOCKING_TIME_OFFLINE); + } + + if (status == PR_SUCCESS) { + // + // we are connected! + // + OnSocketConnected(); + } else { +#if defined(TEST_CONNECT_ERRORS) + code = RandomizeConnectError(code); +#endif + // + // If the PR_Connect(...) would block, then poll for a connection. + // + if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) { + mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE); + // + // If the socket is already connected, then return success... + // + } else if (PR_IS_CONNECTED_ERROR == code) { + // + // we are connected! + // + OnSocketConnected(); + + if (mTLSSocketControl && !mProxyHost.IsEmpty() && proxyTransparent && + usingSSL) { + // if the connection phase is finished, and the ssl layer has + // been pushed, and we were proxying (transparently; ie. nothing + // has to happen in the protocol layer above us), it's time for + // the ssl to start doing it's thing. + SOCKET_LOG((" calling ProxyStartSSL()\n")); + mTLSSocketControl->ProxyStartSSL(); + // XXX what if we were forced to poll on the socket for a successful + // connection... wouldn't we need to call ProxyStartSSL after a call + // to PR_ConnectContinue indicates that we are connected? + // + // XXX this appears to be what the old socket transport did. why + // isn't this broken? + } + } + // + // A SOCKS request was rejected; get the actual error code from + // the OS error + // + else if (PR_UNKNOWN_ERROR == code && mProxyTransparent && + !mProxyHost.IsEmpty()) { + code = PR_GetOSError(); + rv = ErrorAccordingToNSPR(code); + } + // + // The connection was refused... + // + else { + if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() && + connectStarted && connectCalled) { + SendPRBlockingTelemetry( + connectStarted, Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_NORMAL, + Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_SHUTDOWN, + Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_CONNECTIVITY_CHANGE, + Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_LINK_CHANGE, + Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_OFFLINE); + } + + rv = ErrorAccordingToNSPR(code); + if ((rv == NS_ERROR_CONNECTION_REFUSED) && !mProxyHost.IsEmpty()) { + rv = NS_ERROR_PROXY_CONNECTION_REFUSED; + } + } + } + return rv; +} + +bool nsSocketTransport::RecoverFromError() { + NS_ASSERTION(NS_FAILED(mCondition), "there should be something wrong"); + + SOCKET_LOG( + ("nsSocketTransport::RecoverFromError [this=%p state=%x cond=%" PRIx32 + "]\n", + this, mState, static_cast<uint32_t>(mCondition))); + + if (mDoNotRetryToConnect) { + SOCKET_LOG( + ("nsSocketTransport::RecoverFromError do not retry because " + "mDoNotRetryToConnect is set [this=%p]\n", + this)); + return false; + } + +#if defined(XP_UNIX) + // Unix domain connections don't have multiple addresses to try, + // so the recovery techniques here don't apply. + if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) return false; +#endif + + if ((mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) && + mCondition == NS_ERROR_UNKNOWN_HOST && + (mState == MSG_DNS_LOOKUP_COMPLETE || mState == MSG_ENSURE_CONNECT)) { + SOCKET_LOG((" try again without USE_IP_HINT_ADDRESS")); + mConnectionFlags &= ~nsSocketTransport::USE_IP_HINT_ADDRESS; + mState = STATE_CLOSED; + return NS_SUCCEEDED(PostEvent(MSG_ENSURE_CONNECT, NS_OK)); + } + + // can only recover from errors in these states + if (mState != STATE_RESOLVING && mState != STATE_CONNECTING) { + SOCKET_LOG((" not in a recoverable state")); + return false; + } + + nsresult rv; + +#ifdef DEBUG + { + MutexAutoLock lock(mLock); + NS_ASSERTION(!mFDconnected, "socket should not be connected"); + } +#endif + + // all connection failures need to be reported to DNS so that the next + // time we will use a different address if available. + // NS_BASE_STREAM_CLOSED is not an actual connection failure, so don't report + // to DNS. + if (mState == STATE_CONNECTING && mDNSRecord && + mCondition != NS_BASE_STREAM_CLOSED) { + mDNSRecord->ReportUnusable(SocketPort()); + } + + if (mCondition != NS_ERROR_CONNECTION_REFUSED && + mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED && + mCondition != NS_ERROR_NET_TIMEOUT && + mCondition != NS_ERROR_UNKNOWN_HOST && + mCondition != NS_ERROR_UNKNOWN_PROXY_HOST) { + SOCKET_LOG((" not a recoverable error %" PRIx32, + static_cast<uint32_t>(mCondition))); + return false; + } + + bool tryAgain = false; + + if ((mState == STATE_CONNECTING) && mDNSRecord) { + if (mNetAddr.raw.family == AF_INET) { + if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY, + UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS); + } + } else if (mNetAddr.raw.family == AF_INET6) { + if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY, + UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS); + } + } + } + + if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY && + mCondition == NS_ERROR_UNKNOWN_HOST && mState == STATE_RESOLVING && + !mProxyTransparentResolvesHost) { + SOCKET_LOG((" trying lookup again with opposite ip family\n")); + mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4); + mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY; + // This will tell the consuming half-open to reset preference on the + // connection entry + mResetFamilyPreference = true; + tryAgain = true; + } + + // try next ip address only if past the resolver stage... + if (mState == STATE_CONNECTING && mDNSRecord) { + nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr); + mDNSRecord->IsTRR(&mResolvedByTRR); + mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode); + mDNSRecord->GetTrrSkipReason(&mTRRSkipReason); + if (NS_SUCCEEDED(rv)) { + SOCKET_LOG((" trying again with next ip address\n")); + tryAgain = true; + } else if (mExternalDNSResolution) { + mRetryDnsIfPossible = true; + bool trrEnabled; + mDNSRecord->IsTRR(&trrEnabled); + // Bug 1648147 - If the server responded with `0.0.0.0` or `::` then we + // should intentionally not fallback to regular DNS. + if (trrEnabled && !StaticPrefs::network_trr_fallback_on_zero_response() && + ((mNetAddr.raw.family == AF_INET && mNetAddr.inet.ip == 0) || + (mNetAddr.raw.family == AF_INET6 && mNetAddr.inet6.ip.u64[0] == 0 && + mNetAddr.inet6.ip.u64[1] == 0))) { + SOCKET_LOG((" TRR returned 0.0.0.0 and there are no other IPs")); + mRetryDnsIfPossible = false; + } + } else if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY) { + SOCKET_LOG((" failed to connect, trying with opposite ip family\n")); + // Drop state to closed. This will trigger new round of DNS + // resolving bellow. + mState = STATE_CLOSED; + mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4); + mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY; + // This will tell the consuming half-open to reset preference on the + // connection entry + mResetFamilyPreference = true; + tryAgain = true; + } else if (!(mConnectionFlags & DISABLE_TRR)) { + bool trrEnabled; + mDNSRecord->IsTRR(&trrEnabled); + + // Bug 1648147 - If the server responded with `0.0.0.0` or `::` then we + // should intentionally not fallback to regular DNS. + if (!StaticPrefs::network_trr_fallback_on_zero_response() && + ((mNetAddr.raw.family == AF_INET && mNetAddr.inet.ip == 0) || + (mNetAddr.raw.family == AF_INET6 && mNetAddr.inet6.ip.u64[0] == 0 && + mNetAddr.inet6.ip.u64[1] == 0))) { + SOCKET_LOG((" TRR returned 0.0.0.0 and there are no other IPs")); + } else if (trrEnabled) { + nsIRequest::TRRMode trrMode = nsIRequest::TRR_DEFAULT_MODE; + mDNSRecord->GetEffectiveTRRMode(&trrMode); + // If current trr mode is trr only, we should not retry. + if (trrMode != nsIRequest::TRR_ONLY_MODE) { + // Drop state to closed. This will trigger a new round of + // DNS resolving. Bypass the cache this time since the + // cached data came from TRR and failed already! + SOCKET_LOG((" failed to connect with TRR enabled, try w/o\n")); + mState = STATE_CLOSED; + mConnectionFlags |= DISABLE_TRR | BYPASS_CACHE | REFRESH_CACHE; + tryAgain = true; + } + } + } + } + + // prepare to try again. + if (tryAgain) { + uint32_t msg; + + if (mState == STATE_CONNECTING) { + mState = STATE_RESOLVING; + msg = MSG_DNS_LOOKUP_COMPLETE; + } else { + mState = STATE_CLOSED; + msg = MSG_ENSURE_CONNECT; + } + + rv = PostEvent(msg, NS_OK); + if (NS_FAILED(rv)) tryAgain = false; + } + + return tryAgain; +} + +// called on the socket thread only +void nsSocketTransport::OnMsgInputClosed(nsresult reason) { + SOCKET_LOG(("nsSocketTransport::OnMsgInputClosed [this=%p reason=%" PRIx32 + "]\n", + this, static_cast<uint32_t>(reason))); + + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + mInputClosed = true; + // check if event should affect entire transport + if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED)) { + mCondition = reason; // XXX except if NS_FAILED(mCondition), right?? + } else if (mOutputClosed) { + mCondition = + NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right?? + } else { + if (mState == STATE_TRANSFERRING) mPollFlags &= ~PR_POLL_READ; + mInput->OnSocketReady(reason); + } +} + +// called on the socket thread only +void nsSocketTransport::OnMsgOutputClosed(nsresult reason) { + SOCKET_LOG(("nsSocketTransport::OnMsgOutputClosed [this=%p reason=%" PRIx32 + "]\n", + this, static_cast<uint32_t>(reason))); + + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + mOutputClosed = true; + // check if event should affect entire transport + if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED)) { + mCondition = reason; // XXX except if NS_FAILED(mCondition), right?? + } else if (mInputClosed) { + mCondition = + NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right?? + } else { + if (mState == STATE_TRANSFERRING) mPollFlags &= ~PR_POLL_WRITE; + mOutput->OnSocketReady(reason); + } +} + +void nsSocketTransport::OnSocketConnected() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG((" advancing to STATE_TRANSFERRING\n")); + + mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT); + mState = STATE_TRANSFERRING; + + // Set the m*AddrIsSet flags only when state has reached TRANSFERRING + // because we need to make sure its value does not change due to failover + mNetAddrIsSet = true; + + // assign mFD (must do this within the transport lock), but take care not + // to trample over mFDref if mFD is already set. + { + MutexAutoLock lock(mLock); + NS_ASSERTION(mFD.IsInitialized(), "no socket"); + NS_ASSERTION(mFDref == 1, "wrong socket ref count"); + SetSocketName(mFD); + mFDconnected = true; + mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; + } + + // Ensure keepalive is configured correctly if previously enabled. + if (mKeepaliveEnabled) { + nsresult rv = SetKeepaliveEnabledInternal(true); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%" PRIx32 "]", + static_cast<uint32_t>(rv))); + } + } + + SendStatus(NS_NET_STATUS_CONNECTED_TO); +} + +void nsSocketTransport::SetSocketName(PRFileDesc* fd) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mSelfAddrIsSet) { + return; + } + + PRNetAddr prAddr; + memset(&prAddr, 0, sizeof(prAddr)); + if (PR_GetSockName(fd, &prAddr) == PR_SUCCESS) { + PRNetAddrToNetAddr(&prAddr, &mSelfAddr); + mSelfAddrIsSet = true; + } +} + +PRFileDesc* nsSocketTransport::GetFD_Locked() { + mLock.AssertCurrentThreadOwns(); + + // mFD is not available to the streams while disconnected. + if (!mFDconnected) return nullptr; + + if (mFD.IsInitialized()) mFDref++; + + return mFD; +} + +class ThunkPRClose : public Runnable { + public: + explicit ThunkPRClose(PRFileDesc* fd) + : Runnable("net::ThunkPRClose"), mFD(fd) {} + + NS_IMETHOD Run() override { + nsSocketTransport::CloseSocket( + mFD, gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()); + return NS_OK; + } + + private: + PRFileDesc* mFD; +}; + +void STS_PRCloseOnSocketTransport(PRFileDesc* fd, bool lingerPolarity, + int16_t lingerTimeout) { + if (gSocketTransportService) { + // Can't PR_Close() a socket off STS thread. Thunk it to STS to die + gSocketTransportService->Dispatch(new ThunkPRClose(fd), NS_DISPATCH_NORMAL); + } else { + // something horrible has happened + NS_ASSERTION(gSocketTransportService, "No STS service"); + } +} + +void nsSocketTransport::ReleaseFD_Locked(PRFileDesc* fd) { + mLock.AssertCurrentThreadOwns(); + + NS_ASSERTION(mFD == fd, "wrong fd"); + + if (--mFDref == 0) { + if (gIOService->IsNetTearingDown() && + ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) > + gSocketTransportService->MaxTimeForPrClosePref())) { + // If shutdown last to long, let the socket leak and do not close it. + SOCKET_LOG(("Intentional leak")); + } else { + if (mLingerPolarity || mLingerTimeout) { + PRSocketOptionData socket_linger; + socket_linger.option = PR_SockOpt_Linger; + socket_linger.value.linger.polarity = mLingerPolarity; + socket_linger.value.linger.linger = mLingerTimeout; + PR_SetSocketOption(mFD, &socket_linger); + } + if (OnSocketThread()) { + SOCKET_LOG(("nsSocketTransport: calling PR_Close [this=%p]\n", this)); + CloseSocket( + mFD, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()); + } else { + // Can't PR_Close() a socket off STS thread. Thunk it to STS to die + STS_PRCloseOnSocketTransport(mFD, mLingerPolarity, mLingerTimeout); + } + } + mFD = nullptr; + } +} + +//----------------------------------------------------------------------------- +// socket event handler impl + +void nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status, + nsISupports* param, + std::function<void()>&& task) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG( + ("nsSocketTransport::OnSocketEvent [this=%p type=%u status=%" PRIx32 + " param=%p]\n", + this, type, static_cast<uint32_t>(status), param)); + + if (NS_FAILED(mCondition)) { + // block event since we're apparently already dead. + SOCKET_LOG((" blocking event [condition=%" PRIx32 "]\n", + static_cast<uint32_t>(mCondition))); + // + // notify input/output streams in case either has a pending notify. + // + mInput->OnSocketReady(mCondition); + mOutput->OnSocketReady(mCondition); + return; + } + + switch (type) { + case MSG_ENSURE_CONNECT: + SOCKET_LOG((" MSG_ENSURE_CONNECT\n")); + if (task) { + task(); + } + + // Apply port remapping here so that we do it on the socket thread and + // before we process the resolved DNS name or create the socket the first + // time. + if (!mPortRemappingApplied) { + mPortRemappingApplied = true; + + mSocketTransportService->ApplyPortRemap(&mPort); + mSocketTransportService->ApplyPortRemap(&mOriginPort); + } + + // + // ensure that we have created a socket, attached it, and have a + // connection. + // + if (mState == STATE_CLOSED) { + // Unix domain sockets are ready to connect; mNetAddr is all we + // need. Internet address families require a DNS lookup (or possibly + // several) before we can connect. +#if defined(XP_UNIX) + if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) { + mCondition = InitiateSocket(); + } else { +#else + { +#endif + mCondition = ResolveHost(); + } + + } else { + SOCKET_LOG((" ignoring redundant event\n")); + } + break; + + case MSG_DNS_LOOKUP_COMPLETE: + if (mDNSRequest) { // only send this if we actually resolved anything + SendStatus(NS_NET_STATUS_RESOLVED_HOST); + } + + SOCKET_LOG((" MSG_DNS_LOOKUP_COMPLETE\n")); + mDNSRequest = nullptr; + + if (mDNSRecord) { + mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr); + mDNSRecord->IsTRR(&mResolvedByTRR); + mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode); + mDNSRecord->GetTrrSkipReason(&mTRRSkipReason); + } + // status contains DNS lookup status + if (NS_FAILED(status)) { + // When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP + // proxy host is not found, so we fixup the error code. + // For SOCKS proxies (mProxyTransparent == true), the socket + // transport resolves the real host here, so there's no fixup + // (see bug 226943). + if ((status == NS_ERROR_UNKNOWN_HOST) && !mProxyTransparent && + !mProxyHost.IsEmpty()) { + mCondition = NS_ERROR_UNKNOWN_PROXY_HOST; + } else { + mCondition = status; + } + } else if (mState == STATE_RESOLVING) { + mCondition = InitiateSocket(); + } + break; + + case MSG_RETRY_INIT_SOCKET: + mCondition = InitiateSocket(); + break; + + case MSG_INPUT_CLOSED: + SOCKET_LOG((" MSG_INPUT_CLOSED\n")); + OnMsgInputClosed(status); + break; + + case MSG_INPUT_PENDING: + SOCKET_LOG((" MSG_INPUT_PENDING\n")); + OnMsgInputPending(); + break; + + case MSG_OUTPUT_CLOSED: + SOCKET_LOG((" MSG_OUTPUT_CLOSED\n")); + OnMsgOutputClosed(status); + break; + + case MSG_OUTPUT_PENDING: + SOCKET_LOG((" MSG_OUTPUT_PENDING\n")); + OnMsgOutputPending(); + break; + case MSG_TIMEOUT_CHANGED: + SOCKET_LOG((" MSG_TIMEOUT_CHANGED\n")); + { + MutexAutoLock lock(mLock); + mPollTimeout = + mTimeouts[(mState == STATE_TRANSFERRING) ? TIMEOUT_READ_WRITE + : TIMEOUT_CONNECT]; + } + break; + default: + SOCKET_LOG((" unhandled event!\n")); + } + + if (NS_FAILED(mCondition)) { + SOCKET_LOG((" after event [this=%p cond=%" PRIx32 "]\n", this, + static_cast<uint32_t>(mCondition))); + if (!mAttached) { // need to process this error ourselves... + OnSocketDetached(nullptr); + } + } else if (mPollFlags == PR_POLL_EXCEPT) { + mPollFlags = 0; // make idle + } +} + +uint64_t nsSocketTransport::ByteCountReceived() { return mInput->ByteCount(); } + +uint64_t nsSocketTransport::ByteCountSent() { return mOutput->ByteCount(); } + +//----------------------------------------------------------------------------- +// socket handler impl + +void nsSocketTransport::OnSocketReady(PRFileDesc* fd, int16_t outFlags) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + SOCKET_LOG1(("nsSocketTransport::OnSocketReady [this=%p outFlags=%hd]\n", + this, outFlags)); + + if (outFlags == -1) { + SOCKET_LOG(("socket timeout expired\n")); + mCondition = NS_ERROR_NET_TIMEOUT; + return; + } + + if (mState == STATE_TRANSFERRING) { + // if waiting to write and socket is writable or hit an exception. + if ((mPollFlags & PR_POLL_WRITE) && (outFlags & ~PR_POLL_READ)) { + // assume that we won't need to poll any longer (the stream will + // request that we poll again if it is still pending). + mPollFlags &= ~PR_POLL_WRITE; + mOutput->OnSocketReady(NS_OK); + } + // if waiting to read and socket is readable or hit an exception. + if ((mPollFlags & PR_POLL_READ) && (outFlags & ~PR_POLL_WRITE)) { + // assume that we won't need to poll any longer (the stream will + // request that we poll again if it is still pending). + mPollFlags &= ~PR_POLL_READ; + mInput->OnSocketReady(NS_OK); + } + // Update poll timeout in case it was changed + { + MutexAutoLock lock(mLock); + mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; + } + } else if ((mState == STATE_CONNECTING) && !gIOService->IsNetTearingDown()) { + // We do not need to do PR_ConnectContinue when we are already + // shutting down. + + // We use PRIntervalTime here because we need + // nsIOService::LastOfflineStateChange time and + // nsIOService::LastConectivityChange time to be atomic. + PRIntervalTime connectStarted = 0; + if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + connectStarted = PR_IntervalNow(); + } + + PRStatus status = PR_ConnectContinue(fd, outFlags); + + if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() && + connectStarted) { + SendPRBlockingTelemetry( + connectStarted, Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_NORMAL, + Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_SHUTDOWN, + Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_CONNECTIVITY_CHANGE, + Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_LINK_CHANGE, + Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_OFFLINE); + } + + if (status == PR_SUCCESS) { + // + // we are connected! + // + OnSocketConnected(); + + if (mNetAddr.raw.family == AF_INET) { + if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY, + SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS); + } + } else if (mNetAddr.raw.family == AF_INET6) { + if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY, + SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS); + } + } + } else { + PRErrorCode code = PR_GetError(); +#if defined(TEST_CONNECT_ERRORS) + code = RandomizeConnectError(code); +#endif + // + // If the connect is still not ready, then continue polling... + // + if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) { + // Set up the select flags for connect... + mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE); + // Update poll timeout in case it was changed + { + MutexAutoLock lock(mLock); + mPollTimeout = mTimeouts[TIMEOUT_CONNECT]; + } + } + // + // The SOCKS proxy rejected our request. Find out why. + // + else if (PR_UNKNOWN_ERROR == code && mProxyTransparent && + !mProxyHost.IsEmpty()) { + code = PR_GetOSError(); + mCondition = ErrorAccordingToNSPR(code); + } else { + // + // else, the connection failed... + // + mCondition = ErrorAccordingToNSPR(code); + if ((mCondition == NS_ERROR_CONNECTION_REFUSED) && + !mProxyHost.IsEmpty()) { + mCondition = NS_ERROR_PROXY_CONNECTION_REFUSED; + } + SOCKET_LOG((" connection failed! [reason=%" PRIx32 "]\n", + static_cast<uint32_t>(mCondition))); + } + } + } else if ((mState == STATE_CONNECTING) && gIOService->IsNetTearingDown()) { + // We do not need to do PR_ConnectContinue when we are already + // shutting down. + SOCKET_LOG( + ("We are in shutdown so skip PR_ConnectContinue and set " + "and error.\n")); + mCondition = NS_ERROR_ABORT; + } else { + NS_ERROR("unexpected socket state"); + mCondition = NS_ERROR_UNEXPECTED; + } + + if (mPollFlags == PR_POLL_EXCEPT) mPollFlags = 0; // make idle +} + +// called on the socket thread only +void nsSocketTransport::OnSocketDetached(PRFileDesc* fd) { + SOCKET_LOG(("nsSocketTransport::OnSocketDetached [this=%p cond=%" PRIx32 + "]\n", + this, static_cast<uint32_t>(mCondition))); + + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + mAttached = false; + + // if we didn't initiate this detach, then be sure to pass an error + // condition up to our consumers. (e.g., STS is shutting down.) + if (NS_SUCCEEDED(mCondition)) { + if (gIOService->IsOffline()) { + mCondition = NS_ERROR_OFFLINE; + } else { + mCondition = NS_ERROR_ABORT; + } + } + + // If we are not shutting down try again. + if (!gIOService->IsNetTearingDown() && RecoverFromError()) { + mCondition = NS_OK; + } else { + mState = STATE_CLOSED; + + // make sure there isn't any pending DNS request + if (mDNSRequest) { + mDNSRequest->Cancel(NS_ERROR_ABORT); + mDNSRequest = nullptr; + } + + // + // notify input/output streams + // + mInput->OnSocketReady(mCondition); + mOutput->OnSocketReady(mCondition); + if (gIOService->IsNetTearingDown()) { + if (mInputCopyContext) { + NS_CancelAsyncCopy(mInputCopyContext, mCondition); + } + if (mOutputCopyContext) { + NS_CancelAsyncCopy(mOutputCopyContext, mCondition); + } + } + } + + if (mCondition == NS_ERROR_NET_RESET && mDNSRecord && + mOutput->ByteCount() == 0) { + // If we are here, it's likely that we are retrying a transaction. Blocking + // the already used address could increase the successful rate of the retry. + mDNSRecord->ReportUnusable(SocketPort()); + } + + // finally, release our reference to the socket (must do this within + // the transport lock) possibly closing the socket. Also release our + // listeners to break potential refcount cycles. + + // We should be careful not to release mEventSink and mCallbacks while + // we're locked, because releasing it might require acquiring the lock + // again, so we just null out mEventSink and mCallbacks while we're + // holding the lock, and let the stack based objects' destuctors take + // care of destroying it if needed. + nsCOMPtr<nsIInterfaceRequestor> ourCallbacks; + nsCOMPtr<nsITransportEventSink> ourEventSink; + { + MutexAutoLock lock(mLock); + if (mFD.IsInitialized()) { + ReleaseFD_Locked(mFD); + // flag mFD as unusable; this prevents other consumers from + // acquiring a reference to mFD. + mFDconnected = false; + } + + // We must release mCallbacks and mEventSink to avoid memory leak + // but only when RecoverFromError() above failed. Otherwise we lose + // link with UI and security callbacks on next connection attempt + // round. That would lead e.g. to a broken certificate exception page. + if (NS_FAILED(mCondition)) { + mCallbacks.swap(ourCallbacks); + mEventSink.swap(ourEventSink); + } + } +} + +void nsSocketTransport::IsLocal(bool* aIsLocal) { + { + MutexAutoLock lock(mLock); + +#if defined(XP_UNIX) + // Unix-domain sockets are always local. + if (mNetAddr.raw.family == PR_AF_LOCAL) { + *aIsLocal = true; + return; + } +#endif + + *aIsLocal = mNetAddr.IsLoopbackAddr(); + } +} + +//----------------------------------------------------------------------------- +// xpcom api + +NS_IMPL_ISUPPORTS(nsSocketTransport, nsISocketTransport, nsITransport, + nsIDNSListener, nsIClassInfo, nsIInterfaceRequestor) +NS_IMPL_CI_INTERFACE_GETTER(nsSocketTransport, nsISocketTransport, nsITransport, + nsIDNSListener, nsIInterfaceRequestor) + +NS_IMETHODIMP +nsSocketTransport::OpenInputStream(uint32_t flags, uint32_t segsize, + uint32_t segcount, + nsIInputStream** aResult) { + SOCKET_LOG( + ("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n", this, flags)); + + NS_ENSURE_TRUE(!mInput->IsReferenced(), NS_ERROR_UNEXPECTED); + + nsresult rv; + nsCOMPtr<nsIAsyncInputStream> pipeIn; + nsCOMPtr<nsIInputStream> result; + nsCOMPtr<nsISupports> inputCopyContext; + + if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) { + // XXX if the caller wants blocking, then the caller also gets buffered! + // bool openBuffered = !(flags & OPEN_UNBUFFERED); + bool openBlocking = (flags & OPEN_BLOCKING); + + net_ResolveSegmentParams(segsize, segcount); + + // create a pipe + nsCOMPtr<nsIAsyncOutputStream> pipeOut; + NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), !openBlocking, + true, segsize, segcount); + + // async copy from socket to pipe + rv = NS_AsyncCopy(mInput.get(), pipeOut, mSocketTransportService, + NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize, nullptr, nullptr, + true, true, getter_AddRefs(inputCopyContext)); + if (NS_FAILED(rv)) return rv; + + result = pipeIn; + } else { + result = mInput.get(); + } + + // flag input stream as open + mInputClosed = false; + // mInputCopyContext can be only touched on socket thread + auto task = [self = RefPtr{this}, inputCopyContext(inputCopyContext)]() { + MOZ_ASSERT(OnSocketThread()); + self->mInputCopyContext = inputCopyContext; + }; + rv = PostEvent(MSG_ENSURE_CONNECT, NS_OK, nullptr, std::move(task)); + if (NS_FAILED(rv)) { + return rv; + } + + result.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::OpenOutputStream(uint32_t flags, uint32_t segsize, + uint32_t segcount, + nsIOutputStream** aResult) { + SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n", this, + flags)); + + NS_ENSURE_TRUE(!mOutput->IsReferenced(), NS_ERROR_UNEXPECTED); + + nsresult rv; + nsCOMPtr<nsIAsyncOutputStream> pipeOut; + nsCOMPtr<nsIOutputStream> result; + nsCOMPtr<nsISupports> outputCopyContext; + if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) { + // XXX if the caller wants blocking, then the caller also gets buffered! + // bool openBuffered = !(flags & OPEN_UNBUFFERED); + bool openBlocking = (flags & OPEN_BLOCKING); + + net_ResolveSegmentParams(segsize, segcount); + + // create a pipe + nsCOMPtr<nsIAsyncInputStream> pipeIn; + NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, + !openBlocking, segsize, segcount); + + // async copy from socket to pipe + rv = NS_AsyncCopy(pipeIn, mOutput.get(), mSocketTransportService, + NS_ASYNCCOPY_VIA_READSEGMENTS, segsize, nullptr, nullptr, + true, true, getter_AddRefs(outputCopyContext)); + if (NS_FAILED(rv)) return rv; + + result = pipeOut; + } else { + result = mOutput.get(); + } + + // flag output stream as open + mOutputClosed = false; + + // mOutputCopyContext can be only touched on socket thread + auto task = [self = RefPtr{this}, outputCopyContext(outputCopyContext)]() { + MOZ_ASSERT(OnSocketThread()); + self->mOutputCopyContext = outputCopyContext; + }; + rv = PostEvent(MSG_ENSURE_CONNECT, NS_OK, nullptr, std::move(task)); + if (NS_FAILED(rv)) return rv; + + result.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::Close(nsresult reason) { + SOCKET_LOG(("nsSocketTransport::Close %p reason=%" PRIx32, this, + static_cast<uint32_t>(reason))); + + if (NS_SUCCEEDED(reason)) reason = NS_BASE_STREAM_CLOSED; + + mDoNotRetryToConnect = true; + + mInput->CloseWithStatus(reason); + mOutput->CloseWithStatus(reason); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetTlsSocketControl(nsITLSSocketControl** tlsSocketControl) { + MutexAutoLock lock(mLock); + *tlsSocketControl = do_AddRef(mTLSSocketControl).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor** callbacks) { + MutexAutoLock lock(mLock); + *callbacks = do_AddRef(mCallbacks).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor* callbacks) { + nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks; + NS_NewNotificationCallbacksAggregation(callbacks, nullptr, + GetCurrentSerialEventTarget(), + getter_AddRefs(threadsafeCallbacks)); + MutexAutoLock lock(mLock); + mCallbacks = threadsafeCallbacks; + SOCKET_LOG(("Reset callbacks for tlsSocketInfo=%p callbacks=%p\n", + mTLSSocketControl.get(), mCallbacks.get())); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetEventSink(nsITransportEventSink* sink, + nsIEventTarget* target) { + nsCOMPtr<nsITransportEventSink> temp; + if (target) { + nsresult rv = + net_NewTransportEventSinkProxy(getter_AddRefs(temp), sink, target); + if (NS_FAILED(rv)) return rv; + sink = temp.get(); + } + + MutexAutoLock lock(mLock); + mEventSink = sink; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::IsAlive(bool* result) { + *result = false; + + nsresult conditionWhileLocked = NS_OK; + PRFileDescAutoLock fd(this, &conditionWhileLocked); + if (NS_FAILED(conditionWhileLocked) || !fd.IsInitialized()) { + return NS_OK; + } + + // XXX do some idle-time based checks?? + + char c; + int32_t rval = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0); + + if ((rval > 0) || (rval < 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR)) { + *result = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetHost(nsACString& host) { + host = SocketHost(); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetPort(int32_t* port) { + *port = (int32_t)SocketPort(); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetScriptableOriginAttributes( + JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) { + if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetScriptableOriginAttributes( + JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) { + MutexAutoLock lock(mLock); + NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE); + + OriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + mOriginAttributes = attrs; + return NS_OK; +} + +nsresult nsSocketTransport::GetOriginAttributes( + OriginAttributes* aOriginAttributes) { + NS_ENSURE_ARG(aOriginAttributes); + *aOriginAttributes = mOriginAttributes; + return NS_OK; +} + +nsresult nsSocketTransport::SetOriginAttributes( + const OriginAttributes& aOriginAttributes) { + MutexAutoLock lock(mLock); + NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE); + + mOriginAttributes = aOriginAttributes; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetPeerAddr(NetAddr* addr) { + // once we are in the connected state, mNetAddr will not change. + // so if we can verify that we are in the connected state, then + // we can freely access mNetAddr from any thread without being + // inside a critical section. + + if (!mNetAddrIsSet) { + SOCKET_LOG( + ("nsSocketTransport::GetPeerAddr [this=%p state=%d] " + "NOT_AVAILABLE because not yet connected.", + this, mState)); + return NS_ERROR_NOT_AVAILABLE; + } + + memcpy(addr, &mNetAddr, sizeof(NetAddr)); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetSelfAddr(NetAddr* addr) { + // once we are in the connected state, mSelfAddr will not change. + // so if we can verify that we are in the connected state, then + // we can freely access mSelfAddr from any thread without being + // inside a critical section. + + if (!mSelfAddrIsSet) { + SOCKET_LOG( + ("nsSocketTransport::GetSelfAddr [this=%p state=%d] " + "NOT_AVAILABLE because not yet connected.", + this, mState)); + return NS_ERROR_NOT_AVAILABLE; + } + + memcpy(addr, &mSelfAddr, sizeof(NetAddr)); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::Bind(NetAddr* aLocalAddr) { + NS_ENSURE_ARG(aLocalAddr); + + MutexAutoLock lock(mLock); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mAttached) { + return NS_ERROR_FAILURE; + } + + mBindAddr = MakeUnique<NetAddr>(); + memcpy(mBindAddr.get(), aLocalAddr, sizeof(NetAddr)); + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetScriptablePeerAddr(nsINetAddr** addr) { + NetAddr rawAddr; + + nsresult rv; + rv = GetPeerAddr(&rawAddr); + if (NS_FAILED(rv)) return rv; + + RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr); + netaddr.forget(addr); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetScriptableSelfAddr(nsINetAddr** addr) { + NetAddr rawAddr; + + nsresult rv; + rv = GetSelfAddr(&rawAddr); + if (NS_FAILED(rv)) return rv; + + RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr); + netaddr.forget(addr); + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetTimeout(uint32_t type, uint32_t* value) { + NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE); + MutexAutoLock lock(mLock); + *value = (uint32_t)mTimeouts[type]; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetTimeout(uint32_t type, uint32_t value) { + NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE); + + SOCKET_LOG(("nsSocketTransport::SetTimeout %p type=%u, value=%u", this, type, + value)); + + // truncate overly large timeout values. + { + MutexAutoLock lock(mLock); + mTimeouts[type] = (uint16_t)std::min<uint32_t>(value, UINT16_MAX); + } + PostEvent(MSG_TIMEOUT_CHANGED); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetReuseAddrPort(bool reuseAddrPort) { + mReuseAddrPort = reuseAddrPort; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetLinger(bool aPolarity, int16_t aTimeout) { + MutexAutoLock lock(mLock); + + mLingerPolarity = aPolarity; + mLingerTimeout = aTimeout; + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetQoSBits(uint8_t aQoSBits) { + // Don't do any checking here of bits. Why? Because as of RFC-4594 + // several different Class Selector and Assured Forwarding values + // have been defined, but that isn't to say more won't be added later. + // In that case, any checking would be an impediment to interoperating + // with newer QoS definitions. + + mQoSBits = aQoSBits; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetQoSBits(uint8_t* aQoSBits) { + *aQoSBits = mQoSBits; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetRecvBufferSize(uint32_t* aSize) { + PRFileDescAutoLock fd(this); + if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED; + + nsresult rv = NS_OK; + PRSocketOptionData opt; + opt.option = PR_SockOpt_RecvBufferSize; + if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) { + *aSize = opt.value.recv_buffer_size; + } else { + rv = NS_ERROR_FAILURE; + } + + return rv; +} + +NS_IMETHODIMP +nsSocketTransport::GetSendBufferSize(uint32_t* aSize) { + PRFileDescAutoLock fd(this); + if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED; + + nsresult rv = NS_OK; + PRSocketOptionData opt; + opt.option = PR_SockOpt_SendBufferSize; + if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) { + *aSize = opt.value.send_buffer_size; + } else { + rv = NS_ERROR_FAILURE; + } + + return rv; +} + +NS_IMETHODIMP +nsSocketTransport::SetRecvBufferSize(uint32_t aSize) { + PRFileDescAutoLock fd(this); + if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED; + + nsresult rv = NS_OK; + PRSocketOptionData opt; + opt.option = PR_SockOpt_RecvBufferSize; + opt.value.recv_buffer_size = aSize; + if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE; + + return rv; +} + +NS_IMETHODIMP +nsSocketTransport::SetSendBufferSize(uint32_t aSize) { + PRFileDescAutoLock fd(this); + if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED; + + nsresult rv = NS_OK; + PRSocketOptionData opt; + opt.option = PR_SockOpt_SendBufferSize; + opt.value.send_buffer_size = aSize; + if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE; + + return rv; +} + +NS_IMETHODIMP +nsSocketTransport::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec, + nsresult status) { + SOCKET_LOG(("nsSocketTransport::OnLookupComplete: this=%p status %" PRIx32 + ".", + this, static_cast<uint32_t>(status))); + + if (NS_SUCCEEDED(status)) { + mDNSRecord = do_QueryInterface(rec); + MOZ_ASSERT(mDNSRecord); + } + + if (nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface(rec)) { + addrRecord->IsTRR(&mResolvedByTRR); + addrRecord->GetEffectiveTRRMode(&mEffectiveTRRMode); + addrRecord->GetTrrSkipReason(&mTRRSkipReason); + } + + // flag host lookup complete for the benefit of the ResolveHost method. + mResolving = false; + nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, nullptr); + + // if posting a message fails, then we should assume that the socket + // transport has been shutdown. this should never happen! if it does + // it means that the socket transport service was shutdown before the + // DNS service. + if (NS_FAILED(rv)) { + NS_WARNING("unable to post DNS lookup complete message"); + } + + return NS_OK; +} + +// nsIInterfaceRequestor +NS_IMETHODIMP +nsSocketTransport::GetInterface(const nsIID& iid, void** result) { + if (iid.Equals(NS_GET_IID(nsIDNSRecord)) || + iid.Equals(NS_GET_IID(nsIDNSAddrRecord))) { + return mDNSRecord ? mDNSRecord->QueryInterface(iid, result) + : NS_ERROR_NO_INTERFACE; + } + return this->QueryInterface(iid, result); +} + +NS_IMETHODIMP +nsSocketTransport::GetInterfaces(nsTArray<nsIID>& array) { + return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(array); +} + +NS_IMETHODIMP +nsSocketTransport::GetScriptableHelper(nsIXPCScriptable** _retval) { + *_retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetContractID(nsACString& aContractID) { + aContractID.SetIsVoid(true); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetClassDescription(nsACString& aClassDescription) { + aClassDescription.SetIsVoid(true); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetClassID(nsCID** aClassID) { + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetFlags(uint32_t* aFlags) { + *aFlags = nsIClassInfo::THREADSAFE; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsSocketTransport::GetConnectionFlags(uint32_t* value) { + *value = mConnectionFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetConnectionFlags(uint32_t value) { + SOCKET_LOG( + ("nsSocketTransport::SetConnectionFlags %p flags=%u", this, value)); + + mConnectionFlags = value; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetIsPrivate(bool aIsPrivate) { + mIsPrivate = aIsPrivate; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetTlsFlags(uint32_t* value) { + *value = mTlsFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetTlsFlags(uint32_t value) { + mTlsFlags = value; + return NS_OK; +} + +void nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + // The global pref toggles keepalive as a system feature; it only affects + // an individual socket if keepalive has been specifically enabled for it. + // So, ensure keepalive is configured correctly if previously enabled. + if (mKeepaliveEnabled) { + nsresult rv = SetKeepaliveEnabledInternal(aEnabled); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabledInternal [%s] failed rv[0x%" PRIx32 "]", + aEnabled ? "enable" : "disable", static_cast<uint32_t>(rv))); + } + } +} + +nsresult nsSocketTransport::SetKeepaliveEnabledInternal(bool aEnable) { + MOZ_ASSERT(mKeepaliveIdleTimeS > 0 && mKeepaliveIdleTimeS <= kMaxTCPKeepIdle); + MOZ_ASSERT(mKeepaliveRetryIntervalS > 0 && + mKeepaliveRetryIntervalS <= kMaxTCPKeepIntvl); + MOZ_ASSERT(mKeepaliveProbeCount > 0 && + mKeepaliveProbeCount <= kMaxTCPKeepCount); + + PRFileDescAutoLock fd(this); + if (NS_WARN_IF(!fd.IsInitialized())) { + return NS_ERROR_NOT_INITIALIZED; + } + + // Only enable if keepalives are globally enabled, but ensure other + // options are set correctly on the fd. + bool enable = aEnable && mSocketTransportService->IsKeepaliveEnabled(); + nsresult rv = + fd.SetKeepaliveVals(enable, mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS, + mKeepaliveProbeCount); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveVals failed rv[0x%" PRIx32 "]", + static_cast<uint32_t>(rv))); + return rv; + } + rv = fd.SetKeepaliveEnabled(enable); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabled failed rv[0x%" PRIx32 "]", + static_cast<uint32_t>(rv))); + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetKeepaliveEnabled(bool* aResult) { + MOZ_ASSERT(aResult); + + *aResult = mKeepaliveEnabled; + return NS_OK; +} + +nsresult nsSocketTransport::EnsureKeepaliveValsAreInitialized() { + nsresult rv = NS_OK; + int32_t val = -1; + if (mKeepaliveIdleTimeS == -1) { + rv = mSocketTransportService->GetKeepaliveIdleTime(&val); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + mKeepaliveIdleTimeS = val; + } + if (mKeepaliveRetryIntervalS == -1) { + rv = mSocketTransportService->GetKeepaliveRetryInterval(&val); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + mKeepaliveRetryIntervalS = val; + } + if (mKeepaliveProbeCount == -1) { + rv = mSocketTransportService->GetKeepaliveProbeCount(&val); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + mKeepaliveProbeCount = val; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetKeepaliveEnabled(bool aEnable) { +#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX) + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (aEnable == mKeepaliveEnabled) { + SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] already %s.", this, + aEnable ? "enabled" : "disabled")); + return NS_OK; + } + + nsresult rv = NS_OK; + if (aEnable) { + rv = EnsureKeepaliveValsAreInitialized(); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG( + (" SetKeepaliveEnabled [%p] " + "error [0x%" PRIx32 "] initializing keepalive vals", + this, static_cast<uint32_t>(rv))); + return rv; + } + } + SOCKET_LOG( + ("nsSocketTransport::SetKeepaliveEnabled [%p] " + "%s, idle time[%ds] retry interval[%ds] packet count[%d]: " + "globally %s.", + this, aEnable ? "enabled" : "disabled", mKeepaliveIdleTimeS, + mKeepaliveRetryIntervalS, mKeepaliveProbeCount, + mSocketTransportService->IsKeepaliveEnabled() ? "enabled" : "disabled")); + + // Set mKeepaliveEnabled here so that state is maintained; it is possible + // that we're in between fds, e.g. the 1st IP address failed, so we're about + // to retry on a 2nd from the DNS record. + mKeepaliveEnabled = aEnable; + + rv = SetKeepaliveEnabledInternal(aEnable); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%" PRIx32 "]", + static_cast<uint32_t>(rv))); + return rv; + } + + return NS_OK; +#else /* !(defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)) */ + SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled unsupported platform")); + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +nsSocketTransport::SetKeepaliveVals(int32_t aIdleTime, int32_t aRetryInterval) { +#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX) + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) { + return NS_ERROR_INVALID_ARG; + } + + if (aIdleTime == mKeepaliveIdleTimeS && + aRetryInterval == mKeepaliveRetryIntervalS) { + SOCKET_LOG( + ("nsSocketTransport::SetKeepaliveVals [%p] idle time " + "already %ds and retry interval already %ds.", + this, mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS)); + return NS_OK; + } + mKeepaliveIdleTimeS = aIdleTime; + mKeepaliveRetryIntervalS = aRetryInterval; + + nsresult rv = NS_OK; + if (mKeepaliveProbeCount == -1) { + int32_t val = -1; + nsresult rv = mSocketTransportService->GetKeepaliveProbeCount(&val); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + mKeepaliveProbeCount = val; + } + + SOCKET_LOG( + ("nsSocketTransport::SetKeepaliveVals [%p] " + "keepalive %s, idle time[%ds] retry interval[%ds] " + "packet count[%d]", + this, mKeepaliveEnabled ? "enabled" : "disabled", mKeepaliveIdleTimeS, + mKeepaliveRetryIntervalS, mKeepaliveProbeCount)); + + PRFileDescAutoLock fd(this); + if (NS_WARN_IF(!fd.IsInitialized())) { + return NS_ERROR_NULL_POINTER; + } + + rv = fd.SetKeepaliveVals(mKeepaliveEnabled, mKeepaliveIdleTimeS, + mKeepaliveRetryIntervalS, mKeepaliveProbeCount); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +#else + SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals unsupported platform")); + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +#ifdef ENABLE_SOCKET_TRACING + +# include <stdio.h> +# include <ctype.h> +# include "prenv.h" + +static void DumpBytesToFile(const char* path, const char* header, + const char* buf, int32_t n) { + FILE* fp = fopen(path, "a"); + + fprintf(fp, "\n%s [%d bytes]\n", header, n); + + const unsigned char* p; + while (n) { + p = (const unsigned char*)buf; + + int32_t i, row_max = std::min(16, n); + + for (i = 0; i < row_max; ++i) fprintf(fp, "%02x ", *p++); + for (i = row_max; i < 16; ++i) fprintf(fp, " "); + + p = (const unsigned char*)buf; + for (i = 0; i < row_max; ++i, ++p) { + if (isprint(*p)) + fprintf(fp, "%c", *p); + else + fprintf(fp, "."); + } + + fprintf(fp, "\n"); + buf += row_max; + n -= row_max; + } + + fprintf(fp, "\n"); + fclose(fp); +} + +void nsSocketTransport::TraceInBuf(const char* buf, int32_t n) { + char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG"); + if (!val || !*val) return; + + nsAutoCString header; + header.AssignLiteral("Reading from: "); + header.Append(mHost); + header.Append(':'); + header.AppendInt(mPort); + + DumpBytesToFile(val, header.get(), buf, n); +} + +void nsSocketTransport::TraceOutBuf(const char* buf, int32_t n) { + char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG"); + if (!val || !*val) return; + + nsAutoCString header; + header.AssignLiteral("Writing to: "); + header.Append(mHost); + header.Append(':'); + header.AppendInt(mPort); + + DumpBytesToFile(val, header.get(), buf, n); +} + +#endif + +static void LogNSPRError(const char* aPrefix, const void* aObjPtr) { +#if defined(DEBUG) + PRErrorCode errCode = PR_GetError(); + int errLen = PR_GetErrorTextLength(); + nsAutoCString errStr; + if (errLen > 0) { + errStr.SetLength(errLen); + PR_GetErrorText(errStr.BeginWriting()); + } + NS_WARNING( + nsPrintfCString("%s [%p] NSPR error[0x%x] %s.", + aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode, + errLen > 0 ? errStr.BeginReading() : "<no error text>") + .get()); +#endif +} + +nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled( + bool aEnable) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(!(aEnable && !gSocketTransportService->IsKeepaliveEnabled()), + "Cannot enable keepalive if global pref is disabled!"); + if (aEnable && !gSocketTransportService->IsKeepaliveEnabled()) { + return NS_ERROR_ILLEGAL_VALUE; + } + + PRSocketOptionData opt; + + opt.option = PR_SockOpt_Keepalive; + opt.value.keep_alive = aEnable; + PRStatus status = PR_SetSocketOption(mFd, &opt); + if (NS_WARN_IF(status != PR_SUCCESS)) { + LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled", + mSocketTransport); + return ErrorAccordingToNSPR(PR_GetError()); + } + return NS_OK; +} + +static void LogOSError(const char* aPrefix, const void* aObjPtr) { +#if defined(DEBUG) + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + +# ifdef XP_WIN + DWORD errCode = WSAGetLastError(); + char* errMessage; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&errMessage, 0, NULL); + NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%lx] %s", + aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, + errCode, + errMessage ? errMessage : "<no error text>") + .get()); + LocalFree(errMessage); +# else + int errCode = errno; + char* errMessage = strerror(errno); + NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%x] %s", + aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, + errCode, + errMessage ? errMessage : "<no error text>") + .get()); +# endif +#endif +} + +/* XXX PR_SetSockOpt does not support setting keepalive values, so native + * handles and platform specific apis (setsockopt, WSAIOCtl) are used in this + * file. Requires inclusion of NSPR private/pprio.h, and platform headers. + */ + +nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals( + bool aEnabled, int aIdleTime, int aRetryInterval, int aProbeCount) { +#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX) + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aProbeCount <= 0 || kMaxTCPKeepCount < aProbeCount)) { + return NS_ERROR_INVALID_ARG; + } + + PROsfd sock = PR_FileDesc2NativeHandle(mFd); + if (NS_WARN_IF(sock == -1)) { + LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals", + mSocketTransport); + return ErrorAccordingToNSPR(PR_GetError()); + } +#endif + +#if defined(XP_WIN) + // Windows allows idle time and retry interval to be set; NOT ping count. + struct tcp_keepalive keepalive_vals = {(u_long)aEnabled, + // Windows uses msec. + (u_long)(aIdleTime * 1000UL), + (u_long)(aRetryInterval * 1000UL)}; + DWORD bytes_returned; + int err = + WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive_vals, + sizeof(keepalive_vals), NULL, 0, &bytes_returned, NULL, NULL); + if (NS_WARN_IF(err)) { + LogOSError("nsSocketTransport WSAIoctl failed", mSocketTransport); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; + +#elif defined(XP_DARWIN) + // Darwin uses sec; only supports idle time being set. + int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &aIdleTime, + sizeof(aIdleTime)); + if (NS_WARN_IF(err)) { + LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE", + mSocketTransport); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; + +#elif defined(XP_UNIX) + // Not all *nix OSes support the following setsockopt() options + // ... but we assume they are supported in the Android kernel; + // build errors will tell us if they are not. +# if defined(ANDROID) || defined(TCP_KEEPIDLE) + // Idle time until first keepalive probe; interval between ack'd probes; + // seconds. + int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &aIdleTime, + sizeof(aIdleTime)); + if (NS_WARN_IF(err)) { + LogOSError("nsSocketTransport Failed setting TCP_KEEPIDLE", + mSocketTransport); + return NS_ERROR_UNEXPECTED; + } + +# endif +# if defined(ANDROID) || defined(TCP_KEEPINTVL) + // Interval between unack'd keepalive probes; seconds. + err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &aRetryInterval, + sizeof(aRetryInterval)); + if (NS_WARN_IF(err)) { + LogOSError("nsSocketTransport Failed setting TCP_KEEPINTVL", + mSocketTransport); + return NS_ERROR_UNEXPECTED; + } + +# endif +# if defined(ANDROID) || defined(TCP_KEEPCNT) + // Number of unack'd keepalive probes before connection times out. + err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &aProbeCount, + sizeof(aProbeCount)); + if (NS_WARN_IF(err)) { + LogOSError("nsSocketTransport Failed setting TCP_KEEPCNT", + mSocketTransport); + return NS_ERROR_UNEXPECTED; + } + +# endif + return NS_OK; +#else + MOZ_ASSERT(false, + "nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals " + "called on unsupported platform!"); + return NS_ERROR_UNEXPECTED; +#endif +} + +void nsSocketTransport::CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled) { +#if defined(XP_WIN) + AttachShutdownLayer(aFd); +#endif + + // We use PRIntervalTime here because we need + // nsIOService::LastOfflineStateChange time and + // nsIOService::LastConectivityChange time to be atomic. + PRIntervalTime closeStarted; + if (aTelemetryEnabled) { + closeStarted = PR_IntervalNow(); + } + + PR_Close(aFd); + + if (aTelemetryEnabled) { + SendPRBlockingTelemetry( + closeStarted, Telemetry::PRCLOSE_TCP_BLOCKING_TIME_NORMAL, + Telemetry::PRCLOSE_TCP_BLOCKING_TIME_SHUTDOWN, + Telemetry::PRCLOSE_TCP_BLOCKING_TIME_CONNECTIVITY_CHANGE, + Telemetry::PRCLOSE_TCP_BLOCKING_TIME_LINK_CHANGE, + Telemetry::PRCLOSE_TCP_BLOCKING_TIME_OFFLINE); + } +} + +void nsSocketTransport::SendPRBlockingTelemetry( + PRIntervalTime aStart, Telemetry::HistogramID aIDNormal, + Telemetry::HistogramID aIDShutdown, + Telemetry::HistogramID aIDConnectivityChange, + Telemetry::HistogramID aIDLinkChange, Telemetry::HistogramID aIDOffline) { + PRIntervalTime now = PR_IntervalNow(); + if (gIOService->IsNetTearingDown()) { + Telemetry::Accumulate(aIDShutdown, PR_IntervalToMilliseconds(now - aStart)); + + } else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange()) < + 60) { + Telemetry::Accumulate(aIDConnectivityChange, + PR_IntervalToMilliseconds(now - aStart)); + } else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange()) < + 60) { + Telemetry::Accumulate(aIDLinkChange, + PR_IntervalToMilliseconds(now - aStart)); + + } else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange()) < + 60) { + Telemetry::Accumulate(aIDOffline, PR_IntervalToMilliseconds(now - aStart)); + } else { + Telemetry::Accumulate(aIDNormal, PR_IntervalToMilliseconds(now - aStart)); + } +} + +NS_IMETHODIMP +nsSocketTransport::GetResetIPFamilyPreference(bool* aReset) { + *aReset = mResetFamilyPreference; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetEchConfigUsed(bool* aEchConfigUsed) { + *aEchConfigUsed = mEchConfigUsed; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetEchConfig(const nsACString& aEchConfig) { + mEchConfig = aEchConfig; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::ResolvedByTRR(bool* aResolvedByTRR) { + *aResolvedByTRR = mResolvedByTRR; + return NS_OK; +} + +NS_IMETHODIMP nsSocketTransport::GetEffectiveTRRMode( + nsIRequest::TRRMode* aEffectiveTRRMode) { + *aEffectiveTRRMode = mEffectiveTRRMode; + return NS_OK; +} + +NS_IMETHODIMP nsSocketTransport::GetTrrSkipReason( + nsITRRSkipReason::value* aSkipReason) { + *aSkipReason = mTRRSkipReason; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetRetryDnsIfPossible(bool* aRetryDns) { + *aRetryDns = mRetryDnsIfPossible; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetStatus(nsresult* aStatus) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + *aStatus = mCondition; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsSocketTransport2.h b/netwerk/base/nsSocketTransport2.h new file mode 100644 index 0000000000..7dd94abac7 --- /dev/null +++ b/netwerk/base/nsSocketTransport2.h @@ -0,0 +1,510 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSocketTransport2_h__ +#define nsSocketTransport2_h__ + +#ifdef DEBUG_darinf +# define ENABLE_SOCKET_TRACING +#endif + +#include <functional> + +#include "mozilla/Mutex.h" +#include "nsSocketTransportService2.h" +#include "nsString.h" +#include "nsCOMPtr.h" + +#include "nsIInterfaceRequestor.h" +#include "nsISocketTransport.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIDNSListener.h" +#include "nsIDNSRecord.h" +#include "nsIClassInfo.h" +#include "mozilla/net/DNS.h" +#include "nsASocketHandler.h" +#include "mozilla/Telemetry.h" + +#include "prerror.h" +#include "ssl.h" + +class nsICancelable; +class nsIDNSRecord; +class nsIInterfaceRequestor; + +//----------------------------------------------------------------------------- + +// after this short interval, we will return to PR_Poll +#define NS_SOCKET_CONNECT_TIMEOUT PR_MillisecondsToInterval(20) + +//----------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +nsresult ErrorAccordingToNSPR(PRErrorCode errorCode); + +class nsSocketInputStream; +class nsSocketOutputStream; + +//----------------------------------------------------------------------------- + +class nsSocketTransport final : public nsASocketHandler, + public nsISocketTransport, + public nsIDNSListener, + public nsIClassInfo, + public nsIInterfaceRequestor { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORT + NS_DECL_NSISOCKETTRANSPORT + NS_DECL_NSIDNSLISTENER + NS_DECL_NSICLASSINFO + NS_DECL_NSIINTERFACEREQUESTOR + + nsSocketTransport(); + + // this method instructs the socket transport to open a socket of the + // given type(s) to the given host or proxy. + nsresult Init(const nsTArray<nsCString>& socketTypes, const nsACString& host, + uint16_t port, const nsACString& hostRoute, uint16_t portRoute, + nsIProxyInfo* proxyInfo, nsIDNSRecord* dnsRecord); + + // this method instructs the socket transport to use an already connected + // socket with the given address. + nsresult InitWithConnectedSocket(PRFileDesc* socketFD, const NetAddr* addr); + + // this method instructs the socket transport to use an already connected + // socket with the given address, and additionally supplies the security + // callbacks interface requestor. + nsresult InitWithConnectedSocket(PRFileDesc* aFD, const NetAddr* aAddr, + nsIInterfaceRequestor* aCallbacks); + +#ifdef XP_UNIX + // This method instructs the socket transport to open a socket + // connected to the given Unix domain address. We can only create + // unlayered, simple, stream sockets. + nsresult InitWithFilename(const char* filename); + + // This method instructs the socket transport to open a socket + // connected to the given Unix domain address that includes abstract + // socket address. If using abstract socket address, first character of + // name parameter has to be \0. + // We can only create unlayered, simple, stream sockets. + nsresult InitWithName(const char* name, size_t len); +#endif + + // nsASocketHandler methods: + void OnSocketReady(PRFileDesc*, int16_t outFlags) override; + void OnSocketDetached(PRFileDesc*) override; + void IsLocal(bool* aIsLocal) override; + void OnKeepaliveEnabledPrefChange(bool aEnabled) final; + + // called when a socket event is handled + void OnSocketEvent(uint32_t type, nsresult status, nsISupports* param, + std::function<void()>&& task); + + uint64_t ByteCountReceived() override; + uint64_t ByteCountSent() override; + static void CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled); + static void SendPRBlockingTelemetry( + PRIntervalTime aStart, Telemetry::HistogramID aIDNormal, + Telemetry::HistogramID aIDShutdown, + Telemetry::HistogramID aIDConnectivityChange, + Telemetry::HistogramID aIDLinkChange, Telemetry::HistogramID aIDOffline); + + protected: + virtual ~nsSocketTransport(); + + private: + // event types + enum { + MSG_ENSURE_CONNECT, + MSG_DNS_LOOKUP_COMPLETE, + MSG_RETRY_INIT_SOCKET, + MSG_TIMEOUT_CHANGED, + MSG_INPUT_CLOSED, + MSG_INPUT_PENDING, + MSG_OUTPUT_CLOSED, + MSG_OUTPUT_PENDING + }; + nsresult PostEvent(uint32_t type, nsresult status = NS_OK, + nsISupports* param = nullptr, + std::function<void()>&& task = nullptr); + + enum { + STATE_CLOSED, + STATE_IDLE, + STATE_RESOLVING, + STATE_CONNECTING, + STATE_TRANSFERRING + }; + + // Safer way to get and automatically release PRFileDesc objects. + class MOZ_STACK_CLASS PRFileDescAutoLock { + public: + explicit PRFileDescAutoLock(nsSocketTransport* aSocketTransport, + nsresult* aConditionWhileLocked = nullptr) + : mSocketTransport(aSocketTransport), mFd(nullptr) { + MOZ_ASSERT(aSocketTransport); + MutexAutoLock lock(mSocketTransport->mLock); + if (aConditionWhileLocked) { + *aConditionWhileLocked = mSocketTransport->mCondition; + if (NS_FAILED(mSocketTransport->mCondition)) { + return; + } + } + mFd = mSocketTransport->GetFD_Locked(); + } + ~PRFileDescAutoLock() { + MutexAutoLock lock(mSocketTransport->mLock); + if (mFd) { + mSocketTransport->ReleaseFD_Locked(mFd); + } + } + bool IsInitialized() { return mFd; } + operator PRFileDesc*() { return mFd; } + nsresult SetKeepaliveEnabled(bool aEnable); + nsresult SetKeepaliveVals(bool aEnabled, int aIdleTime, int aRetryInterval, + int aProbeCount); + + private: + operator PRFileDescAutoLock*() { return nullptr; } + + // Weak ptr to nsSocketTransport since this is a stack class only. + nsSocketTransport* mSocketTransport; + PRFileDesc* mFd; + }; + friend class PRFileDescAutoLock; + + class LockedPRFileDesc { + public: + explicit LockedPRFileDesc(nsSocketTransport* aSocketTransport) + : mSocketTransport(aSocketTransport), mFd(nullptr) { + MOZ_ASSERT(aSocketTransport); + } + ~LockedPRFileDesc() = default; + bool IsInitialized() { return mFd; } + LockedPRFileDesc& operator=(PRFileDesc* aFd) { + mSocketTransport->mLock.AssertCurrentThreadOwns(); + mFd = aFd; + return *this; + } + operator PRFileDesc*() { + if (mSocketTransport->mAttached) { + mSocketTransport->mLock.AssertCurrentThreadOwns(); + } + return mFd; + } + bool operator==(PRFileDesc* aFd) { + mSocketTransport->mLock.AssertCurrentThreadOwns(); + return mFd == aFd; + } + + private: + operator LockedPRFileDesc*() { return nullptr; } + // Weak ptr to nsSocketTransport since it owns this class. + nsSocketTransport* mSocketTransport; + PRFileDesc* mFd; + }; + friend class LockedPRFileDesc; + + //------------------------------------------------------------------------- + // these members are "set" at initialization time and are never modified + // afterwards. this allows them to be safely accessed from any thread. + //------------------------------------------------------------------------- + + // socket type info: + nsTArray<nsCString> mTypes; + nsCString mHost; + nsCString mProxyHost; + nsCString mOriginHost; + uint16_t mPort{0}; + nsCOMPtr<nsIProxyInfo> mProxyInfo; + uint16_t mProxyPort{0}; + uint16_t mOriginPort{0}; + bool mProxyTransparent{false}; + bool mProxyTransparentResolvesHost{false}; + bool mHttpsProxy{false}; + uint32_t mConnectionFlags{0}; + // When we fail to connect using a prefered IP family, we tell the consumer to + // reset the IP family preference on the connection entry. + bool mResetFamilyPreference{false}; + uint32_t mTlsFlags{0}; + bool mReuseAddrPort{false}; + + // The origin attributes are used to create sockets. The first party domain + // will eventually be used to isolate OCSP cache and is only non-empty when + // "privacy.firstparty.isolate" is enabled. Setting this is the only way to + // carry origin attributes down to NSPR layers which are final consumers. + // It must be set before the socket transport is built. + OriginAttributes mOriginAttributes; + + uint16_t SocketPort() { + return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyPort : mPort; + } + const nsCString& SocketHost() { + return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyHost : mHost; + } + + Atomic<bool> mInputClosed{true}; + Atomic<bool> mOutputClosed{true}; + + //------------------------------------------------------------------------- + // members accessible only on the socket transport thread: + // (the exception being initialization/shutdown time) + //------------------------------------------------------------------------- + + // socket state vars: + uint32_t mState{STATE_CLOSED}; // STATE_??? flags + bool mAttached{false}; + + // this flag is used to determine if the results of a host lookup arrive + // recursively or not. this flag is not protected by any lock. + bool mResolving{false}; + + nsCOMPtr<nsICancelable> mDNSRequest; + nsCOMPtr<nsIDNSAddrRecord> mDNSRecord; + + nsCString mEchConfig; + bool mEchConfigUsed = false; + bool mResolvedByTRR{false}; + nsIRequest::TRRMode mEffectiveTRRMode{nsIRequest::TRR_DEFAULT_MODE}; + nsITRRSkipReason::value mTRRSkipReason{nsITRRSkipReason::TRR_UNSET}; + + nsCOMPtr<nsISupports> mInputCopyContext; + nsCOMPtr<nsISupports> mOutputCopyContext; + + // mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have + // reached STATE_TRANSFERRING. It must not change after that. + void SetSocketName(PRFileDesc* fd); + NetAddr mNetAddr; + NetAddr mSelfAddr; // getsockname() + Atomic<bool, Relaxed> mNetAddrIsSet{false}; + Atomic<bool, Relaxed> mSelfAddrIsSet{false}; + + UniquePtr<NetAddr> mBindAddr; + + // socket methods (these can only be called on the socket thread): + + void SendStatus(nsresult status); + nsresult ResolveHost(); + nsresult BuildSocket(PRFileDesc*&, bool&, bool&); + nsresult InitiateSocket(); + bool RecoverFromError(); + + void OnMsgInputPending() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mState == STATE_TRANSFERRING) { + mPollFlags |= (PR_POLL_READ | PR_POLL_EXCEPT); + } + } + void OnMsgOutputPending() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mState == STATE_TRANSFERRING) { + mPollFlags |= (PR_POLL_WRITE | PR_POLL_EXCEPT); + } + } + void OnMsgInputClosed(nsresult reason); + void OnMsgOutputClosed(nsresult reason); + + // called when the socket is connected + void OnSocketConnected(); + + //------------------------------------------------------------------------- + // socket input/output objects. these may be accessed on any thread with + // the exception of some specific methods (XXX). + + // protects members in this section. + Mutex mLock{"nsSocketTransport.mLock"}; + LockedPRFileDesc mFD MOZ_GUARDED_BY(mLock); + // mFD is closed when mFDref goes to zero. + nsrefcnt mFDref MOZ_GUARDED_BY(mLock){0}; + // mFD is available to consumer when TRUE. + bool mFDconnected MOZ_GUARDED_BY(mLock){false}; + + // A delete protector reference to gSocketTransportService held for lifetime + // of 'this'. Sometimes used interchangably with gSocketTransportService due + // to scoping. + RefPtr<nsSocketTransportService> mSocketTransportService; + + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; + nsCOMPtr<nsITransportEventSink> mEventSink; + nsCOMPtr<nsITLSSocketControl> mTLSSocketControl; + + UniquePtr<nsSocketInputStream> mInput; + UniquePtr<nsSocketOutputStream> mOutput; + + friend class nsSocketInputStream; + friend class nsSocketOutputStream; + + // socket timeouts are protected by mLock. + uint16_t mTimeouts[2]{0}; + + // linger options to use when closing + bool mLingerPolarity{false}; + int16_t mLingerTimeout{0}; + + // QoS setting for socket + uint8_t mQoSBits{0x00}; + + // + // mFD access methods: called with mLock held. + // + PRFileDesc* GetFD_Locked() MOZ_REQUIRES(mLock); + void ReleaseFD_Locked(PRFileDesc* fd) MOZ_REQUIRES(mLock); + + // + // stream state changes (called outside mLock): + // + void OnInputClosed(nsresult reason) { + // no need to post an event if called on the socket thread + if (OnSocketThread()) { + OnMsgInputClosed(reason); + } else { + PostEvent(MSG_INPUT_CLOSED, reason); + } + } + void OnInputPending() { + // no need to post an event if called on the socket thread + if (OnSocketThread()) { + OnMsgInputPending(); + } else { + PostEvent(MSG_INPUT_PENDING); + } + } + void OnOutputClosed(nsresult reason) { + // no need to post an event if called on the socket thread + if (OnSocketThread()) { + OnMsgOutputClosed(reason); // XXX need to not be inside lock! + } else { + PostEvent(MSG_OUTPUT_CLOSED, reason); + } + } + void OnOutputPending() { + // no need to post an event if called on the socket thread + if (OnSocketThread()) { + OnMsgOutputPending(); + } else { + PostEvent(MSG_OUTPUT_PENDING); + } + } + +#ifdef ENABLE_SOCKET_TRACING + void TraceInBuf(const char* buf, int32_t n); + void TraceOutBuf(const char* buf, int32_t n); +#endif + + // Reads prefs to get default keepalive config. + nsresult EnsureKeepaliveValsAreInitialized(); + + // Groups calls to fd.SetKeepaliveEnabled and fd.SetKeepaliveVals. + nsresult SetKeepaliveEnabledInternal(bool aEnable); + + // True if keepalive has been enabled by the socket owner. Note: Keepalive + // must also be enabled globally for it to be enabled in TCP. + bool mKeepaliveEnabled{false}; + + // Keepalive config (support varies by platform). + int32_t mKeepaliveIdleTimeS{-1}; + int32_t mKeepaliveRetryIntervalS{-1}; + int32_t mKeepaliveProbeCount{-1}; + + Atomic<bool> mDoNotRetryToConnect{false}; + + // Whether the port remapping has already been applied. We definitely want to + // prevent duplicate calls in case of chaining remapping. + bool mPortRemappingApplied = false; + + bool mExternalDNSResolution = false; + bool mRetryDnsIfPossible = false; +}; + +class nsSocketInputStream : public nsIAsyncInputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + explicit nsSocketInputStream(nsSocketTransport*); + virtual ~nsSocketInputStream() = default; + + // nsSocketTransport holds a ref to us + bool IsReferenced() { return mReaderRefCnt > 0; } + nsresult Condition() { + MutexAutoLock lock(mTransport->mLock); + return mCondition; + } + uint64_t ByteCount() { + MutexAutoLock lock(mTransport->mLock); + return mByteCount; + } + uint64_t ByteCount(MutexAutoLock&) MOZ_NO_THREAD_SAFETY_ANALYSIS { + return mByteCount; + } + + // called by the socket transport on the socket thread... + void OnSocketReady(nsresult condition); + + private: + nsSocketTransport* mTransport; + ThreadSafeAutoRefCnt mReaderRefCnt{0}; + + // access to these should be protected by mTransport->mLock but we + // can't order things to allow using MOZ_GUARDED_BY(). + nsresult mCondition MOZ_GUARDED_BY(mTransport->mLock){NS_OK}; + nsCOMPtr<nsIInputStreamCallback> mCallback MOZ_GUARDED_BY(mTransport->mLock); + uint32_t mCallbackFlags MOZ_GUARDED_BY(mTransport->mLock){0}; + uint64_t mByteCount MOZ_GUARDED_BY(mTransport->mLock){0}; +}; + +//----------------------------------------------------------------------------- + +class nsSocketOutputStream : public nsIAsyncOutputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + + explicit nsSocketOutputStream(nsSocketTransport*); + virtual ~nsSocketOutputStream() = default; + + // nsSocketTransport holds a ref to us + bool IsReferenced() { return mWriterRefCnt > 0; } + nsresult Condition() { + MutexAutoLock lock(mTransport->mLock); + return mCondition; + } + uint64_t ByteCount() { + MutexAutoLock lock(mTransport->mLock); + return mByteCount; + } + uint64_t ByteCount(MutexAutoLock&) MOZ_NO_THREAD_SAFETY_ANALYSIS { + return mByteCount; + } + + // called by the socket transport on the socket thread... + void OnSocketReady(nsresult condition); + + private: + static nsresult WriteFromSegments(nsIInputStream*, void*, const char*, + uint32_t offset, uint32_t count, + uint32_t* countRead); + + nsSocketTransport* mTransport; + ThreadSafeAutoRefCnt mWriterRefCnt{0}; + + // access to these should be protected by mTransport->mLock but we + // can't order things to allow using MOZ_GUARDED_BY(). + nsresult mCondition MOZ_GUARDED_BY(mTransport->mLock){NS_OK}; + nsCOMPtr<nsIOutputStreamCallback> mCallback MOZ_GUARDED_BY(mTransport->mLock); + uint32_t mCallbackFlags MOZ_GUARDED_BY(mTransport->mLock){0}; + uint64_t mByteCount MOZ_GUARDED_BY(mTransport->mLock){0}; +}; + +} // namespace net +} // namespace mozilla + +#endif // !nsSocketTransport_h__ diff --git a/netwerk/base/nsSocketTransportService2.cpp b/netwerk/base/nsSocketTransportService2.cpp new file mode 100644 index 0000000000..a6aa249465 --- /dev/null +++ b/netwerk/base/nsSocketTransportService2.cpp @@ -0,0 +1,1899 @@ +// vim:set sw=2 sts=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSocketTransportService2.h" + +#include "IOActivityMonitor.h" +#include "mozilla/Atomics.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Likely.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/ProfilerThreadSleep.h" +#include "mozilla/PublicSSL.h" +#include "mozilla/ReverseIterator.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Tokenizer.h" +#include "mozilla/Telemetry.h" +#include "nsASocketHandler.h" +#include "nsError.h" +#include "nsIFile.h" +#include "nsINetworkLinkService.h" +#include "nsIOService.h" +#include "nsIObserverService.h" +#include "nsIWidget.h" +#include "nsServiceManagerUtils.h" +#include "nsSocketTransport2.h" +#include "nsThreadUtils.h" +#include "prerror.h" +#include "prnetdb.h" + +namespace mozilla { +namespace net { + +#define SOCKET_THREAD_LONGTASK_MS 3 + +LazyLogModule gSocketTransportLog("nsSocketTransport"); +LazyLogModule gUDPSocketLog("UDPSocket"); +LazyLogModule gTCPSocketLog("TCPSocket"); + +nsSocketTransportService* gSocketTransportService = nullptr; +static Atomic<PRThread*, Relaxed> gSocketThread(nullptr); + +#define SEND_BUFFER_PREF "network.tcp.sendbuffer" +#define KEEPALIVE_ENABLED_PREF "network.tcp.keepalive.enabled" +#define KEEPALIVE_IDLE_TIME_PREF "network.tcp.keepalive.idle_time" +#define KEEPALIVE_RETRY_INTERVAL_PREF "network.tcp.keepalive.retry_interval" +#define KEEPALIVE_PROBE_COUNT_PREF "network.tcp.keepalive.probe_count" +#define SOCKET_LIMIT_TARGET 1000U +#define MAX_TIME_BETWEEN_TWO_POLLS \ + "network.sts.max_time_for_events_between_two_polls" +#define POLL_BUSY_WAIT_PERIOD "network.sts.poll_busy_wait_period" +#define POLL_BUSY_WAIT_PERIOD_TIMEOUT \ + "network.sts.poll_busy_wait_period_timeout" +#define MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN \ + "network.sts.max_time_for_pr_close_during_shutdown" +#define POLLABLE_EVENT_TIMEOUT "network.sts.pollable_event_timeout" + +#define REPAIR_POLLABLE_EVENT_TIME 10 + +uint32_t nsSocketTransportService::gMaxCount; +PRCallOnceType nsSocketTransportService::gMaxCountInitOnce; + +// Utility functions +bool OnSocketThread() { return PR_GetCurrentThread() == gSocketThread; } + +//----------------------------------------------------------------------------- + +bool nsSocketTransportService::SocketContext::IsTimedOut( + PRIntervalTime now) const { + return TimeoutIn(now) == 0; +} + +void nsSocketTransportService::SocketContext::EnsureTimeout( + PRIntervalTime now) { + SOCKET_LOG(("SocketContext::EnsureTimeout socket=%p", mHandler.get())); + if (!mPollStartEpoch) { + SOCKET_LOG((" engaging")); + mPollStartEpoch = now; + } +} + +void nsSocketTransportService::SocketContext::DisengageTimeout() { + SOCKET_LOG(("SocketContext::DisengageTimeout socket=%p", mHandler.get())); + mPollStartEpoch = 0; +} + +PRIntervalTime nsSocketTransportService::SocketContext::TimeoutIn( + PRIntervalTime now) const { + SOCKET_LOG(("SocketContext::TimeoutIn socket=%p, timeout=%us", mHandler.get(), + mHandler->mPollTimeout)); + + if (mHandler->mPollTimeout == UINT16_MAX || !mPollStartEpoch) { + SOCKET_LOG((" not engaged")); + return NS_SOCKET_POLL_TIMEOUT; + } + + PRIntervalTime elapsed = (now - mPollStartEpoch); + PRIntervalTime timeout = PR_SecondsToInterval(mHandler->mPollTimeout); + + if (elapsed >= timeout) { + SOCKET_LOG((" timed out!")); + return 0; + } + SOCKET_LOG((" remains %us", PR_IntervalToSeconds(timeout - elapsed))); + return timeout - elapsed; +} + +void nsSocketTransportService::SocketContext::MaybeResetEpoch() { + if (mPollStartEpoch && mHandler->mPollTimeout == UINT16_MAX) { + mPollStartEpoch = 0; + } +} + +//----------------------------------------------------------------------------- +// ctor/dtor (called on the main/UI thread by the service manager) + +nsSocketTransportService::nsSocketTransportService() + : mPollableEventTimeout(TimeDuration::FromSeconds(6)), + mMaxTimeForPrClosePref(PR_SecondsToInterval(5)), + mNetworkLinkChangeBusyWaitPeriod(PR_SecondsToInterval(50)), + mNetworkLinkChangeBusyWaitTimeout(PR_SecondsToInterval(7)) { + NS_ASSERTION(NS_IsMainThread(), "wrong thread"); + + PR_CallOnce(&gMaxCountInitOnce, DiscoverMaxCount); + + NS_ASSERTION(!gSocketTransportService, "must not instantiate twice"); + gSocketTransportService = this; + + // The Poll list always has an entry at [0]. The rest of the + // list is a duplicate of the Active list's PRFileDesc file descriptors. + PRPollDesc entry = {nullptr, PR_POLL_READ | PR_POLL_EXCEPT, 0}; + mPollList.InsertElementAt(0, entry); +} + +void nsSocketTransportService::ApplyPortRemap(uint16_t* aPort) { + MOZ_ASSERT(IsOnCurrentThreadInfallible()); + + if (!mPortRemapping) { + return; + } + + // Reverse the array to make later rules override earlier rules. + for (auto const& portMapping : Reversed(*mPortRemapping)) { + if (*aPort < std::get<0>(portMapping)) { + continue; + } + if (*aPort > std::get<1>(portMapping)) { + continue; + } + + *aPort = std::get<2>(portMapping); + return; + } +} + +bool nsSocketTransportService::UpdatePortRemapPreference( + nsACString const& aPortMappingPref) { + TPortRemapping portRemapping; + + auto consumePreference = [&]() -> bool { + Tokenizer tokenizer(aPortMappingPref); + + tokenizer.SkipWhites(); + if (tokenizer.CheckEOF()) { + return true; + } + + nsTArray<std::tuple<uint16_t, uint16_t>> ranges(2); + while (true) { + uint16_t loPort; + tokenizer.SkipWhites(); + if (!tokenizer.ReadInteger(&loPort)) { + break; + } + + uint16_t hiPort; + tokenizer.SkipWhites(); + if (tokenizer.CheckChar('-')) { + tokenizer.SkipWhites(); + if (!tokenizer.ReadInteger(&hiPort)) { + break; + } + } else { + hiPort = loPort; + } + + ranges.AppendElement(std::make_tuple(loPort, hiPort)); + + tokenizer.SkipWhites(); + if (tokenizer.CheckChar(',')) { + continue; // another port or port range is expected + } + + if (tokenizer.CheckChar('=')) { + uint16_t targetPort; + tokenizer.SkipWhites(); + if (!tokenizer.ReadInteger(&targetPort)) { + break; + } + + // Storing reversed, because the most common cases (like 443) will very + // likely be listed as first, less common cases will be added to the end + // of the list mapping to the same port. As we iterate the whole + // remapping array from the end, this may have a small perf win by + // hitting the most common cases earlier. + for (auto const& range : Reversed(ranges)) { + portRemapping.AppendElement(std::make_tuple( + std::get<0>(range), std::get<1>(range), targetPort)); + } + ranges.Clear(); + + tokenizer.SkipWhites(); + if (tokenizer.CheckChar(';')) { + continue; // more mappings (or EOF) expected + } + if (tokenizer.CheckEOF()) { + return true; + } + } + + // Anything else is unexpected. + break; + } + + // 'break' from the parsing loop means ill-formed preference + portRemapping.Clear(); + return false; + }; + + bool rv = consumePreference(); + + if (!IsOnCurrentThread()) { + nsCOMPtr<nsIThread> thread = GetThreadSafely(); + if (!thread) { + // Init hasn't been called yet. Could probably just assert. + // If shutdown, the dispatch below will just silently fail. + NS_ASSERTION(false, "ApplyPortRemapPreference before STS::Init"); + return false; + } + thread->Dispatch(NewRunnableMethod<TPortRemapping>( + "net::ApplyPortRemapping", this, + &nsSocketTransportService::ApplyPortRemapPreference, portRemapping)); + } else { + ApplyPortRemapPreference(portRemapping); + } + + return rv; +} + +nsSocketTransportService::~nsSocketTransportService() { + NS_ASSERTION(NS_IsMainThread(), "wrong thread"); + NS_ASSERTION(!mInitialized, "not shutdown properly"); + + gSocketTransportService = nullptr; +} + +//----------------------------------------------------------------------------- +// event queue (any thread) + +already_AddRefed<nsIThread> nsSocketTransportService::GetThreadSafely() { + MutexAutoLock lock(mLock); + nsCOMPtr<nsIThread> result = mThread; + return result.forget(); +} + +NS_IMETHODIMP +nsSocketTransportService::DispatchFromScript(nsIRunnable* event, + uint32_t flags) { + nsCOMPtr<nsIRunnable> event_ref(event); + return Dispatch(event_ref.forget(), flags); +} + +NS_IMETHODIMP +nsSocketTransportService::Dispatch(already_AddRefed<nsIRunnable> event, + uint32_t flags) { + nsCOMPtr<nsIRunnable> event_ref(event); + SOCKET_LOG(("STS dispatch [%p]\n", event_ref.get())); + + nsCOMPtr<nsIThread> thread = GetThreadSafely(); + nsresult rv; + rv = thread ? thread->Dispatch(event_ref.forget(), flags) + : NS_ERROR_NOT_INITIALIZED; + if (rv == NS_ERROR_UNEXPECTED) { + // Thread is no longer accepting events. We must have just shut it + // down on the main thread. Pretend we never saw it. + rv = NS_ERROR_NOT_INITIALIZED; + } + return rv; +} + +NS_IMETHODIMP +nsSocketTransportService::DelayedDispatch(already_AddRefed<nsIRunnable>, + uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsSocketTransportService::RegisterShutdownTask(nsITargetShutdownTask* task) { + nsCOMPtr<nsIThread> thread = GetThreadSafely(); + return thread ? thread->RegisterShutdownTask(task) : NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsSocketTransportService::UnregisterShutdownTask(nsITargetShutdownTask* task) { + nsCOMPtr<nsIThread> thread = GetThreadSafely(); + return thread ? thread->UnregisterShutdownTask(task) : NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsSocketTransportService::IsOnCurrentThread(bool* result) { + *result = OnSocketThread(); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +nsSocketTransportService::IsOnCurrentThreadInfallible() { + return OnSocketThread(); +} + +//----------------------------------------------------------------------------- +// nsIDirectTaskDispatcher + +already_AddRefed<nsIDirectTaskDispatcher> +nsSocketTransportService::GetDirectTaskDispatcherSafely() { + MutexAutoLock lock(mLock); + nsCOMPtr<nsIDirectTaskDispatcher> result = mDirectTaskDispatcher; + return result.forget(); +} + +NS_IMETHODIMP +nsSocketTransportService::DispatchDirectTask( + already_AddRefed<nsIRunnable> aEvent) { + nsCOMPtr<nsIDirectTaskDispatcher> dispatcher = + GetDirectTaskDispatcherSafely(); + NS_ENSURE_TRUE(dispatcher, NS_ERROR_NOT_INITIALIZED); + return dispatcher->DispatchDirectTask(std::move(aEvent)); +} + +NS_IMETHODIMP nsSocketTransportService::DrainDirectTasks() { + nsCOMPtr<nsIDirectTaskDispatcher> dispatcher = + GetDirectTaskDispatcherSafely(); + if (!dispatcher) { + // nothing to drain. + return NS_OK; + } + return dispatcher->DrainDirectTasks(); +} + +NS_IMETHODIMP nsSocketTransportService::HaveDirectTasks(bool* aValue) { + nsCOMPtr<nsIDirectTaskDispatcher> dispatcher = + GetDirectTaskDispatcherSafely(); + if (!dispatcher) { + *aValue = false; + return NS_OK; + } + return dispatcher->HaveDirectTasks(aValue); +} + +//----------------------------------------------------------------------------- +// socket api (socket thread only) + +NS_IMETHODIMP +nsSocketTransportService::NotifyWhenCanAttachSocket(nsIRunnable* event) { + SOCKET_LOG(("nsSocketTransportService::NotifyWhenCanAttachSocket\n")); + + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (CanAttachSocket()) { + return Dispatch(event, NS_DISPATCH_NORMAL); + } + + auto* runnable = new LinkedRunnableEvent(event); + mPendingSocketQueue.insertBack(runnable); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::AttachSocket(PRFileDesc* fd, + nsASocketHandler* handler) { + SOCKET_LOG( + ("nsSocketTransportService::AttachSocket [handler=%p]\n", handler)); + + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (!CanAttachSocket()) { + return NS_ERROR_NOT_AVAILABLE; + } + + SocketContext sock{fd, handler, 0}; + + AddToIdleList(&sock); + return NS_OK; +} + +// the number of sockets that can be attached at any given time is +// limited. this is done because some operating systems (e.g., Win9x) +// limit the number of sockets that can be created by an application. +// AttachSocket will fail if the limit is exceeded. consumers should +// call CanAttachSocket and check the result before creating a socket. + +bool nsSocketTransportService::CanAttachSocket() { + MOZ_ASSERT(!mShuttingDown); + uint32_t total = mActiveList.Length() + mIdleList.Length(); + bool rv = total < gMaxCount; + + MOZ_ASSERT(mInitialized); + return rv; +} + +nsresult nsSocketTransportService::DetachSocket(SocketContextList& listHead, + SocketContext* sock) { + SOCKET_LOG(("nsSocketTransportService::DetachSocket [handler=%p]\n", + sock->mHandler.get())); + MOZ_ASSERT((&listHead == &mActiveList) || (&listHead == &mIdleList), + "DetachSocket invalid head"); + + { + // inform the handler that this socket is going away + sock->mHandler->OnSocketDetached(sock->mFD); + } + mSentBytesCount += sock->mHandler->ByteCountSent(); + mReceivedBytesCount += sock->mHandler->ByteCountReceived(); + + // cleanup + sock->mFD = nullptr; + + if (&listHead == &mActiveList) { + RemoveFromPollList(sock); + } else { + RemoveFromIdleList(sock); + } + + // NOTE: sock is now an invalid pointer + + // + // notify the first element on the pending socket queue... + // + nsCOMPtr<nsIRunnable> event; + LinkedRunnableEvent* runnable = mPendingSocketQueue.getFirst(); + if (runnable) { + event = runnable->TakeEvent(); + runnable->remove(); + delete runnable; + } + if (event) { + // move event from pending queue to dispatch queue + return Dispatch(event, NS_DISPATCH_NORMAL); + } + return NS_OK; +} + +// Returns the index of a SocketContext within a list, or -1 if it's +// not a pointer to a list element +// NOTE: this could be supplied by nsTArray<> +int64_t nsSocketTransportService::SockIndex(SocketContextList& aList, + SocketContext* aSock) { + ptrdiff_t index = -1; + if (!aList.IsEmpty()) { + index = aSock - &aList[0]; + if (index < 0 || (size_t)index + 1 > aList.Length()) { + index = -1; + } + } + return (int64_t)index; +} + +void nsSocketTransportService::AddToPollList(SocketContext* sock) { + MOZ_ASSERT(SockIndex(mActiveList, sock) == -1, + "AddToPollList Socket Already Active"); + + SOCKET_LOG(("nsSocketTransportService::AddToPollList %p [handler=%p]\n", sock, + sock->mHandler.get())); + + sock->EnsureTimeout(PR_IntervalNow()); + PRPollDesc poll; + poll.fd = sock->mFD; + poll.in_flags = sock->mHandler->mPollFlags; + poll.out_flags = 0; + if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) { + auto newSocketIndex = mActiveList.Length(); + newSocketIndex = ChaosMode::randomUint32LessThan(newSocketIndex + 1); + mActiveList.InsertElementAt(newSocketIndex, *sock); + // mPollList is offset by 1 + mPollList.InsertElementAt(newSocketIndex + 1, poll); + } else { + // Avoid refcount bump/decrease + mActiveList.EmplaceBack(sock->mFD, sock->mHandler.forget(), + sock->mPollStartEpoch); + mPollList.AppendElement(poll); + } + + SOCKET_LOG( + (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length())); +} + +void nsSocketTransportService::RemoveFromPollList(SocketContext* sock) { + SOCKET_LOG(("nsSocketTransportService::RemoveFromPollList %p [handler=%p]\n", + sock, sock->mHandler.get())); + + auto index = SockIndex(mActiveList, sock); + MOZ_RELEASE_ASSERT(index != -1, "invalid index"); + + SOCKET_LOG((" index=%" PRId64 " mActiveList.Length()=%zu\n", index, + mActiveList.Length())); + mActiveList.UnorderedRemoveElementAt(index); + // mPollList is offset by 1 + mPollList.UnorderedRemoveElementAt(index + 1); + + SOCKET_LOG( + (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length())); +} + +void nsSocketTransportService::AddToIdleList(SocketContext* sock) { + MOZ_ASSERT(SockIndex(mIdleList, sock) == -1, + "AddToIdleList Socket Already Idle"); + + SOCKET_LOG(("nsSocketTransportService::AddToIdleList %p [handler=%p]\n", sock, + sock->mHandler.get())); + + // Avoid refcount bump/decrease + mIdleList.EmplaceBack(sock->mFD, sock->mHandler.forget(), + sock->mPollStartEpoch); + + SOCKET_LOG( + (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length())); +} + +void nsSocketTransportService::RemoveFromIdleList(SocketContext* sock) { + SOCKET_LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%p]\n", + sock->mHandler.get())); + auto index = SockIndex(mIdleList, sock); + MOZ_RELEASE_ASSERT(index != -1); + mIdleList.UnorderedRemoveElementAt(index); + + SOCKET_LOG( + (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length())); +} + +void nsSocketTransportService::MoveToIdleList(SocketContext* sock) { + SOCKET_LOG(("nsSocketTransportService::MoveToIdleList %p [handler=%p]\n", + sock, sock->mHandler.get())); + MOZ_ASSERT(SockIndex(mIdleList, sock) == -1); + MOZ_ASSERT(SockIndex(mActiveList, sock) != -1); + AddToIdleList(sock); + RemoveFromPollList(sock); +} + +void nsSocketTransportService::MoveToPollList(SocketContext* sock) { + SOCKET_LOG(("nsSocketTransportService::MoveToPollList %p [handler=%p]\n", + sock, sock->mHandler.get())); + MOZ_ASSERT(SockIndex(mIdleList, sock) != -1); + MOZ_ASSERT(SockIndex(mActiveList, sock) == -1); + AddToPollList(sock); + RemoveFromIdleList(sock); +} + +void nsSocketTransportService::ApplyPortRemapPreference( + TPortRemapping const& portRemapping) { + MOZ_ASSERT(IsOnCurrentThreadInfallible()); + + mPortRemapping.reset(); + if (!portRemapping.IsEmpty()) { + mPortRemapping.emplace(portRemapping); + } +} + +PRIntervalTime nsSocketTransportService::PollTimeout(PRIntervalTime now) { + if (mActiveList.IsEmpty()) { + return NS_SOCKET_POLL_TIMEOUT; + } + + // compute minimum time before any socket timeout expires. + PRIntervalTime minR = NS_SOCKET_POLL_TIMEOUT; + for (uint32_t i = 0; i < mActiveList.Length(); ++i) { + const SocketContext& s = mActiveList[i]; + PRIntervalTime r = s.TimeoutIn(now); + if (r < minR) { + minR = r; + } + } + if (minR == NS_SOCKET_POLL_TIMEOUT) { + SOCKET_LOG(("poll timeout: none\n")); + return NS_SOCKET_POLL_TIMEOUT; + } + SOCKET_LOG(("poll timeout: %" PRIu32 "\n", PR_IntervalToSeconds(minR))); + return minR; +} + +int32_t nsSocketTransportService::Poll(TimeDuration* pollDuration, + PRIntervalTime ts) { + MOZ_ASSERT(IsOnCurrentThread()); + PRPollDesc* firstPollEntry; + uint32_t pollCount; + PRIntervalTime pollTimeout; + *pollDuration = nullptr; + + // If there are pending events for this thread then + // DoPollIteration() should service the network without blocking. + bool pendingEvents = false; + mRawThread->HasPendingEvents(&pendingEvents); + + if (mPollList[0].fd) { + mPollList[0].out_flags = 0; + firstPollEntry = &mPollList[0]; + pollCount = mPollList.Length(); + pollTimeout = pendingEvents ? PR_INTERVAL_NO_WAIT : PollTimeout(ts); + } else { + // no pollable event, so busy wait... + pollCount = mActiveList.Length(); + if (pollCount) { + firstPollEntry = &mPollList[1]; + } else { + firstPollEntry = nullptr; + } + pollTimeout = + pendingEvents ? PR_INTERVAL_NO_WAIT : PR_MillisecondsToInterval(25); + } + + if ((ts - mLastNetworkLinkChangeTime) < mNetworkLinkChangeBusyWaitPeriod) { + // Being here means we are few seconds after a network change has + // been detected. + PRIntervalTime to = mNetworkLinkChangeBusyWaitTimeout; + if (to) { + pollTimeout = std::min(to, pollTimeout); + SOCKET_LOG((" timeout shorthened after network change event")); + } + } + + TimeStamp pollStart; + if (Telemetry::CanRecordPrereleaseData()) { + pollStart = TimeStamp::NowLoRes(); + } + + SOCKET_LOG((" timeout = %i milliseconds\n", + PR_IntervalToMilliseconds(pollTimeout))); + + int32_t n; + { +#ifdef MOZ_GECKO_PROFILER + TimeStamp startTime = TimeStamp::Now(); + if (pollTimeout != PR_INTERVAL_NO_WAIT) { + // There will be an actual non-zero wait, let the profiler know about it + // by marking thread as sleeping around the polling call. + profiler_thread_sleep(); + } +#endif + + n = PR_Poll(firstPollEntry, pollCount, pollTimeout); + +#ifdef MOZ_GECKO_PROFILER + if (pollTimeout != PR_INTERVAL_NO_WAIT) { + profiler_thread_wake(); + } + if (profiler_thread_is_being_profiled_for_markers()) { + PROFILER_MARKER_TEXT( + "SocketTransportService::Poll", NETWORK, + MarkerTiming::IntervalUntilNowFrom(startTime), + pollTimeout == PR_INTERVAL_NO_TIMEOUT + ? nsPrintfCString("Poll count: %u, Poll timeout: NO_TIMEOUT", + pollCount) + : pollTimeout == PR_INTERVAL_NO_WAIT + ? nsPrintfCString("Poll count: %u, Poll timeout: NO_WAIT", + pollCount) + : nsPrintfCString("Poll count: %u, Poll timeout: %ums", pollCount, + PR_IntervalToMilliseconds(pollTimeout))); + } +#endif + } + + if (Telemetry::CanRecordPrereleaseData() && !pollStart.IsNull()) { + *pollDuration = TimeStamp::NowLoRes() - pollStart; + } + + SOCKET_LOG((" ...returned after %i milliseconds\n", + PR_IntervalToMilliseconds(PR_IntervalNow() - ts))); + + return n; +} + +//----------------------------------------------------------------------------- +// xpcom api + +NS_IMPL_ISUPPORTS(nsSocketTransportService, nsISocketTransportService, + nsIRoutedSocketTransportService, nsIEventTarget, + nsISerialEventTarget, nsIThreadObserver, nsIRunnable, + nsPISocketTransportService, nsIObserver, nsINamed, + nsIDirectTaskDispatcher) + +static const char* gCallbackPrefs[] = { + SEND_BUFFER_PREF, + KEEPALIVE_ENABLED_PREF, + KEEPALIVE_IDLE_TIME_PREF, + KEEPALIVE_RETRY_INTERVAL_PREF, + KEEPALIVE_PROBE_COUNT_PREF, + MAX_TIME_BETWEEN_TWO_POLLS, + MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN, + POLLABLE_EVENT_TIMEOUT, + "network.socket.forcePort", + nullptr, +}; + +/* static */ +void nsSocketTransportService::UpdatePrefs(const char* aPref, void* aSelf) { + static_cast<nsSocketTransportService*>(aSelf)->UpdatePrefs(); +} + +static uint32_t GetThreadStackSize() { +#ifdef XP_WIN + if (!StaticPrefs::network_allow_large_stack_size_for_socket_thread()) { + return nsIThreadManager::DEFAULT_STACK_SIZE; + } + + const uint32_t kWindowsThreadStackSize = 512 * 1024; + // We can remove this custom stack size when DEFAULT_STACK_SIZE is increased. + static_assert(kWindowsThreadStackSize > nsIThreadManager::DEFAULT_STACK_SIZE); + return kWindowsThreadStackSize; +#else + return nsIThreadManager::DEFAULT_STACK_SIZE; +#endif +} + +// called from main thread only +NS_IMETHODIMP +nsSocketTransportService::Init() { + if (!NS_IsMainThread()) { + NS_ERROR("wrong thread"); + return NS_ERROR_UNEXPECTED; + } + + if (mInitialized) { + return NS_OK; + } + + if (mShuttingDown) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIThread> thread; + + if (!XRE_IsContentProcess() || + StaticPrefs::network_allow_raw_sockets_in_content_processes_AtStartup()) { + // Since we Poll, we can't use normal LongTask support in Main Process + nsresult rv = NS_NewNamedThread( + "Socket Thread", getter_AddRefs(thread), this, + {GetThreadStackSize(), false, false, Some(SOCKET_THREAD_LONGTASK_MS)}); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // In the child process, we just want a regular nsThread with no socket + // polling. So we don't want to run the nsSocketTransportService runnable on + // it. + nsresult rv = + NS_NewNamedThread("Socket Thread", getter_AddRefs(thread), nullptr, + {nsIThreadManager::DEFAULT_STACK_SIZE, false, false, + Some(SOCKET_THREAD_LONGTASK_MS)}); + NS_ENSURE_SUCCESS(rv, rv); + + // Set up some of the state that nsSocketTransportService::Run would set. + PRThread* prthread = nullptr; + thread->GetPRThread(&prthread); + gSocketThread = prthread; + mRawThread = thread; + } + + { + MutexAutoLock lock(mLock); + // Install our mThread, protecting against concurrent readers + thread.swap(mThread); + mDirectTaskDispatcher = do_QueryInterface(mThread); + MOZ_DIAGNOSTIC_ASSERT( + mDirectTaskDispatcher, + "Underlying thread must support direct task dispatching"); + } + + Preferences::RegisterCallbacks(UpdatePrefs, gCallbackPrefs, this); + UpdatePrefs(); + + nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); + // Note that the observr notifications are forwarded from parent process to + // socket process. We have to make sure the topics registered below are also + // registered in nsIObserver::Init(). + if (obsSvc) { + obsSvc->AddObserver(this, "profile-initial-state", false); + obsSvc->AddObserver(this, "last-pb-context-exited", false); + obsSvc->AddObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC, true); + obsSvc->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true); + obsSvc->AddObserver(this, "xpcom-shutdown-threads", false); + obsSvc->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); + } + + // We can now dispatch tasks to the socket thread. + mInitialized = true; + return NS_OK; +} + +// called from main thread only +NS_IMETHODIMP +nsSocketTransportService::Shutdown(bool aXpcomShutdown) { + SOCKET_LOG(("nsSocketTransportService::Shutdown\n")); + + NS_ENSURE_STATE(NS_IsMainThread()); + + if (!mInitialized || mShuttingDown) { + // We never inited, or shutdown has already started + return NS_OK; + } + + { + auto observersCopy = mShutdownObservers; + for (auto& observer : observersCopy) { + observer->Observe(); + } + } + + mShuttingDown = true; + + { + MutexAutoLock lock(mLock); + + if (mPollableEvent) { + mPollableEvent->Signal(); + } + } + + // If we're shutting down due to going offline (rather than due to XPCOM + // shutdown), also tear down the thread. The thread will be shutdown during + // xpcom-shutdown-threads if during xpcom-shutdown proper. + if (!aXpcomShutdown) { + ShutdownThread(); + } + + return NS_OK; +} + +nsresult nsSocketTransportService::ShutdownThread() { + SOCKET_LOG(("nsSocketTransportService::ShutdownThread\n")); + + NS_ENSURE_STATE(NS_IsMainThread()); + + if (!mInitialized) { + return NS_OK; + } + + // join with thread + nsCOMPtr<nsIThread> thread = GetThreadSafely(); + thread->Shutdown(); + { + MutexAutoLock lock(mLock); + // Drop our reference to mThread and make sure that any concurrent readers + // are excluded + mThread = nullptr; + mDirectTaskDispatcher = nullptr; + } + + Preferences::UnregisterCallbacks(UpdatePrefs, gCallbackPrefs, this); + + nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); + if (obsSvc) { + obsSvc->RemoveObserver(this, "profile-initial-state"); + obsSvc->RemoveObserver(this, "last-pb-context-exited"); + obsSvc->RemoveObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC); + obsSvc->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC); + obsSvc->RemoveObserver(this, "xpcom-shutdown-threads"); + obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TOPIC); + } + + if (mAfterWakeUpTimer) { + mAfterWakeUpTimer->Cancel(); + mAfterWakeUpTimer = nullptr; + } + + IOActivityMonitor::Shutdown(); + + mInitialized = false; + mShuttingDown = false; + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::GetOffline(bool* offline) { + MutexAutoLock lock(mLock); + *offline = mOffline; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::SetOffline(bool offline) { + MutexAutoLock lock(mLock); + if (!mOffline && offline) { + // signal the socket thread to go offline, so it will detach sockets + mGoingOffline = true; + mOffline = true; + } else if (mOffline && !offline) { + mOffline = false; + } + if (mPollableEvent) { + mPollableEvent->Signal(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::GetKeepaliveIdleTime(int32_t* aKeepaliveIdleTimeS) { + MOZ_ASSERT(aKeepaliveIdleTimeS); + if (NS_WARN_IF(!aKeepaliveIdleTimeS)) { + return NS_ERROR_NULL_POINTER; + } + *aKeepaliveIdleTimeS = mKeepaliveIdleTimeS; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::GetKeepaliveRetryInterval( + int32_t* aKeepaliveRetryIntervalS) { + MOZ_ASSERT(aKeepaliveRetryIntervalS); + if (NS_WARN_IF(!aKeepaliveRetryIntervalS)) { + return NS_ERROR_NULL_POINTER; + } + *aKeepaliveRetryIntervalS = mKeepaliveRetryIntervalS; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::GetKeepaliveProbeCount( + int32_t* aKeepaliveProbeCount) { + MOZ_ASSERT(aKeepaliveProbeCount); + if (NS_WARN_IF(!aKeepaliveProbeCount)) { + return NS_ERROR_NULL_POINTER; + } + *aKeepaliveProbeCount = mKeepaliveProbeCount; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::CreateTransport(const nsTArray<nsCString>& types, + const nsACString& host, int32_t port, + nsIProxyInfo* proxyInfo, + nsIDNSRecord* dnsRecord, + nsISocketTransport** result) { + return CreateRoutedTransport(types, host, port, ""_ns, 0, proxyInfo, + dnsRecord, result); +} + +NS_IMETHODIMP +nsSocketTransportService::CreateRoutedTransport( + const nsTArray<nsCString>& types, const nsACString& host, int32_t port, + const nsACString& hostRoute, int32_t portRoute, nsIProxyInfo* proxyInfo, + nsIDNSRecord* dnsRecord, nsISocketTransport** result) { + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(port >= 0 && port <= 0xFFFF, NS_ERROR_ILLEGAL_VALUE); + + RefPtr<nsSocketTransport> trans = new nsSocketTransport(); + nsresult rv = trans->Init(types, host, port, hostRoute, portRoute, proxyInfo, + dnsRecord); + if (NS_FAILED(rv)) { + return rv; + } + + trans.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::CreateUnixDomainTransport( + nsIFile* aPath, nsISocketTransport** result) { +#ifdef XP_UNIX + nsresult rv; + + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + nsAutoCString path; + rv = aPath->GetNativePath(path); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsSocketTransport> trans = new nsSocketTransport(); + + rv = trans->InitWithFilename(path.get()); + NS_ENSURE_SUCCESS(rv, rv); + + trans.forget(result); + return NS_OK; +#else + return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; +#endif +} + +NS_IMETHODIMP +nsSocketTransportService::CreateUnixDomainAbstractAddressTransport( + const nsACString& aName, nsISocketTransport** result) { + // Abstract socket address is supported on Linux only +#ifdef XP_LINUX + RefPtr<nsSocketTransport> trans = new nsSocketTransport(); + // First character of Abstract socket address is null + UniquePtr<char[]> name(new char[aName.Length() + 1]); + *(name.get()) = 0; + memcpy(name.get() + 1, aName.BeginReading(), aName.Length()); + nsresult rv = trans->InitWithName(name.get(), aName.Length() + 1); + if (NS_FAILED(rv)) { + return rv; + } + + trans.forget(result); + return NS_OK; +#else + return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; +#endif +} + +NS_IMETHODIMP +nsSocketTransportService::OnDispatchedEvent() { +#ifndef XP_WIN + // On windows poll can hang and this became worse when we introduced the + // patch for bug 698882 (see also bug 1292181), therefore we reverted the + // behavior on windows to be as before bug 698882, e.g. write to the socket + // also if an event dispatch is on the socket thread and writing to the + // socket for each event. + if (OnSocketThread()) { + // this check is redundant to one done inside ::Signal(), but + // we can do it here and skip obtaining the lock - given that + // this is a relatively common occurance its worth the + // redundant code + SOCKET_LOG(("OnDispatchedEvent Same Thread Skip Signal\n")); + return NS_OK; + } +#else + if (gIOService->IsNetTearingDown()) { + // Poll can hang sometimes. If we are in shutdown, we are going to + // start a watchdog. If we do not exit poll within + // REPAIR_POLLABLE_EVENT_TIME signal a pollable event again. + StartPollWatchdog(); + } +#endif + + MutexAutoLock lock(mLock); + if (mPollableEvent) { + mPollableEvent->Signal(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::OnProcessNextEvent(nsIThreadInternal* thread, + bool mayWait) { + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::AfterProcessNextEvent(nsIThreadInternal* thread, + bool eventWasProcessed) { + return NS_OK; +} + +void nsSocketTransportService::MarkTheLastElementOfPendingQueue() { + mServingPendingQueue = false; +} + +NS_IMETHODIMP +nsSocketTransportService::Run() { + SOCKET_LOG(("STS thread init %d sockets\n", gMaxCount)); + +#if defined(XP_WIN) + // see bug 1361495, gethostname() triggers winsock initialization. + // so do it here (on parent and child) to protect against it being done first + // accidentally on the main thread.. especially via PR_GetSystemInfo(). This + // will also improve latency of first real winsock operation + // .. + // If STS-thread is no longer needed this should still be run before exiting + + char ignoredStackBuffer[255]; + Unused << gethostname(ignoredStackBuffer, 255); +#endif + + psm::InitializeSSLServerCertVerificationThreads(); + + gSocketThread = PR_GetCurrentThread(); + + { + // See bug 1843384: + // Avoid blocking the main thread by allocating the PollableEvent outside + // the mutex. Still has the potential to hang the socket thread, but the + // main thread remains responsive. + PollableEvent* pollable = new PollableEvent(); + MutexAutoLock lock(mLock); + mPollableEvent.reset(pollable); + + // + // NOTE: per bug 190000, this failure could be caused by Zone-Alarm + // or similar software. + // + // NOTE: per bug 191739, this failure could also be caused by lack + // of a loopback device on Windows and OS/2 platforms (it creates + // a loopback socket pair on these platforms to implement a pollable + // event object). if we can't create a pollable event, then we'll + // have to "busy wait" to implement the socket event queue :-( + // + if (!mPollableEvent->Valid()) { + mPollableEvent = nullptr; + NS_WARNING("running socket transport thread without a pollable event"); + SOCKET_LOG(("running socket transport thread without a pollable event")); + } + + PRPollDesc entry = {mPollableEvent ? mPollableEvent->PollableFD() : nullptr, + PR_POLL_READ | PR_POLL_EXCEPT, 0}; + SOCKET_LOG(("Setting entry 0")); + mPollList[0] = entry; + } + + mRawThread = NS_GetCurrentThread(); + + // Ensure a call to GetCurrentSerialEventTarget() returns this event target. + SerialEventTargetGuard guard(this); + + // hook ourselves up to observe event processing for this thread + nsCOMPtr<nsIThreadInternal> threadInt = do_QueryInterface(mRawThread); + threadInt->SetObserver(this); + + // make sure the pseudo random number generator is seeded on this thread + srand(static_cast<unsigned>(PR_Now())); + + // For the calculation of the duration of the last cycle (i.e. the last + // for-loop iteration before shutdown). + TimeStamp startOfCycleForLastCycleCalc; + + // For measuring of the poll iteration duration without time spent blocked + // in poll(). + TimeStamp pollCycleStart; + // Time blocked in poll(). + TimeDuration singlePollDuration; + + // For calculating the time needed for a new element to run. + TimeStamp startOfIteration; + TimeStamp startOfNextIteration; + + // If there is too many pending events queued, we will run some poll() + // between them and the following variable is cumulative time spent + // blocking in poll(). + TimeDuration pollDuration; + + for (;;) { + bool pendingEvents = false; + if (Telemetry::CanRecordPrereleaseData()) { + startOfCycleForLastCycleCalc = TimeStamp::NowLoRes(); + startOfNextIteration = TimeStamp::NowLoRes(); + } + pollDuration = nullptr; + // We pop out to this loop when there are no pending events. + // If we don't reset these, we may not re-enter ProcessNextEvent() + // until we have events to process, and it may seem like we have + // an event running for a very long time. + mRawThread->SetRunningEventDelay(TimeDuration(), TimeStamp()); + + do { + if (Telemetry::CanRecordPrereleaseData()) { + pollCycleStart = TimeStamp::NowLoRes(); + } + + DoPollIteration(&singlePollDuration); + + if (Telemetry::CanRecordPrereleaseData() && !pollCycleStart.IsNull()) { + Telemetry::Accumulate(Telemetry::STS_POLL_BLOCK_TIME, + singlePollDuration.ToMilliseconds()); + Telemetry::AccumulateTimeDelta(Telemetry::STS_POLL_CYCLE, + pollCycleStart + singlePollDuration, + TimeStamp::NowLoRes()); + pollDuration += singlePollDuration; + } + + mRawThread->HasPendingEvents(&pendingEvents); + if (pendingEvents) { + if (!mServingPendingQueue) { + nsresult rv = Dispatch( + NewRunnableMethod( + "net::nsSocketTransportService::" + "MarkTheLastElementOfPendingQueue", + this, + &nsSocketTransportService::MarkTheLastElementOfPendingQueue), + nsIEventTarget::DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING( + "Could not dispatch a new event on the " + "socket thread."); + } else { + mServingPendingQueue = true; + } + + if (Telemetry::CanRecordPrereleaseData()) { + startOfIteration = startOfNextIteration; + // Everything that comes after this point will + // be served in the next iteration. If no even + // arrives, startOfNextIteration will be reset at the + // beginning of each for-loop. + startOfNextIteration = TimeStamp::NowLoRes(); + } + } + TimeStamp eventQueueStart = TimeStamp::NowLoRes(); + do { + NS_ProcessNextEvent(mRawThread); + pendingEvents = false; + mRawThread->HasPendingEvents(&pendingEvents); + } while (pendingEvents && mServingPendingQueue && + ((TimeStamp::NowLoRes() - eventQueueStart).ToMilliseconds() < + mMaxTimePerPollIter)); + + if (Telemetry::CanRecordPrereleaseData() && !mServingPendingQueue && + !startOfIteration.IsNull()) { + Telemetry::AccumulateTimeDelta(Telemetry::STS_POLL_AND_EVENTS_CYCLE, + startOfIteration + pollDuration, + TimeStamp::NowLoRes()); + pollDuration = nullptr; + } + } + } while (pendingEvents); + + bool goingOffline = false; + // now that our event queue is empty, check to see if we should exit + if (mShuttingDown) { + if (Telemetry::CanRecordPrereleaseData() && + !startOfCycleForLastCycleCalc.IsNull()) { + Telemetry::AccumulateTimeDelta( + Telemetry::STS_POLL_AND_EVENT_THE_LAST_CYCLE, + startOfCycleForLastCycleCalc, TimeStamp::NowLoRes()); + } + break; + } + { + MutexAutoLock lock(mLock); + if (mGoingOffline) { + mGoingOffline = false; + goingOffline = true; + } + } + // Avoid potential deadlock + if (goingOffline) { + Reset(true); + } + } + + SOCKET_LOG(("STS shutting down thread\n")); + + // detach all sockets, including locals + Reset(false); + + // We don't clear gSocketThread so that OnSocketThread() won't be a false + // alarm for events generated by stopping the SSL threads during shutdown. + psm::StopSSLServerCertVerificationThreads(); + + // Final pass over the event queue. This makes sure that events posted by + // socket detach handlers get processed. + NS_ProcessPendingEvents(mRawThread); + + SOCKET_LOG(("STS thread exit\n")); + MOZ_ASSERT(mPollList.Length() == 1); + MOZ_ASSERT(mActiveList.IsEmpty()); + MOZ_ASSERT(mIdleList.IsEmpty()); + + return NS_OK; +} + +void nsSocketTransportService::DetachSocketWithGuard( + bool aGuardLocals, SocketContextList& socketList, int32_t index) { + bool isGuarded = false; + if (aGuardLocals) { + socketList[index].mHandler->IsLocal(&isGuarded); + if (!isGuarded) { + socketList[index].mHandler->KeepWhenOffline(&isGuarded); + } + } + if (!isGuarded) { + DetachSocket(socketList, &socketList[index]); + } +} + +void nsSocketTransportService::Reset(bool aGuardLocals) { + // detach any sockets + int32_t i; + for (i = mActiveList.Length() - 1; i >= 0; --i) { + DetachSocketWithGuard(aGuardLocals, mActiveList, i); + } + for (i = mIdleList.Length() - 1; i >= 0; --i) { + DetachSocketWithGuard(aGuardLocals, mIdleList, i); + } +} + +nsresult nsSocketTransportService::DoPollIteration(TimeDuration* pollDuration) { + SOCKET_LOG(("STS poll iter\n")); + + PRIntervalTime now = PR_IntervalNow(); + + // We can't have more than int32_max sockets in use + int32_t i, count; + // + // poll loop + // + // walk active list backwards to see if any sockets should actually be + // idle, then walk the idle list backwards to see if any idle sockets + // should become active. take care to check only idle sockets that + // were idle to begin with ;-) + // + count = mIdleList.Length(); + for (i = mActiveList.Length() - 1; i >= 0; --i) { + //--- + SOCKET_LOG((" active [%u] { handler=%p condition=%" PRIx32 + " pollflags=%hu }\n", + i, mActiveList[i].mHandler.get(), + static_cast<uint32_t>(mActiveList[i].mHandler->mCondition), + mActiveList[i].mHandler->mPollFlags)); + //--- + if (NS_FAILED(mActiveList[i].mHandler->mCondition)) { + DetachSocket(mActiveList, &mActiveList[i]); + } else { + uint16_t in_flags = mActiveList[i].mHandler->mPollFlags; + if (in_flags == 0) { + MoveToIdleList(&mActiveList[i]); + } else { + // update poll flags + mPollList[i + 1].in_flags = in_flags; + mPollList[i + 1].out_flags = 0; + mActiveList[i].EnsureTimeout(now); + } + } + } + for (i = count - 1; i >= 0; --i) { + //--- + SOCKET_LOG((" idle [%u] { handler=%p condition=%" PRIx32 + " pollflags=%hu }\n", + i, mIdleList[i].mHandler.get(), + static_cast<uint32_t>(mIdleList[i].mHandler->mCondition), + mIdleList[i].mHandler->mPollFlags)); + //--- + if (NS_FAILED(mIdleList[i].mHandler->mCondition)) { + DetachSocket(mIdleList, &mIdleList[i]); + } else if (mIdleList[i].mHandler->mPollFlags != 0) { + MoveToPollList(&mIdleList[i]); + } + } + + { + MutexAutoLock lock(mLock); + if (mPollableEvent) { + // we want to make sure the timeout is measured from the time + // we enter poll(). This method resets the timestamp to 'now', + // if we were first signalled between leaving poll() and here. + // If we didn't do this and processing events took longer than + // the allowed signal timeout, we would detect it as a + // false-positive. AdjustFirstSignalTimestamp is then a no-op + // until mPollableEvent->Clear() is called. + mPollableEvent->AdjustFirstSignalTimestamp(); + } + } + + SOCKET_LOG((" calling PR_Poll [active=%zu idle=%zu]\n", mActiveList.Length(), + mIdleList.Length())); + + // Measures seconds spent while blocked on PR_Poll + int32_t n = 0; + *pollDuration = nullptr; + + if (!gIOService->IsNetTearingDown()) { + // Let's not do polling during shutdown. +#if defined(XP_WIN) + StartPolling(); +#endif + n = Poll(pollDuration, now); +#if defined(XP_WIN) + EndPolling(); +#endif + } + + now = PR_IntervalNow(); +#ifdef MOZ_GECKO_PROFILER + TimeStamp startTime; + bool profiling = profiler_thread_is_being_profiled_for_markers(); + if (profiling) { + startTime = TimeStamp::Now(); + } +#endif + + if (n < 0) { + SOCKET_LOG((" PR_Poll error [%d] os error [%d]\n", PR_GetError(), + PR_GetOSError())); + } else { + // + // service "active" sockets... + // + for (i = 0; i < int32_t(mActiveList.Length()); ++i) { + PRPollDesc& desc = mPollList[i + 1]; + SocketContext& s = mActiveList[i]; + if (n > 0 && desc.out_flags != 0) { + s.DisengageTimeout(); + s.mHandler->OnSocketReady(desc.fd, desc.out_flags); + } else if (s.IsTimedOut(now)) { + SOCKET_LOG(("socket %p timed out", s.mHandler.get())); + s.DisengageTimeout(); + s.mHandler->OnSocketReady(desc.fd, -1); + } else { + s.MaybeResetEpoch(); + } + } + // + // check for "dead" sockets and remove them (need to do this in + // reverse order obviously). + // + for (i = mActiveList.Length() - 1; i >= 0; --i) { + if (NS_FAILED(mActiveList[i].mHandler->mCondition)) { + DetachSocket(mActiveList, &mActiveList[i]); + } + } + + { + MutexAutoLock lock(mLock); + // acknowledge pollable event (should not block) + if (n != 0 && + (mPollList[0].out_flags & (PR_POLL_READ | PR_POLL_EXCEPT)) && + mPollableEvent && + ((mPollList[0].out_flags & PR_POLL_EXCEPT) || + !mPollableEvent->Clear())) { + // On Windows, the TCP loopback connection in the + // pollable event may become broken when a laptop + // switches between wired and wireless networks or + // wakes up from hibernation. We try to create a + // new pollable event. If that fails, we fall back + // on "busy wait". + TryRepairPollableEvent(); + } + + if (mPollableEvent && + !mPollableEvent->IsSignallingAlive(mPollableEventTimeout)) { + SOCKET_LOG(("Pollable event signalling failed/timed out")); + TryRepairPollableEvent(); + } + } + } +#ifdef MOZ_GECKO_PROFILER + if (profiling) { + TimeStamp endTime = TimeStamp::Now(); + if ((endTime - startTime).ToMilliseconds() >= SOCKET_THREAD_LONGTASK_MS) { + struct LongTaskMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("SocketThreadLongTask"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter) { + aWriter.StringProperty("category", "LongTask"); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormatSearchable("category", "Type", + MS::Format::String, + MS::Searchable::Searchable); + return schema; + } + }; + + profiler_add_marker(ProfilerString8View("LongTaskSocketProcessing"), + geckoprofiler::category::OTHER, + MarkerTiming::Interval(startTime, endTime), + LongTaskMarker{}); + } + } + +#endif + + return NS_OK; +} + +void nsSocketTransportService::UpdateSendBufferPref() { + int32_t bufferSize; + + // If the pref is set, honor it. 0 means use OS defaults. + nsresult rv = Preferences::GetInt(SEND_BUFFER_PREF, &bufferSize); + if (NS_SUCCEEDED(rv)) { + mSendBufferSize = bufferSize; + return; + } + +#if defined(XP_WIN) + mSendBufferSize = 131072 * 4; +#endif +} + +nsresult nsSocketTransportService::UpdatePrefs() { + mSendBufferSize = 0; + + UpdateSendBufferPref(); + + // Default TCP Keepalive Values. + int32_t keepaliveIdleTimeS; + nsresult rv = + Preferences::GetInt(KEEPALIVE_IDLE_TIME_PREF, &keepaliveIdleTimeS); + if (NS_SUCCEEDED(rv)) { + mKeepaliveIdleTimeS = clamped(keepaliveIdleTimeS, 1, kMaxTCPKeepIdle); + } + + int32_t keepaliveRetryIntervalS; + rv = Preferences::GetInt(KEEPALIVE_RETRY_INTERVAL_PREF, + &keepaliveRetryIntervalS); + if (NS_SUCCEEDED(rv)) { + mKeepaliveRetryIntervalS = + clamped(keepaliveRetryIntervalS, 1, kMaxTCPKeepIntvl); + } + + int32_t keepaliveProbeCount; + rv = Preferences::GetInt(KEEPALIVE_PROBE_COUNT_PREF, &keepaliveProbeCount); + if (NS_SUCCEEDED(rv)) { + mKeepaliveProbeCount = clamped(keepaliveProbeCount, 1, kMaxTCPKeepCount); + } + bool keepaliveEnabled = false; + rv = Preferences::GetBool(KEEPALIVE_ENABLED_PREF, &keepaliveEnabled); + if (NS_SUCCEEDED(rv) && keepaliveEnabled != mKeepaliveEnabledPref) { + mKeepaliveEnabledPref = keepaliveEnabled; + OnKeepaliveEnabledPrefChange(); + } + + int32_t maxTimePref; + rv = Preferences::GetInt(MAX_TIME_BETWEEN_TWO_POLLS, &maxTimePref); + if (NS_SUCCEEDED(rv) && maxTimePref >= 0) { + mMaxTimePerPollIter = maxTimePref; + } + + int32_t pollBusyWaitPeriod; + rv = Preferences::GetInt(POLL_BUSY_WAIT_PERIOD, &pollBusyWaitPeriod); + if (NS_SUCCEEDED(rv) && pollBusyWaitPeriod > 0) { + mNetworkLinkChangeBusyWaitPeriod = PR_SecondsToInterval(pollBusyWaitPeriod); + } + + int32_t pollBusyWaitPeriodTimeout; + rv = Preferences::GetInt(POLL_BUSY_WAIT_PERIOD_TIMEOUT, + &pollBusyWaitPeriodTimeout); + if (NS_SUCCEEDED(rv) && pollBusyWaitPeriodTimeout > 0) { + mNetworkLinkChangeBusyWaitTimeout = + PR_SecondsToInterval(pollBusyWaitPeriodTimeout); + } + + int32_t maxTimeForPrClosePref; + rv = Preferences::GetInt(MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN, + &maxTimeForPrClosePref); + if (NS_SUCCEEDED(rv) && maxTimeForPrClosePref >= 0) { + mMaxTimeForPrClosePref = PR_MillisecondsToInterval(maxTimeForPrClosePref); + } + + int32_t pollableEventTimeout; + rv = Preferences::GetInt(POLLABLE_EVENT_TIMEOUT, &pollableEventTimeout); + if (NS_SUCCEEDED(rv) && pollableEventTimeout >= 0) { + MutexAutoLock lock(mLock); + mPollableEventTimeout = TimeDuration::FromSeconds(pollableEventTimeout); + } + + nsAutoCString portMappingPref; + rv = Preferences::GetCString("network.socket.forcePort", portMappingPref); + if (NS_SUCCEEDED(rv)) { + bool rv = UpdatePortRemapPreference(portMappingPref); + if (!rv) { + NS_ERROR( + "network.socket.forcePort preference is ill-formed, this will likely " + "make everything unexpectedly fail!"); + } + } + + return NS_OK; +} + +void nsSocketTransportService::OnKeepaliveEnabledPrefChange() { + // Dispatch to socket thread if we're not executing there. + if (!OnSocketThread()) { + gSocketTransportService->Dispatch( + NewRunnableMethod( + "net::nsSocketTransportService::OnKeepaliveEnabledPrefChange", this, + &nsSocketTransportService::OnKeepaliveEnabledPrefChange), + NS_DISPATCH_NORMAL); + return; + } + + SOCKET_LOG(("nsSocketTransportService::OnKeepaliveEnabledPrefChange %s", + mKeepaliveEnabledPref ? "enabled" : "disabled")); + + // Notify each socket that keepalive has been en/disabled globally. + for (int32_t i = mActiveList.Length() - 1; i >= 0; --i) { + NotifyKeepaliveEnabledPrefChange(&mActiveList[i]); + } + for (int32_t i = mIdleList.Length() - 1; i >= 0; --i) { + NotifyKeepaliveEnabledPrefChange(&mIdleList[i]); + } +} + +void nsSocketTransportService::NotifyKeepaliveEnabledPrefChange( + SocketContext* sock) { + MOZ_ASSERT(sock, "SocketContext cannot be null!"); + MOZ_ASSERT(sock->mHandler, "SocketContext does not have a handler!"); + + if (!sock || !sock->mHandler) { + return; + } + + sock->mHandler->OnKeepaliveEnabledPrefChange(mKeepaliveEnabledPref); +} + +NS_IMETHODIMP +nsSocketTransportService::GetName(nsACString& aName) { + aName.AssignLiteral("nsSocketTransportService"); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::Observe(nsISupports* subject, const char* topic, + const char16_t* data) { + SOCKET_LOG(("nsSocketTransportService::Observe topic=%s", topic)); + + if (!strcmp(topic, "profile-initial-state")) { + if (!Preferences::GetBool(IO_ACTIVITY_ENABLED_PREF, false)) { + return NS_OK; + } + return net::IOActivityMonitor::Init(); + } + + if (!strcmp(topic, "last-pb-context-exited")) { + nsCOMPtr<nsIRunnable> ev = NewRunnableMethod( + "net::nsSocketTransportService::ClosePrivateConnections", this, + &nsSocketTransportService::ClosePrivateConnections); + nsresult rv = Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) { + nsCOMPtr<nsITimer> timer = do_QueryInterface(subject); + if (timer == mAfterWakeUpTimer) { + mAfterWakeUpTimer = nullptr; + mSleepPhase = false; + } + +#if defined(XP_WIN) + if (timer == mPollRepairTimer) { + DoPollRepair(); + } +#endif + + } else if (!strcmp(topic, NS_WIDGET_SLEEP_OBSERVER_TOPIC)) { + mSleepPhase = true; + if (mAfterWakeUpTimer) { + mAfterWakeUpTimer->Cancel(); + mAfterWakeUpTimer = nullptr; + } + } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) { + if (mSleepPhase && !mAfterWakeUpTimer) { + NS_NewTimerWithObserver(getter_AddRefs(mAfterWakeUpTimer), this, 2000, + nsITimer::TYPE_ONE_SHOT); + } + } else if (!strcmp(topic, "xpcom-shutdown-threads")) { + ShutdownThread(); + } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) { + mLastNetworkLinkChangeTime = PR_IntervalNow(); + } + + return NS_OK; +} + +void nsSocketTransportService::ClosePrivateConnections() { + MOZ_ASSERT(IsOnCurrentThread(), "Must be called on the socket thread"); + + for (int32_t i = mActiveList.Length() - 1; i >= 0; --i) { + if (mActiveList[i].mHandler->mIsPrivate) { + DetachSocket(mActiveList, &mActiveList[i]); + } + } + for (int32_t i = mIdleList.Length() - 1; i >= 0; --i) { + if (mIdleList[i].mHandler->mIsPrivate) { + DetachSocket(mIdleList, &mIdleList[i]); + } + } + + ClearPrivateSSLState(); +} + +NS_IMETHODIMP +nsSocketTransportService::GetSendBufferSize(int32_t* value) { + *value = mSendBufferSize; + return NS_OK; +} + +/// ugly OS specific includes are placed at the bottom of the src for clarity + +#if defined(XP_WIN) +# include <windows.h> +#elif defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX) +# include <sys/resource.h> +#endif + +PRStatus nsSocketTransportService::DiscoverMaxCount() { + gMaxCount = SOCKET_LIMIT_MIN; + +#if defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX) + // On unix and os x network sockets and file + // descriptors are the same. OS X comes defaulted at 256, + // most linux at 1000. We can reliably use [sg]rlimit to + // query that and raise it if needed. + + struct rlimit rlimitData {}; + if (getrlimit(RLIMIT_NOFILE, &rlimitData) == -1) { // rlimit broken - use min + return PR_SUCCESS; + } + + if (rlimitData.rlim_cur >= SOCKET_LIMIT_TARGET) { // larger than target! + gMaxCount = SOCKET_LIMIT_TARGET; + return PR_SUCCESS; + } + + int32_t maxallowed = rlimitData.rlim_max; + if ((uint32_t)maxallowed <= SOCKET_LIMIT_MIN) { + return PR_SUCCESS; // so small treat as if rlimit is broken + } + + if ((maxallowed == -1) || // no hard cap - ok to set target + ((uint32_t)maxallowed >= SOCKET_LIMIT_TARGET)) { + maxallowed = SOCKET_LIMIT_TARGET; + } + + rlimitData.rlim_cur = maxallowed; + setrlimit(RLIMIT_NOFILE, &rlimitData); + if ((getrlimit(RLIMIT_NOFILE, &rlimitData) != -1) && + (rlimitData.rlim_cur > SOCKET_LIMIT_MIN)) { + gMaxCount = rlimitData.rlim_cur; + } + +#elif defined(XP_WIN) && !defined(WIN_CE) + // >= XP is confirmed to have at least 1000 + static_assert(SOCKET_LIMIT_TARGET <= 1000, + "SOCKET_LIMIT_TARGET max value is 1000"); + gMaxCount = SOCKET_LIMIT_TARGET; +#else + // other platforms are harder to test - so leave at safe legacy value +#endif + + return PR_SUCCESS; +} + +// Used to return connection info to Dashboard.cpp +void nsSocketTransportService::AnalyzeConnection(nsTArray<SocketInfo>* data, + SocketContext* context, + bool aActive) { + if (context->mHandler->mIsPrivate) { + return; + } + PRFileDesc* aFD = context->mFD; + + PRFileDesc* idLayer = PR_GetIdentitiesLayer(aFD, PR_NSPR_IO_LAYER); + + NS_ENSURE_TRUE_VOID(idLayer); + + PRDescType type = PR_GetDescType(idLayer); + char host[64] = {0}; + uint16_t port; + const char* type_desc; + + if (type == PR_DESC_SOCKET_TCP) { + type_desc = "TCP"; + PRNetAddr peer_addr; + PodZero(&peer_addr); + + PRStatus rv = PR_GetPeerName(aFD, &peer_addr); + if (rv != PR_SUCCESS) { + return; + } + + rv = PR_NetAddrToString(&peer_addr, host, sizeof(host)); + if (rv != PR_SUCCESS) { + return; + } + + if (peer_addr.raw.family == PR_AF_INET) { + port = peer_addr.inet.port; + } else { + port = peer_addr.ipv6.port; + } + port = PR_ntohs(port); + } else { + if (type == PR_DESC_SOCKET_UDP) { + type_desc = "UDP"; + } else { + type_desc = "other"; + } + NetAddr addr; + if (context->mHandler->GetRemoteAddr(&addr) != NS_OK) { + return; + } + if (!addr.ToStringBuffer(host, sizeof(host))) { + return; + } + if (addr.GetPort(&port) != NS_OK) { + return; + } + } + + uint64_t sent = context->mHandler->ByteCountSent(); + uint64_t received = context->mHandler->ByteCountReceived(); + SocketInfo info = {nsCString(host), sent, received, port, aActive, + nsCString(type_desc)}; + + data->AppendElement(info); +} + +void nsSocketTransportService::GetSocketConnections( + nsTArray<SocketInfo>* data) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + for (uint32_t i = 0; i < mActiveList.Length(); i++) { + AnalyzeConnection(data, &mActiveList[i], true); + } + for (uint32_t i = 0; i < mIdleList.Length(); i++) { + AnalyzeConnection(data, &mIdleList[i], false); + } +} + +bool nsSocketTransportService::IsTelemetryEnabledAndNotSleepPhase() { + return Telemetry::CanRecordPrereleaseData() && !mSleepPhase; +} + +#if defined(XP_WIN) +void nsSocketTransportService::StartPollWatchdog() { + // Start off the timer from a runnable off of the main thread in order to + // avoid a deadlock, see bug 1370448. + RefPtr<nsSocketTransportService> self(this); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsSocketTransportService::StartPollWatchdog", [self] { + MutexAutoLock lock(self->mLock); + + // Poll can hang sometimes. If we are in shutdown, we are going to start + // a watchdog. If we do not exit poll within REPAIR_POLLABLE_EVENT_TIME + // signal a pollable event again. + if (gIOService->IsNetTearingDown() && self->mPolling && + !self->mPollRepairTimer) { + NS_NewTimerWithObserver(getter_AddRefs(self->mPollRepairTimer), self, + REPAIR_POLLABLE_EVENT_TIME, + nsITimer::TYPE_REPEATING_SLACK); + } + })); +} + +void nsSocketTransportService::DoPollRepair() { + MutexAutoLock lock(mLock); + if (mPolling && mPollableEvent) { + mPollableEvent->Signal(); + } else if (mPollRepairTimer) { + mPollRepairTimer->Cancel(); + } +} + +void nsSocketTransportService::StartPolling() { + MutexAutoLock lock(mLock); + mPolling = true; +} + +void nsSocketTransportService::EndPolling() { + MutexAutoLock lock(mLock); + mPolling = false; + if (mPollRepairTimer) { + mPollRepairTimer->Cancel(); + } +} + +#endif + +void nsSocketTransportService::TryRepairPollableEvent() { + mLock.AssertCurrentThreadOwns(); + + NS_WARNING("Trying to repair mPollableEvent"); + mPollableEvent.reset(new PollableEvent()); + if (!mPollableEvent->Valid()) { + mPollableEvent = nullptr; + } + SOCKET_LOG( + ("running socket transport thread without " + "a pollable event now valid=%d", + !!mPollableEvent)); + mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr; + mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT; + mPollList[0].out_flags = 0; +} + +NS_IMETHODIMP +nsSocketTransportService::AddShutdownObserver( + nsISTSShutdownObserver* aObserver) { + mShutdownObservers.AppendElement(aObserver); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::RemoveShutdownObserver( + nsISTSShutdownObserver* aObserver) { + mShutdownObservers.RemoveElement(aObserver); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsSocketTransportService2.h b/netwerk/base/nsSocketTransportService2.h new file mode 100644 index 0000000000..4c4848044b --- /dev/null +++ b/netwerk/base/nsSocketTransportService2.h @@ -0,0 +1,355 @@ +/* vim:set ts=4 sw=2 sts=2 ci et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSocketTransportService2_h__ +#define nsSocketTransportService2_h__ + +#include "PollableEvent.h" +#include "mozilla/Atomics.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Logging.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" + +#include "mozilla/UniquePtr.h" +#include "mozilla/net/DashboardTypes.h" +#include "nsCOMPtr.h" +#include "nsASocketHandler.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsIObserver.h" +#include "nsIRunnable.h" +#include "nsIThreadInternal.h" +#include "nsITimer.h" +#include "nsPISocketTransportService.h" +#include "prinit.h" +#include "prinrval.h" + +struct PRPollDesc; +class nsIPrefBranch; + +//----------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +// +// set MOZ_LOG=nsSocketTransport:5 +// +extern LazyLogModule gSocketTransportLog; +#define SOCKET_LOG(args) MOZ_LOG(gSocketTransportLog, LogLevel::Debug, args) +#define SOCKET_LOG1(args) MOZ_LOG(gSocketTransportLog, LogLevel::Error, args) +#define SOCKET_LOG_ENABLED() MOZ_LOG_TEST(gSocketTransportLog, LogLevel::Debug) + +// +// set MOZ_LOG=UDPSocket:5 +// +extern LazyLogModule gUDPSocketLog; +#define UDPSOCKET_LOG(args) MOZ_LOG(gUDPSocketLog, LogLevel::Debug, args) +#define UDPSOCKET_LOG_ENABLED() MOZ_LOG_TEST(gUDPSocketLog, LogLevel::Debug) + +//----------------------------------------------------------------------------- + +#define NS_SOCKET_POLL_TIMEOUT PR_INTERVAL_NO_TIMEOUT + +//----------------------------------------------------------------------------- + +// These maximums are borrowed from the linux kernel. +static const int32_t kMaxTCPKeepIdle = 32767; // ~9 hours. +static const int32_t kMaxTCPKeepIntvl = 32767; +static const int32_t kMaxTCPKeepCount = 127; +static const int32_t kDefaultTCPKeepCount = +#if defined(XP_WIN) + 10; // Hardcoded in Windows. +#elif defined(XP_MACOSX) + 8; // Hardcoded in OSX. +#else + 4; // Specifiable in Linux. +#endif + +class LinkedRunnableEvent final + : public LinkedListElement<LinkedRunnableEvent> { + public: + explicit LinkedRunnableEvent(nsIRunnable* event) : mEvent(event) {} + ~LinkedRunnableEvent() = default; + + already_AddRefed<nsIRunnable> TakeEvent() { return mEvent.forget(); } + + private: + nsCOMPtr<nsIRunnable> mEvent; +}; + +//----------------------------------------------------------------------------- + +class nsSocketTransportService final : public nsPISocketTransportService, + public nsISerialEventTarget, + public nsIThreadObserver, + public nsIRunnable, + public nsIObserver, + public nsINamed, + public nsIDirectTaskDispatcher { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSPISOCKETTRANSPORTSERVICE + NS_DECL_NSISOCKETTRANSPORTSERVICE + NS_DECL_NSIROUTEDSOCKETTRANSPORTSERVICE + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSITHREADOBSERVER + NS_DECL_NSIRUNNABLE + NS_DECL_NSIOBSERVER + NS_DECL_NSINAMED + NS_DECL_NSIDIRECTTASKDISPATCHER + + static const uint32_t SOCKET_LIMIT_MIN = 50U; + + nsSocketTransportService(); + + // Max Socket count may need to get initialized/used by nsHttpHandler + // before this class is initialized. + static uint32_t gMaxCount; + static PRCallOnceType gMaxCountInitOnce; + static PRStatus DiscoverMaxCount(); + + bool CanAttachSocket(); + + // Called by the networking dashboard on the socket thread only + // Fills the passed array with socket information + void GetSocketConnections(nsTArray<SocketInfo>*); + uint64_t GetSentBytes() { return mSentBytesCount; } + uint64_t GetReceivedBytes() { return mReceivedBytesCount; } + + // Returns true if keepalives are enabled in prefs. + bool IsKeepaliveEnabled() { return mKeepaliveEnabledPref; } + + bool IsTelemetryEnabledAndNotSleepPhase(); + PRIntervalTime MaxTimeForPrClosePref() { return mMaxTimeForPrClosePref; } + + // According the preference value of `network.socket.forcePort` this method + // possibly remaps the port number passed as the arg. + void ApplyPortRemap(uint16_t* aPort); + + // Reads the preference string and updates (rewrites) the mPortRemapping + // array on the socket thread. Returns true if the whole pref string was + // correctly formed. + bool UpdatePortRemapPreference(nsACString const& aPortMappingPref); + + protected: + virtual ~nsSocketTransportService(); + + private: + //------------------------------------------------------------------------- + // misc (any thread) + //------------------------------------------------------------------------- + + // The value is guaranteed to be valid and not dangling while on the socket + // thread as mThread is only ever reset after it's been shutdown. + // This member should only ever be read on the socket thread. + nsIThread* mRawThread{nullptr}; + + // Returns mThread in a thread-safe manner. + already_AddRefed<nsIThread> GetThreadSafely(); + // Same as above, but return mThread as a nsIDirectTaskDispatcher + already_AddRefed<nsIDirectTaskDispatcher> GetDirectTaskDispatcherSafely(); + + //------------------------------------------------------------------------- + // initialization and shutdown (any thread) + //------------------------------------------------------------------------- + + Atomic<bool> mInitialized{false}; + // indicates whether we are currently in the process of shutting down + Atomic<bool> mShuttingDown{false}; + + Mutex mLock{"nsSocketTransportService::mLock"}; + // Variables in the next section protected by mLock + + // mThread and mDirectTaskDispatcher are only ever modified on the main + // thread. Will be set on Init and set to null after shutdown. You must access + // mThread and mDirectTaskDispatcher outside the main thread via respectively + // GetThreadSafely and GetDirectTaskDispatchedSafely(). + nsCOMPtr<nsIThread> mThread MOZ_GUARDED_BY(mLock); + // We store a pointer to mThread as a direct task dispatcher to avoid having + // to do do_QueryInterface whenever we need to access the interface. + nsCOMPtr<nsIDirectTaskDispatcher> mDirectTaskDispatcher MOZ_GUARDED_BY(mLock); + UniquePtr<PollableEvent> mPollableEvent MOZ_GUARDED_BY(mLock); + bool mOffline MOZ_GUARDED_BY(mLock) = false; + bool mGoingOffline MOZ_GUARDED_BY(mLock) = false; + + // Detaches all sockets. + void Reset(bool aGuardLocals); + + nsresult ShutdownThread(); + + //------------------------------------------------------------------------- + // socket lists (socket thread only) + // + // only "active" sockets are on the poll list. the active list is kept + // in sync with the poll list such that: + // + // mActiveList[k].mFD == mPollList[k+1].fd + // + // where k=0,1,2,... + //------------------------------------------------------------------------- + + class SocketContext { + public: + SocketContext(PRFileDesc* aFD, + already_AddRefed<nsASocketHandler>&& aHandler, + PRIntervalTime aPollStartEpoch) + : mFD(aFD), mHandler(aHandler), mPollStartEpoch(aPollStartEpoch) {} + SocketContext(PRFileDesc* aFD, nsASocketHandler* aHandler, + PRIntervalTime aPollStartEpoch) + : mFD(aFD), mHandler(aHandler), mPollStartEpoch(aPollStartEpoch) {} + ~SocketContext() = default; + + // Returns true iff the socket has not been signalled longer than + // the desired timeout (mHandler->mPollTimeout). + bool IsTimedOut(PRIntervalTime now) const; + // Engages the timeout by marking the epoch we start polling this socket. + // If epoch is already marked this does nothing, hence, this method can be + // called everytime we put this socket to poll() list with in-flags set. + void EnsureTimeout(PRIntervalTime now); + // Called after an event on a socket has been signalled to turn of the + // timeout calculation. + void DisengageTimeout(); + // Returns the number of intervals this socket is about to timeout in, + // or 0 (zero) when it has already timed out. Returns + // NS_SOCKET_POLL_TIMEOUT when there is no timeout set on the socket. + PRIntervalTime TimeoutIn(PRIntervalTime now) const; + // When a socket timeout is reset and later set again, it may happen + // that mPollStartEpoch is not reset in between. We have to manually + // call this on every iteration over sockets to ensure the epoch reset. + void MaybeResetEpoch(); + + PRFileDesc* mFD; + RefPtr<nsASocketHandler> mHandler; + PRIntervalTime mPollStartEpoch; // time we started to poll this socket + }; + + using SocketContextList = AutoTArray<SocketContext, SOCKET_LIMIT_MIN>; + int64_t SockIndex(SocketContextList& aList, SocketContext* aSock); + + SocketContextList mActiveList; + SocketContextList mIdleList; + + nsresult DetachSocket(SocketContextList& listHead, SocketContext*); + void AddToIdleList(SocketContext* sock); + void AddToPollList(SocketContext* sock); + void RemoveFromIdleList(SocketContext* sock); + void RemoveFromPollList(SocketContext* sock); + void MoveToIdleList(SocketContext* sock); + void MoveToPollList(SocketContext* sock); + + void InitMaxCount(); + + // Total bytes number transfered through all the sockets except active ones + uint64_t mSentBytesCount{0}; + uint64_t mReceivedBytesCount{0}; + //------------------------------------------------------------------------- + // poll list (socket thread only) + // + // first element of the poll list is mPollableEvent (or null if the pollable + // event cannot be created). + //------------------------------------------------------------------------- + + nsTArray<PRPollDesc> mPollList; + + PRIntervalTime PollTimeout( + PRIntervalTime now); // computes ideal poll timeout + nsresult DoPollIteration(TimeDuration* pollDuration); + // perfoms a single poll iteration + int32_t Poll(TimeDuration* pollDuration, PRIntervalTime ts); + // calls PR_Poll. the out param + // interval indicates the poll + // duration in seconds. + // pollDuration is used only for + // telemetry + + //------------------------------------------------------------------------- + // pending socket queue - see NotifyWhenCanAttachSocket + //------------------------------------------------------------------------- + AutoCleanLinkedList<LinkedRunnableEvent> mPendingSocketQueue; + + // Preference Monitor for SendBufferSize and Keepalive prefs. + nsresult UpdatePrefs(); + static void UpdatePrefs(const char* aPref, void* aSelf); + void UpdateSendBufferPref(); + int32_t mSendBufferSize{0}; + // Number of seconds of connection is idle before first keepalive ping. + int32_t mKeepaliveIdleTimeS{600}; + // Number of seconds between retries should keepalive pings fail. + int32_t mKeepaliveRetryIntervalS{1}; + // Number of keepalive probes to send. + int32_t mKeepaliveProbeCount{kDefaultTCPKeepCount}; + // True if TCP keepalive is enabled globally. + bool mKeepaliveEnabledPref{false}; + // Timeout of pollable event signalling. + TimeDuration mPollableEventTimeout MOZ_GUARDED_BY(mLock); + + Atomic<bool> mServingPendingQueue{false}; + Atomic<int32_t, Relaxed> mMaxTimePerPollIter{100}; + Atomic<PRIntervalTime, Relaxed> mMaxTimeForPrClosePref; + // Timestamp of the last network link change event, tracked + // also on child processes. + Atomic<PRIntervalTime, Relaxed> mLastNetworkLinkChangeTime{0}; + // Preference for how long we do busy wait after network link + // change has been detected. + Atomic<PRIntervalTime, Relaxed> mNetworkLinkChangeBusyWaitPeriod; + // Preference for the value of timeout for poll() we use during + // the network link change event period. + Atomic<PRIntervalTime, Relaxed> mNetworkLinkChangeBusyWaitTimeout; + + // Between a computer going to sleep and waking up the PR_*** telemetry + // will be corrupted - so do not record it. + Atomic<bool, Relaxed> mSleepPhase{false}; + nsCOMPtr<nsITimer> mAfterWakeUpTimer; + + // Lazily created array of forced port remappings. The tuple members meaning + // is exactly: + // <0> the greater-or-equal port number of the range to remap + // <1> the less-or-equal port number of the range to remap + // <2> the port number to remap to, when the given port number falls to the + // range + using TPortRemapping = + CopyableTArray<std::tuple<uint16_t, uint16_t, uint16_t>>; + Maybe<TPortRemapping> mPortRemapping; + + // Called on the socket thread to apply the mapping build on the main thread + // from the preference. + void ApplyPortRemapPreference(TPortRemapping const& portRemapping); + + void OnKeepaliveEnabledPrefChange(); + void NotifyKeepaliveEnabledPrefChange(SocketContext* sock); + + // Report socket status to about:networking + void AnalyzeConnection(nsTArray<SocketInfo>* data, SocketContext* context, + bool aActive); + + void ClosePrivateConnections(); + void DetachSocketWithGuard(bool aGuardLocals, SocketContextList& socketList, + int32_t index); + + void MarkTheLastElementOfPendingQueue(); + +#if defined(XP_WIN) + Atomic<bool> mPolling{false}; + nsCOMPtr<nsITimer> mPollRepairTimer; + void StartPollWatchdog(); + void DoPollRepair(); + void StartPolling(); + void EndPolling(); +#endif + + void TryRepairPollableEvent(); + + CopyableTArray<nsCOMPtr<nsISTSShutdownObserver>> mShutdownObservers; +}; + +extern nsSocketTransportService* gSocketTransportService; +bool OnSocketThread(); + +} // namespace net +} // namespace mozilla + +#endif // !nsSocketTransportService_h__ diff --git a/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp new file mode 100644 index 0000000000..de3578c439 --- /dev/null +++ b/netwerk/base/nsStandardURL.cpp @@ -0,0 +1,4034 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ipc/IPCMessageUtils.h" + +#include "nsASCIIMask.h" +#include "nsStandardURL.h" +#include "nsCRT.h" +#include "nsEscape.h" +#include "nsIFile.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIIDNService.h" +#include "mozilla/Logging.h" +#include "nsIURLParser.h" +#include "nsPrintfCString.h" +#include "nsNetCID.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/TextUtils.h" +#include <algorithm> +#include "nsContentUtils.h" +#include "prprf.h" +#include "nsReadableUtils.h" +#include "mozilla/net/MozURL_ffi.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Utf8.h" +#include "nsIClassInfoImpl.h" +#include <string.h> + +// +// setenv MOZ_LOG nsStandardURL:5 +// +static mozilla::LazyLogModule gStandardURLLog("nsStandardURL"); + +// The Chromium code defines its own LOG macro which we don't want +#undef LOG +#define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args) +#undef LOG_ENABLED +#define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug) + +using namespace mozilla::ipc; + +namespace mozilla { +namespace net { + +static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID); + +// This will always be initialized and destroyed on the main thread, but +// can be safely used on other threads. +StaticRefPtr<nsIIDNService> nsStandardURL::gIDN; + +// This value will only be updated on the main thread once. +static Atomic<bool, Relaxed> gInitialized{false}; + +const char nsStandardURL::gHostLimitDigits[] = {'/', '\\', '?', '#', 0}; + +// Invalid host characters +// Note that the array below will be initialized at compile time, +// so we do not need to "optimize" TestForInvalidHostCharacters. +// +constexpr bool TestForInvalidHostCharacters(char c) { + // Testing for these: + // CONTROL_CHARACTERS " #/:?@[\\]*<>|\""; + return (c > 0 && c < 32) || // The control characters are [1, 31] + c == 0x7F || // // DEL (delete) + c == ' ' || c == '#' || c == '/' || c == ':' || c == '?' || c == '@' || + c == '[' || c == '\\' || c == ']' || c == '*' || c == '<' || + c == '^' || +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) + // Mailnews %-escapes file paths into URLs. + c == '>' || c == '|' || c == '"'; +#else + c == '>' || c == '|' || c == '"' || c == '%'; +#endif +} +constexpr ASCIIMaskArray sInvalidHostChars = + CreateASCIIMask(TestForInvalidHostCharacters); + +//---------------------------------------------------------------------------- +// nsStandardURL::nsSegmentEncoder +//---------------------------------------------------------------------------- + +nsStandardURL::nsSegmentEncoder::nsSegmentEncoder(const Encoding* encoding) + : mEncoding(encoding) { + if (mEncoding == UTF_8_ENCODING) { + mEncoding = nullptr; + } +} + +int32_t nsStandardURL::nsSegmentEncoder::EncodeSegmentCount( + const char* aStr, const URLSegment& aSeg, int16_t aMask, nsCString& aOut, + bool& aAppended, uint32_t aExtraLen) { + // aExtraLen is characters outside the segment that will be + // added when the segment is not empty (like the @ following + // a username). + if (!aStr || aSeg.mLen <= 0) { + // Empty segment, so aExtraLen not added per above. + aAppended = false; + return 0; + } + + uint32_t origLen = aOut.Length(); + + Span<const char> span = Span(aStr + aSeg.mPos, aSeg.mLen); + + // first honor the origin charset if appropriate. as an optimization, + // only do this if the segment is non-ASCII. Further, if mEncoding is + // null, then the origin charset is UTF-8 and there is nothing to do. + if (mEncoding) { + size_t upTo; + if (MOZ_UNLIKELY(mEncoding == ISO_2022_JP_ENCODING)) { + upTo = Encoding::ISO2022JPASCIIValidUpTo(AsBytes(span)); + } else { + upTo = Encoding::ASCIIValidUpTo(AsBytes(span)); + } + if (upTo != span.Length()) { + // we have to encode this segment + char bufferArr[512]; + Span<char> buffer = Span(bufferArr); + + auto encoder = mEncoding->NewEncoder(); + + nsAutoCString valid; // has to be declared in this scope + if (MOZ_UNLIKELY(!IsUtf8(span.From(upTo)))) { + MOZ_ASSERT_UNREACHABLE("Invalid UTF-8 passed to nsStandardURL."); + // It's UB to pass invalid UTF-8 to + // EncodeFromUTF8WithoutReplacement(), so let's make our input valid + // UTF-8 by replacing invalid sequences with the REPLACEMENT + // CHARACTER. + UTF_8_ENCODING->Decode( + nsDependentCSubstring(span.Elements(), span.Length()), valid); + // This assigment is OK. `span` can't be used after `valid` has + // been destroyed because the only way out of the scope that `valid` + // was declared in is via return inside the loop below. Specifically, + // the return is the only way out of the loop. + span = valid; + } + + size_t totalRead = 0; + for (;;) { + auto [encoderResult, read, written] = + encoder->EncodeFromUTF8WithoutReplacement( + AsBytes(span.From(totalRead)), AsWritableBytes(buffer), true); + totalRead += read; + auto bufferWritten = buffer.To(written); + if (!NS_EscapeURLSpan(bufferWritten, aMask, aOut)) { + aOut.Append(bufferWritten); + } + if (encoderResult == kInputEmpty) { + aAppended = true; + // Difference between original and current output + // string lengths plus extra length + return aOut.Length() - origLen + aExtraLen; + } + if (encoderResult == kOutputFull) { + continue; + } + aOut.AppendLiteral("%26%23"); + aOut.AppendInt(encoderResult); + aOut.AppendLiteral("%3B"); + } + MOZ_RELEASE_ASSERT( + false, + "There's supposed to be no way out of the above loop except return."); + } + } + + if (NS_EscapeURLSpan(span, aMask, aOut)) { + aAppended = true; + // Difference between original and current output + // string lengths plus extra length + return aOut.Length() - origLen + aExtraLen; + } + aAppended = false; + // Original segment length plus extra length + return span.Length() + aExtraLen; +} + +const nsACString& nsStandardURL::nsSegmentEncoder::EncodeSegment( + const nsACString& str, int16_t mask, nsCString& result) { + const char* text; + bool encoded; + EncodeSegmentCount(str.BeginReading(text), URLSegment(0, str.Length()), mask, + result, encoded); + if (encoded) { + return result; + } + return str; +} + +//---------------------------------------------------------------------------- +// nsStandardURL <public> +//---------------------------------------------------------------------------- + +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN +static StaticMutex gAllURLsMutex MOZ_UNANNOTATED; +static LinkedList<nsStandardURL> gAllURLs; +#endif + +nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL) + : mURLType(URLTYPE_STANDARD), + mSupportsFileURL(aSupportsFileURL), + mCheckedIfHostA(false) { + LOG(("Creating nsStandardURL @%p\n", this)); + + // gInitialized changes value only once (false->true) on the main thread. + // It's OK to race here because in the worst case we'll just + // dispatch a noop runnable to the main thread. + MOZ_ASSERT(gInitialized); + + // default parser in case nsIStandardURL::Init is never called + mParser = net_GetStdURLParser(); + +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN + if (aTrackURL) { + StaticMutexAutoLock lock(gAllURLsMutex); + gAllURLs.insertBack(this); + } +#endif +} + +bool nsStandardURL::IsValid() { + auto checkSegment = [&](const nsStandardURL::URLSegment& aSeg) { +#ifdef EARLY_BETA_OR_EARLIER + // If the parity is not the same, we assume that this is caused by a memory + // error. In this case, we think this URLSegment is valid. + if ((aSeg.mPos.Parity() != aSeg.mPos.CalculateParity()) || + (aSeg.mLen.Parity() != aSeg.mLen.CalculateParity())) { + MOZ_ASSERT(false); + return true; + } +#endif + // Bad value + if (NS_WARN_IF(aSeg.mLen < -1)) { + return false; + } + if (aSeg.mLen == -1) { + return true; + } + + // Points out of string + if (NS_WARN_IF(aSeg.mPos + aSeg.mLen > mSpec.Length())) { + return false; + } + + // Overflow + if (NS_WARN_IF(aSeg.mPos + aSeg.mLen < aSeg.mPos)) { + return false; + } + + return true; + }; + + bool allSegmentsValid = checkSegment(mScheme) && checkSegment(mAuthority) && + checkSegment(mUsername) && checkSegment(mPassword) && + checkSegment(mHost) && checkSegment(mPath) && + checkSegment(mFilepath) && checkSegment(mDirectory) && + checkSegment(mBasename) && checkSegment(mExtension) && + checkSegment(mQuery) && checkSegment(mRef); + if (!allSegmentsValid) { + return false; + } + + if (mScheme.mPos != 0) { + return false; + } + + return true; +} + +void nsStandardURL::SanityCheck() { + if (!IsValid()) { + nsPrintfCString msg( + "mLen:%zX, mScheme (%X,%X), mAuthority (%X,%X), mUsername (%X,%X), " + "mPassword (%X,%X), mHost (%X,%X), mPath (%X,%X), mFilepath (%X,%X), " + "mDirectory (%X,%X), mBasename (%X,%X), mExtension (%X,%X), mQuery " + "(%X,%X), mRef (%X,%X)", + mSpec.Length(), (uint32_t)mScheme.mPos, (int32_t)mScheme.mLen, + (uint32_t)mAuthority.mPos, (int32_t)mAuthority.mLen, + (uint32_t)mUsername.mPos, (int32_t)mUsername.mLen, + (uint32_t)mPassword.mPos, (int32_t)mPassword.mLen, (uint32_t)mHost.mPos, + (int32_t)mHost.mLen, (uint32_t)mPath.mPos, (int32_t)mPath.mLen, + (uint32_t)mFilepath.mPos, (int32_t)mFilepath.mLen, + (uint32_t)mDirectory.mPos, (int32_t)mDirectory.mLen, + (uint32_t)mBasename.mPos, (int32_t)mBasename.mLen, + (uint32_t)mExtension.mPos, (int32_t)mExtension.mLen, + (uint32_t)mQuery.mPos, (int32_t)mQuery.mLen, (uint32_t)mRef.mPos, + (int32_t)mRef.mLen); + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URLSegments, + msg); + + MOZ_CRASH("nsStandardURL::SanityCheck failed"); + } +} + +nsStandardURL::~nsStandardURL() { + LOG(("Destroying nsStandardURL @%p\n", this)); + +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN + { + StaticMutexAutoLock lock(gAllURLsMutex); + if (isInList()) { + remove(); + } + } +#endif +} + +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN +struct DumpLeakedURLs { + DumpLeakedURLs() = default; + ~DumpLeakedURLs(); +}; + +DumpLeakedURLs::~DumpLeakedURLs() { + MOZ_ASSERT(NS_IsMainThread()); + StaticMutexAutoLock lock(gAllURLsMutex); + if (!gAllURLs.isEmpty()) { + printf("Leaked URLs:\n"); + for (auto* url : gAllURLs) { + url->PrintSpec(); + } + gAllURLs.clear(); + } +} +#endif + +void nsStandardURL::InitGlobalObjects() { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + if (gInitialized) { + return; + } + + gInitialized = true; + + nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID)); + if (serv) { + gIDN = serv; + } + MOZ_DIAGNOSTIC_ASSERT(gIDN); + + // Make sure nsURLHelper::InitGlobals() gets called on the main thread + nsCOMPtr<nsIURLParser> parser = net_GetStdURLParser(); + MOZ_DIAGNOSTIC_ASSERT(parser); + Unused << parser; +} + +void nsStandardURL::ShutdownGlobalObjects() { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + gIDN = nullptr; + +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN + if (gInitialized) { + // This instanciates a dummy class, and will trigger the class + // destructor when libxul is unloaded. This is equivalent to atexit(), + // but gracefully handles dlclose(). + StaticMutexAutoLock lock(gAllURLsMutex); + static DumpLeakedURLs d; + } +#endif +} + +//---------------------------------------------------------------------------- +// nsStandardURL <private> +//---------------------------------------------------------------------------- + +void nsStandardURL::Clear() { + mSpec.Truncate(); + + mPort = -1; + + mScheme.Reset(); + mAuthority.Reset(); + mUsername.Reset(); + mPassword.Reset(); + mHost.Reset(); + + mPath.Reset(); + mFilepath.Reset(); + mDirectory.Reset(); + mBasename.Reset(); + + mExtension.Reset(); + mQuery.Reset(); + mRef.Reset(); + + InvalidateCache(); +} + +void nsStandardURL::InvalidateCache(bool invalidateCachedFile) { + if (invalidateCachedFile) { + mFile = nullptr; + } +} + +// Return the number of "dots" in the string, or -1 if invalid. Note that the +// number of relevant entries in the bases/starts/ends arrays is number of +// dots + 1. +// +// length is assumed to be <= host.Length(); the caller is responsible for that +// +// Note that the value returned is guaranteed to be in [-1, 3] range. +inline int32_t ValidateIPv4Number(const nsACString& host, int32_t bases[4], + int32_t dotIndex[3], bool& onlyBase10, + int32_t length, bool trailingDot) { + MOZ_ASSERT(length <= (int32_t)host.Length()); + if (length <= 0) { + return -1; + } + + bool lastWasNumber = false; // We count on this being false for i == 0 + int32_t dotCount = 0; + onlyBase10 = true; + + for (int32_t i = 0; i < length; i++) { + char current = host[i]; + if (current == '.') { + // A dot should not follow a dot, or be first - it can follow an x though. + if (!(lastWasNumber || + (i >= 2 && (host[i - 1] == 'X' || host[i - 1] == 'x') && + host[i - 2] == '0')) || + (i == (length - 1) && trailingDot)) { + return -1; + } + + if (dotCount > 2) { + return -1; + } + lastWasNumber = false; + dotIndex[dotCount] = i; + dotCount++; + } else if (current == 'X' || current == 'x') { + if (!lastWasNumber || // An X should not follow an X or a dot or be first + i == (length - 1) || // No trailing Xs allowed + (dotCount == 0 && + i != 1) || // If we had no dots, an X should be second + host[i - 1] != '0' || // X should always follow a 0. Guaranteed i > + // 0 as lastWasNumber is true + (dotCount > 0 && + host[i - 2] != '.')) { // And that zero follows a dot if it exists + return -1; + } + lastWasNumber = false; + bases[dotCount] = 16; + onlyBase10 = false; + + } else if (current == '0') { + if (i < length - 1 && // Trailing zero doesn't signal octal + host[i + 1] != '.' && // Lone zero is not octal + (i == 0 || host[i - 1] == '.')) { // Zero at start or following a dot + // is a candidate for octal + bases[dotCount] = 8; // This will turn to 16 above if X shows up + onlyBase10 = false; + } + lastWasNumber = true; + + } else if (current >= '1' && current <= '7') { + lastWasNumber = true; + + } else if (current >= '8' && current <= '9') { + if (bases[dotCount] == 8) { + return -1; + } + lastWasNumber = true; + + } else if ((current >= 'a' && current <= 'f') || + (current >= 'A' && current <= 'F')) { + if (bases[dotCount] != 16) { + return -1; + } + lastWasNumber = true; + + } else { + return -1; + } + } + + return dotCount; +} + +inline nsresult ParseIPv4Number10(const nsACString& input, uint32_t& number, + uint32_t maxNumber) { + uint64_t value = 0; + const char* current = input.BeginReading(); + const char* end = input.EndReading(); + for (; current < end; ++current) { + char c = *current; + MOZ_ASSERT(c >= '0' && c <= '9'); + value *= 10; + value += c - '0'; + } + if (value <= maxNumber) { + number = value; + return NS_OK; + } + + // The error case + number = 0; + return NS_ERROR_FAILURE; +} + +inline nsresult ParseIPv4Number(const nsACString& input, int32_t base, + uint32_t& number, uint32_t maxNumber) { + // Accumulate in the 64-bit value + uint64_t value = 0; + const char* current = input.BeginReading(); + const char* end = input.EndReading(); + switch (base) { + case 16: + ++current; + [[fallthrough]]; + case 8: + ++current; + break; + case 10: + default: + break; + } + for (; current < end; ++current) { + value *= base; + char c = *current; + MOZ_ASSERT((base == 10 && IsAsciiDigit(c)) || + (base == 8 && c >= '0' && c <= '7') || + (base == 16 && IsAsciiHexDigit(c))); + if (IsAsciiDigit(c)) { + value += c - '0'; + } else if (c >= 'a' && c <= 'f') { + value += c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + value += c - 'A' + 10; + } + } + + if (value <= maxNumber) { + number = value; + return NS_OK; + } + + // The error case + number = 0; + return NS_ERROR_FAILURE; +} + +// IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser +/* static */ +nsresult nsStandardURL::NormalizeIPv4(const nsACString& host, + nsCString& result) { + int32_t bases[4] = {10, 10, 10, 10}; + bool onlyBase10 = true; // Track this as a special case + int32_t dotIndex[3]; // The positions of the dots in the string + + // Use "length" rather than host.Length() after call to + // ValidateIPv4Number because of potential trailing period. + nsDependentCSubstring filteredHost; + bool trailingDot = false; + if (host.Length() > 0 && host.Last() == '.') { + trailingDot = true; + filteredHost.Rebind(host.BeginReading(), host.Length() - 1); + } else { + filteredHost.Rebind(host.BeginReading(), host.Length()); + } + + int32_t length = static_cast<int32_t>(filteredHost.Length()); + int32_t dotCount = ValidateIPv4Number(filteredHost, bases, dotIndex, + onlyBase10, length, trailingDot); + if (dotCount < 0 || length <= 0) { + return NS_ERROR_FAILURE; + } + + // Max values specified by the spec + static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu, 0xffffu, + 0xffu}; + uint32_t ipv4; + int32_t start = (dotCount > 0 ? dotIndex[dotCount - 1] + 1 : 0); + + // parse the last part first + nsresult res; + // Doing a special case for all items being base 10 gives ~35% speedup + res = (onlyBase10 + ? ParseIPv4Number10(Substring(host, start, length - start), ipv4, + upperBounds[dotCount]) + : ParseIPv4Number(Substring(host, start, length - start), + bases[dotCount], ipv4, upperBounds[dotCount])); + if (NS_FAILED(res)) { + return NS_ERROR_FAILURE; + } + + // parse remaining parts starting from first part + int32_t lastUsed = -1; + for (int32_t i = 0; i < dotCount; i++) { + uint32_t number; + start = lastUsed + 1; + lastUsed = dotIndex[i]; + res = + (onlyBase10 ? ParseIPv4Number10( + Substring(host, start, lastUsed - start), number, 255) + : ParseIPv4Number(Substring(host, start, lastUsed - start), + bases[i], number, 255)); + if (NS_FAILED(res)) { + return NS_ERROR_FAILURE; + } + ipv4 += number << (8 * (3 - i)); + } + + // A special case for ipv4 URL like "127." should have the same result as + // "127". + if (dotCount == 1 && dotIndex[0] == length - 1) { + ipv4 = (ipv4 & 0xff000000) >> 24; + } + + uint8_t ipSegments[4]; + NetworkEndian::writeUint32(ipSegments, ipv4); + result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1], + ipSegments[2], ipSegments[3]); + return NS_OK; +} + +nsresult nsStandardURL::NormalizeIDN(const nsCString& host, nsCString& result) { + result.Truncate(); + mDisplayHost.Truncate(); + nsresult rv; + + if (!gIDN) { + return NS_ERROR_UNEXPECTED; + } + + // Even if it's already ACE, we must still call ConvertUTF8toACE in order + // for the input normalization to take place. + rv = gIDN->ConvertUTF8toACE(host, result); + if (NS_FAILED(rv)) { + return rv; + } + + // If the ASCII representation doesn't contain the xn-- token then we don't + // need to call ConvertToDisplayIDN as that would not change anything. + if (!StringBeginsWith(result, "xn--"_ns) && + result.Find(".xn--"_ns) == kNotFound) { + mCheckedIfHostA = true; + return NS_OK; + } + + bool isAscii = true; + nsAutoCString displayHost; + rv = gIDN->ConvertToDisplayIDN(result, &isAscii, displayHost); + if (NS_FAILED(rv)) { + return rv; + } + + mCheckedIfHostA = true; + if (!isAscii) { + mDisplayHost = displayHost; + } + return NS_OK; +} + +bool nsStandardURL::ValidIPv6orHostname(const char* host, uint32_t length) { + if (!host || !*host) { + // Should not be NULL or empty string + return false; + } + + if (length != strlen(host)) { + // Embedded null + return false; + } + + bool openBracket = host[0] == '['; + bool closeBracket = host[length - 1] == ']'; + + if (openBracket && closeBracket) { + return net_IsValidIPv6Addr(Substring(host + 1, length - 2)); + } + + if (openBracket || closeBracket) { + // Fail if only one of the brackets is present + return false; + } + + const char* end = host + length; + const char* iter = host; + for (; iter != end && *iter; ++iter) { + if (ASCIIMask::IsMasked(sInvalidHostChars, *iter)) { + return false; + } + } + return true; +} + +void nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char* path) { + net_CoalesceDirs(coalesceFlag, path); + int32_t newLen = strlen(path); + if (newLen < mPath.mLen) { + int32_t diff = newLen - mPath.mLen; + mPath.mLen = newLen; + mDirectory.mLen += diff; + mFilepath.mLen += diff; + ShiftFromBasename(diff); + } +} + +uint32_t nsStandardURL::AppendSegmentToBuf(char* buf, uint32_t i, + const char* str, + const URLSegment& segInput, + URLSegment& segOutput, + const nsCString* escapedStr, + bool useEscaped, int32_t* diff) { + MOZ_ASSERT(segInput.mLen == segOutput.mLen); + + if (diff) { + *diff = 0; + } + + if (segInput.mLen > 0) { + if (useEscaped) { + MOZ_ASSERT(diff); + segOutput.mLen = escapedStr->Length(); + *diff = segOutput.mLen - segInput.mLen; + memcpy(buf + i, escapedStr->get(), segOutput.mLen); + } else { + memcpy(buf + i, str + segInput.mPos, segInput.mLen); + } + segOutput.mPos = i; + i += segOutput.mLen; + } else { + segOutput.mPos = i; + } + return i; +} + +uint32_t nsStandardURL::AppendToBuf(char* buf, uint32_t i, const char* str, + uint32_t len) { + memcpy(buf + i, str, len); + return i + len; +} + +static bool ContainsOnlyAsciiDigits(const nsDependentCSubstring& input) { + for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) { + if (!IsAsciiDigit(*c)) { + return false; + } + } + + return true; +} + +static bool ContainsOnlyAsciiHexDigits(const nsDependentCSubstring& input) { + for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) { + if (!IsAsciiHexDigit(*c)) { + return false; + } + } + return true; +} + +// https://url.spec.whatwg.org/#ends-in-a-number-checker +static bool EndsInANumber(const nsCString& input) { + // 1. Let parts be the result of strictly splitting input on U+002E (.). + nsTArray<nsDependentCSubstring> parts; + for (const nsDependentCSubstring& part : input.Split('.')) { + parts.AppendElement(part); + } + + if (parts.Length() == 0) { + return false; + } + + // 2.If the last item in parts is the empty string, then: + // 1. If parts’s size is 1, then return false. + // 2. Remove the last item from parts. + if (parts.LastElement().IsEmpty()) { + if (parts.Length() == 1) { + return false; + } + Unused << parts.PopLastElement(); + } + + // 3. Let last be the last item in parts. + const nsDependentCSubstring& last = parts.LastElement(); + + // 4. If last is non-empty and contains only ASCII digits, then return true. + // The erroneous input "09" will be caught by the IPv4 parser at a later + // stage. + if (!last.IsEmpty()) { + if (ContainsOnlyAsciiDigits(last)) { + return true; + } + } + + // 5. If parsing last as an IPv4 number does not return failure, then return + // true. This is equivalent to checking that last is "0X" or "0x", followed by + // zero or more ASCII hex digits. + if (StringBeginsWith(last, "0x"_ns) || StringBeginsWith(last, "0X"_ns)) { + if (ContainsOnlyAsciiHexDigits(Substring(last, 2))) { + return true; + } + } + + return false; +} + +// basic algorithm: +// 1- escape url segments (for improved GetSpec efficiency) +// 2- allocate spec buffer +// 3- write url segments +// 4- update url segment positions and lengths +nsresult nsStandardURL::BuildNormalizedSpec(const char* spec, + const Encoding* encoding) { + // Assumptions: all member URLSegments must be relative the |spec| argument + // passed to this function. + + // buffers for holding escaped url segments (these will remain empty unless + // escaping is required). + nsAutoCString encUsername, encPassword, encHost, encDirectory, encBasename, + encExtension, encQuery, encRef; + bool useEncUsername, useEncPassword, useEncHost = false, useEncDirectory, + useEncBasename, useEncExtension, + useEncQuery, useEncRef; + nsAutoCString portbuf; + + // + // escape each URL segment, if necessary, and calculate approximate normalized + // spec length. + // + // [scheme://][username[:password]@]host[:port]/path[?query_string][#ref] + + uint32_t approxLen = 0; + + // the scheme is already ASCII + if (mScheme.mLen > 0) { + approxLen += + mScheme.mLen + 3; // includes room for "://", which we insert always + } + + // encode URL segments; convert UTF-8 to origin charset and possibly escape. + // results written to encXXX variables only if |spec| is not already in the + // appropriate encoding. + { + nsSegmentEncoder encoder; + nsSegmentEncoder queryEncoder(encoding); + // Username@ + approxLen += encoder.EncodeSegmentCount(spec, mUsername, esc_Username, + encUsername, useEncUsername, 0); + approxLen += 1; // reserve length for @ + // :password - we insert the ':' even if there's no actual password if + // "user:@" was in the spec + if (mPassword.mLen > 0) { + approxLen += 1 + encoder.EncodeSegmentCount(spec, mPassword, esc_Password, + encPassword, useEncPassword); + } + // mHost is handled differently below due to encoding differences + MOZ_ASSERT(mPort >= -1, "Invalid negative mPort"); + if (mPort != -1 && mPort != mDefaultPort) { + // :port + portbuf.AppendInt(mPort); + approxLen += portbuf.Length() + 1; + } + + approxLen += + 1; // reserve space for possible leading '/' - may not be needed + // Should just use mPath? These are pessimistic, and thus waste space + approxLen += encoder.EncodeSegmentCount(spec, mDirectory, esc_Directory, + encDirectory, useEncDirectory, 1); + approxLen += encoder.EncodeSegmentCount(spec, mBasename, esc_FileBaseName, + encBasename, useEncBasename); + approxLen += encoder.EncodeSegmentCount(spec, mExtension, esc_FileExtension, + encExtension, useEncExtension, 1); + + // These next ones *always* add their leading character even if length is 0 + // Handles items like "http://#" + // ?query + if (mQuery.mLen >= 0) { + approxLen += 1 + queryEncoder.EncodeSegmentCount(spec, mQuery, esc_Query, + encQuery, useEncQuery); + } + // #ref + + if (mRef.mLen >= 0) { + approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref, encRef, + useEncRef); + } + } + + // do not escape the hostname, if IPv6 address literal, mHost will + // already point to a [ ] delimited IPv6 address literal. + // However, perform Unicode normalization on it, as IDN does. + // Note that we don't disallow URLs without a host - file:, etc + if (mHost.mLen > 0) { + nsAutoCString tempHost; + NS_UnescapeURL(spec + mHost.mPos, mHost.mLen, esc_AlwaysCopy | esc_Host, + tempHost); + if (tempHost.Contains('\0')) { + return NS_ERROR_MALFORMED_URI; // null embedded in hostname + } + if (tempHost.Contains(' ')) { + return NS_ERROR_MALFORMED_URI; // don't allow spaces in the hostname + } + nsresult rv = NormalizeIDN(tempHost, encHost); + if (NS_FAILED(rv)) { + return rv; + } + if (!SegmentIs(spec, mScheme, "resource") && + !SegmentIs(spec, mScheme, "chrome")) { + nsAutoCString ipString; + if (encHost.Length() > 0 && encHost.First() == '[' && + encHost.Last() == ']' && + ValidIPv6orHostname(encHost.get(), encHost.Length())) { + rv = (nsresult)rusturl_parse_ipv6addr(&encHost, &ipString); + if (NS_FAILED(rv)) { + return rv; + } + encHost = ipString; + } else { + if (EndsInANumber(encHost)) { + rv = NormalizeIPv4(encHost, ipString); + if (NS_FAILED(rv)) { + return rv; + } + encHost = ipString; + } + } + } + + // NormalizeIDN always copies, if the call was successful. + useEncHost = true; + approxLen += encHost.Length(); + + if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) { + return NS_ERROR_MALFORMED_URI; + } + } else { + // empty host means empty mDisplayHost + mDisplayHost.Truncate(); + mCheckedIfHostA = true; + } + + // We must take a copy of every single segment because they are pointing to + // the |spec| while we are changing their value, in case we must use + // encoded strings. + URLSegment username(mUsername); + URLSegment password(mPassword); + URLSegment host(mHost); + URLSegment path(mPath); + URLSegment directory(mDirectory); + URLSegment basename(mBasename); + URLSegment extension(mExtension); + URLSegment query(mQuery); + URLSegment ref(mRef); + + // The encoded string could be longer than the original input, so we need + // to check the final URI isn't longer than the max length. + if (approxLen + 1 > StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + // + // generate the normalized URL string + // + // approxLen should be correct or 1 high + if (!mSpec.SetLength(approxLen + 1, + fallible)) { // buf needs a trailing '\0' below + return NS_ERROR_OUT_OF_MEMORY; + } + char* buf = mSpec.BeginWriting(); + uint32_t i = 0; + int32_t diff = 0; + + if (mScheme.mLen > 0) { + i = AppendSegmentToBuf(buf, i, spec, mScheme, mScheme); + net_ToLowerCase(buf + mScheme.mPos, mScheme.mLen); + i = AppendToBuf(buf, i, "://", 3); + } + + // record authority starting position + mAuthority.mPos = i; + + // append authority + if (mUsername.mLen > 0 || mPassword.mLen > 0) { + if (mUsername.mLen > 0) { + i = AppendSegmentToBuf(buf, i, spec, username, mUsername, &encUsername, + useEncUsername, &diff); + ShiftFromPassword(diff); + } else { + mUsername.mLen = -1; + } + if (password.mLen > 0) { + buf[i++] = ':'; + i = AppendSegmentToBuf(buf, i, spec, password, mPassword, &encPassword, + useEncPassword, &diff); + ShiftFromHost(diff); + } else { + mPassword.mLen = -1; + } + buf[i++] = '@'; + } else { + mUsername.mLen = -1; + mPassword.mLen = -1; + } + if (host.mLen > 0) { + i = AppendSegmentToBuf(buf, i, spec, host, mHost, &encHost, useEncHost, + &diff); + ShiftFromPath(diff); + + net_ToLowerCase(buf + mHost.mPos, mHost.mLen); + MOZ_ASSERT(mPort >= -1, "Invalid negative mPort"); + if (mPort != -1 && mPort != mDefaultPort) { + buf[i++] = ':'; + // Already formatted while building approxLen + i = AppendToBuf(buf, i, portbuf.get(), portbuf.Length()); + } + } + + // record authority length + mAuthority.mLen = i - mAuthority.mPos; + + // path must always start with a "/" + if (mPath.mLen <= 0) { + LOG(("setting path=/")); + mDirectory.mPos = mFilepath.mPos = mPath.mPos = i; + mDirectory.mLen = mFilepath.mLen = mPath.mLen = 1; + // basename must exist, even if empty (bug 113508) + mBasename.mPos = i + 1; + mBasename.mLen = 0; + buf[i++] = '/'; + } else { + uint32_t leadingSlash = 0; + if (spec[path.mPos] != '/') { + LOG(("adding leading slash to path\n")); + leadingSlash = 1; + buf[i++] = '/'; + // basename must exist, even if empty (bugs 113508, 429347) + if (mBasename.mLen == -1) { + mBasename.mPos = basename.mPos = i; + mBasename.mLen = basename.mLen = 0; + } + } + + // record corrected (file)path starting position + mPath.mPos = mFilepath.mPos = i - leadingSlash; + + i = AppendSegmentToBuf(buf, i, spec, directory, mDirectory, &encDirectory, + useEncDirectory, &diff); + ShiftFromBasename(diff); + + // the directory must end with a '/' + if (buf[i - 1] != '/') { + buf[i++] = '/'; + mDirectory.mLen++; + } + + i = AppendSegmentToBuf(buf, i, spec, basename, mBasename, &encBasename, + useEncBasename, &diff); + ShiftFromExtension(diff); + + // make corrections to directory segment if leadingSlash + if (leadingSlash) { + mDirectory.mPos = mPath.mPos; + if (mDirectory.mLen >= 0) { + mDirectory.mLen += leadingSlash; + } else { + mDirectory.mLen = 1; + } + } + + if (mExtension.mLen >= 0) { + buf[i++] = '.'; + i = AppendSegmentToBuf(buf, i, spec, extension, mExtension, &encExtension, + useEncExtension, &diff); + ShiftFromQuery(diff); + } + // calculate corrected filepath length + mFilepath.mLen = i - mFilepath.mPos; + + if (mQuery.mLen >= 0) { + buf[i++] = '?'; + i = AppendSegmentToBuf(buf, i, spec, query, mQuery, &encQuery, + useEncQuery, &diff); + ShiftFromRef(diff); + } + if (mRef.mLen >= 0) { + buf[i++] = '#'; + i = AppendSegmentToBuf(buf, i, spec, ref, mRef, &encRef, useEncRef, + &diff); + } + // calculate corrected path length + mPath.mLen = i - mPath.mPos; + } + + buf[i] = '\0'; + + // https://url.spec.whatwg.org/#path-state (1.4.1.2) + // https://url.spec.whatwg.org/#windows-drive-letter + if (SegmentIs(buf, mScheme, "file")) { + char* path = &buf[mPath.mPos]; + if (mPath.mLen >= 3 && path[0] == '/' && IsAsciiAlpha(path[1]) && + path[2] == '|') { + buf[mPath.mPos + 2] = ':'; + } + } + + if (mDirectory.mLen > 1) { + netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL; + if (SegmentIs(buf, mScheme, "ftp")) { + coalesceFlag = + (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT | + NET_COALESCE_DOUBLE_SLASH_IS_ROOT); + } + CoalescePath(coalesceFlag, buf + mDirectory.mPos); + } + mSpec.Truncate(strlen(buf)); + NS_ASSERTION(mSpec.Length() <= approxLen, + "We've overflowed the mSpec buffer!"); + MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(), + "The spec should never be this long, we missed a check."); + + MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0); + return NS_OK; +} + +bool nsStandardURL::SegmentIs(const URLSegment& seg, const char* val, + bool ignoreCase) { + // one or both may be null + if (!val || mSpec.IsEmpty()) { + return (!val && (mSpec.IsEmpty() || seg.mLen < 0)); + } + if (seg.mLen < 0) { + return false; + } + // if the first |seg.mLen| chars of |val| match, then |val| must + // also be null terminated at |seg.mLen|. + if (ignoreCase) { + return !nsCRT::strncasecmp(mSpec.get() + seg.mPos, val, seg.mLen) && + (val[seg.mLen] == '\0'); + } + + return !strncmp(mSpec.get() + seg.mPos, val, seg.mLen) && + (val[seg.mLen] == '\0'); +} + +bool nsStandardURL::SegmentIs(const char* spec, const URLSegment& seg, + const char* val, bool ignoreCase) { + // one or both may be null + if (!val || !spec) { + return (!val && (!spec || seg.mLen < 0)); + } + if (seg.mLen < 0) { + return false; + } + // if the first |seg.mLen| chars of |val| match, then |val| must + // also be null terminated at |seg.mLen|. + if (ignoreCase) { + return !nsCRT::strncasecmp(spec + seg.mPos, val, seg.mLen) && + (val[seg.mLen] == '\0'); + } + + return !strncmp(spec + seg.mPos, val, seg.mLen) && (val[seg.mLen] == '\0'); +} + +bool nsStandardURL::SegmentIs(const URLSegment& seg1, const char* val, + const URLSegment& seg2, bool ignoreCase) { + if (seg1.mLen != seg2.mLen) { + return false; + } + if (seg1.mLen == -1 || (!val && mSpec.IsEmpty())) { + return true; // both are empty + } + if (!val) { + return false; + } + if (ignoreCase) { + return !nsCRT::strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos, + seg1.mLen); + } + + return !strncmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen); +} + +int32_t nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len, + const char* val, uint32_t valLen) { + if (val && valLen) { + if (len == 0) { + mSpec.Insert(val, pos, valLen); + } else { + mSpec.Replace(pos, len, nsDependentCString(val, valLen)); + } + return valLen - len; + } + + // else remove the specified segment + mSpec.Cut(pos, len); + return -int32_t(len); +} + +int32_t nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len, + const nsACString& val) { + if (len == 0) { + mSpec.Insert(val, pos); + } else { + mSpec.Replace(pos, len, val); + } + return val.Length() - len; +} + +nsresult nsStandardURL::ParseURL(const char* spec, int32_t specLen) { + nsresult rv; + + if (specLen > (int32_t)StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + // + // parse given URL string + // + uint32_t schemePos = mScheme.mPos; + int32_t schemeLen = mScheme.mLen; + uint32_t authorityPos = mAuthority.mPos; + int32_t authorityLen = mAuthority.mLen; + uint32_t pathPos = mPath.mPos; + int32_t pathLen = mPath.mLen; + rv = mParser->ParseURL(spec, specLen, &schemePos, &schemeLen, &authorityPos, + &authorityLen, &pathPos, &pathLen); + if (NS_FAILED(rv)) { + return rv; + } + mScheme.mPos = schemePos; + mScheme.mLen = schemeLen; + mAuthority.mPos = authorityPos; + mAuthority.mLen = authorityLen; + mPath.mPos = pathPos; + mPath.mLen = pathLen; + +#ifdef DEBUG + if (mScheme.mLen <= 0) { + printf("spec=%s\n", spec); + NS_WARNING("malformed url: no scheme"); + } +#endif + + if (mAuthority.mLen > 0) { + uint32_t usernamePos = mUsername.mPos; + int32_t usernameLen = mUsername.mLen; + uint32_t passwordPos = mPassword.mPos; + int32_t passwordLen = mPassword.mLen; + uint32_t hostPos = mHost.mPos; + int32_t hostLen = mHost.mLen; + rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen, + &usernamePos, &usernameLen, &passwordPos, + &passwordLen, &hostPos, &hostLen, &mPort); + if (NS_FAILED(rv)) { + return rv; + } + + mUsername.mPos = usernamePos; + mUsername.mLen = usernameLen; + mPassword.mPos = passwordPos; + mPassword.mLen = passwordLen; + mHost.mPos = hostPos; + mHost.mLen = hostLen; + + // Don't allow mPort to be set to this URI's default port + if (mPort == mDefaultPort) { + mPort = -1; + } + + mUsername.mPos += mAuthority.mPos; + mPassword.mPos += mAuthority.mPos; + mHost.mPos += mAuthority.mPos; + } + + if (mPath.mLen > 0) { + rv = ParsePath(spec, mPath.mPos, mPath.mLen); + } + + return rv; +} + +nsresult nsStandardURL::ParsePath(const char* spec, uint32_t pathPos, + int32_t pathLen) { + LOG(("ParsePath: %s pathpos %d len %d\n", spec, pathPos, pathLen)); + + if (pathLen > (int32_t)StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + uint32_t filePathPos = mFilepath.mPos; + int32_t filePathLen = mFilepath.mLen; + uint32_t queryPos = mQuery.mPos; + int32_t queryLen = mQuery.mLen; + uint32_t refPos = mRef.mPos; + int32_t refLen = mRef.mLen; + nsresult rv = + mParser->ParsePath(spec + pathPos, pathLen, &filePathPos, &filePathLen, + &queryPos, &queryLen, &refPos, &refLen); + if (NS_FAILED(rv)) { + return rv; + } + + mFilepath.mPos = filePathPos; + mFilepath.mLen = filePathLen; + mQuery.mPos = queryPos; + mQuery.mLen = queryLen; + mRef.mPos = refPos; + mRef.mLen = refLen; + + mFilepath.mPos += pathPos; + mQuery.mPos += pathPos; + mRef.mPos += pathPos; + + if (mFilepath.mLen > 0) { + uint32_t directoryPos = mDirectory.mPos; + int32_t directoryLen = mDirectory.mLen; + uint32_t basenamePos = mBasename.mPos; + int32_t basenameLen = mBasename.mLen; + uint32_t extensionPos = mExtension.mPos; + int32_t extensionLen = mExtension.mLen; + rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen, + &directoryPos, &directoryLen, &basenamePos, + &basenameLen, &extensionPos, &extensionLen); + if (NS_FAILED(rv)) { + return rv; + } + + mDirectory.mPos = directoryPos; + mDirectory.mLen = directoryLen; + mBasename.mPos = basenamePos; + mBasename.mLen = basenameLen; + mExtension.mPos = extensionPos; + mExtension.mLen = extensionLen; + + mDirectory.mPos += mFilepath.mPos; + mBasename.mPos += mFilepath.mPos; + mExtension.mPos += mFilepath.mPos; + } + return NS_OK; +} + +char* nsStandardURL::AppendToSubstring(uint32_t pos, int32_t len, + const char* tail) { + // Verify pos and length are within boundaries + if (pos > mSpec.Length()) { + return nullptr; + } + if (len < 0) { + return nullptr; + } + if ((uint32_t)len > (mSpec.Length() - pos)) { + return nullptr; + } + if (!tail) { + return nullptr; + } + + uint32_t tailLen = strlen(tail); + + // Check for int overflow for proposed length of combined string + if (UINT32_MAX - ((uint32_t)len + 1) < tailLen) { + return nullptr; + } + + char* result = (char*)moz_xmalloc(len + tailLen + 1); + memcpy(result, mSpec.get() + pos, len); + memcpy(result + len, tail, tailLen); + result[len + tailLen] = '\0'; + return result; +} + +nsresult nsStandardURL::ReadSegment(nsIBinaryInputStream* stream, + URLSegment& seg) { + nsresult rv; + + uint32_t pos = seg.mPos; + rv = stream->Read32(&pos); + if (NS_FAILED(rv)) { + return rv; + } + + seg.mPos = pos; + + uint32_t len = seg.mLen; + rv = stream->Read32(&len); + if (NS_FAILED(rv)) { + return rv; + } + + CheckedInt<int32_t> checkedLen(len); + if (!checkedLen.isValid()) { + seg.mLen = -1; + } else { + seg.mLen = len; + } + + return NS_OK; +} + +nsresult nsStandardURL::WriteSegment(nsIBinaryOutputStream* stream, + const URLSegment& seg) { + nsresult rv; + + rv = stream->Write32(seg.mPos); + if (NS_FAILED(rv)) { + return rv; + } + + rv = stream->Write32(uint32_t(seg.mLen)); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +#define SHIFT_FROM(name, what) \ + void nsStandardURL::name(int32_t diff) { \ + if (!diff) return; \ + if ((what).mLen >= 0) { \ + CheckedInt<int32_t> pos = (uint32_t)(what).mPos; \ + pos += diff; \ + MOZ_ASSERT(pos.isValid()); \ + (what).mPos = pos.value(); \ + } else { \ + MOZ_RELEASE_ASSERT((what).mLen == -1); \ + } + +#define SHIFT_FROM_NEXT(name, what, next) \ + SHIFT_FROM(name, what) \ + next(diff); \ + } + +#define SHIFT_FROM_LAST(name, what) \ + SHIFT_FROM(name, what) \ + } + +SHIFT_FROM_NEXT(ShiftFromAuthority, mAuthority, ShiftFromUsername) +SHIFT_FROM_NEXT(ShiftFromUsername, mUsername, ShiftFromPassword) +SHIFT_FROM_NEXT(ShiftFromPassword, mPassword, ShiftFromHost) +SHIFT_FROM_NEXT(ShiftFromHost, mHost, ShiftFromPath) +SHIFT_FROM_NEXT(ShiftFromPath, mPath, ShiftFromFilepath) +SHIFT_FROM_NEXT(ShiftFromFilepath, mFilepath, ShiftFromDirectory) +SHIFT_FROM_NEXT(ShiftFromDirectory, mDirectory, ShiftFromBasename) +SHIFT_FROM_NEXT(ShiftFromBasename, mBasename, ShiftFromExtension) +SHIFT_FROM_NEXT(ShiftFromExtension, mExtension, ShiftFromQuery) +SHIFT_FROM_NEXT(ShiftFromQuery, mQuery, ShiftFromRef) +SHIFT_FROM_LAST(ShiftFromRef, mRef) + +//---------------------------------------------------------------------------- +// nsStandardURL::nsIClassInfo +//---------------------------------------------------------------------------- + +NS_IMPL_CLASSINFO(nsStandardURL, nullptr, nsIClassInfo::THREADSAFE, + NS_STANDARDURL_CID) +// Empty CI getter. We only need nsIClassInfo for Serialization +NS_IMPL_CI_INTERFACE_GETTER0(nsStandardURL) + +//---------------------------------------------------------------------------- +// nsStandardURL::nsISupports +//---------------------------------------------------------------------------- + +NS_IMPL_ADDREF(nsStandardURL) +NS_IMPL_RELEASE(nsStandardURL) + +NS_INTERFACE_MAP_BEGIN(nsStandardURL) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardURL) + NS_INTERFACE_MAP_ENTRY(nsIURI) + NS_INTERFACE_MAP_ENTRY(nsIURL) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL) + NS_INTERFACE_MAP_ENTRY(nsIStandardURL) + NS_INTERFACE_MAP_ENTRY(nsISerializable) + NS_IMPL_QUERY_CLASSINFO(nsStandardURL) + NS_INTERFACE_MAP_ENTRY(nsISensitiveInfoHiddenURI) + // see nsStandardURL::Equals + if (aIID.Equals(kThisImplCID)) { + foundInterface = static_cast<nsIURI*>(this); + } else + NS_INTERFACE_MAP_ENTRY(nsISizeOf) +NS_INTERFACE_MAP_END + +//---------------------------------------------------------------------------- +// nsStandardURL::nsIURI +//---------------------------------------------------------------------------- + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetSpec(nsACString& result) { + MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(), + "The spec should never be this long, we missed a check."); + result = mSpec; + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString& result) { + nsresult rv = GetSpec(result); + if (NS_FAILED(rv)) { + return rv; + } + if (mPassword.mLen > 0) { + result.ReplaceLiteral(mPassword.mPos, mPassword.mLen, "****"); + } + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetSpecIgnoringRef(nsACString& result) { + // URI without ref is 0 to one char before ref + if (mRef.mLen < 0) { + return GetSpec(result); + } + + URLSegment noRef(0, mRef.mPos - 1); + result = Segment(noRef); + MOZ_ASSERT(mCheckedIfHostA); + return NS_OK; +} + +nsresult nsStandardURL::CheckIfHostIsAscii() { + nsresult rv; + if (mCheckedIfHostA) { + return NS_OK; + } + + mCheckedIfHostA = true; + + if (!gIDN) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsAutoCString displayHost; + bool isAscii; + rv = gIDN->ConvertToDisplayIDN(Host(), &isAscii, displayHost); + if (NS_FAILED(rv)) { + mDisplayHost.Truncate(); + mCheckedIfHostA = false; + return rv; + } + + if (!isAscii) { + mDisplayHost = displayHost; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetDisplaySpec(nsACString& aUnicodeSpec) { + aUnicodeSpec.Assign(mSpec); + MOZ_ASSERT(mCheckedIfHostA); + if (!mDisplayHost.IsEmpty()) { + aUnicodeSpec.Replace(mHost.mPos, mHost.mLen, mDisplayHost); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetDisplayHostPort(nsACString& aUnicodeHostPort) { + nsAutoCString unicodeHostPort; + + nsresult rv = GetDisplayHost(unicodeHostPort); + if (NS_FAILED(rv)) { + return rv; + } + + if (StringBeginsWith(Hostport(), "["_ns)) { + aUnicodeHostPort.AssignLiteral("["); + aUnicodeHostPort.Append(unicodeHostPort); + aUnicodeHostPort.AppendLiteral("]"); + } else { + aUnicodeHostPort.Assign(unicodeHostPort); + } + + uint32_t pos = mHost.mPos + mHost.mLen; + if (pos < mPath.mPos) { + aUnicodeHostPort += Substring(mSpec, pos, mPath.mPos - pos); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetDisplayHost(nsACString& aUnicodeHost) { + MOZ_ASSERT(mCheckedIfHostA); + if (mDisplayHost.IsEmpty()) { + return GetAsciiHost(aUnicodeHost); + } + + aUnicodeHost = mDisplayHost; + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetPrePath(nsACString& result) { + result = Prepath(); + MOZ_ASSERT(mCheckedIfHostA); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetDisplayPrePath(nsACString& result) { + result = Prepath(); + MOZ_ASSERT(mCheckedIfHostA); + if (!mDisplayHost.IsEmpty()) { + result.Replace(mHost.mPos, mHost.mLen, mDisplayHost); + } + return NS_OK; +} + +// result is strictly US-ASCII +NS_IMETHODIMP +nsStandardURL::GetScheme(nsACString& result) { + result = Scheme(); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetUserPass(nsACString& result) { + result = Userpass(); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetUsername(nsACString& result) { + result = Username(); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetPassword(nsACString& result) { + result = Password(); + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetHostPort(nsACString& result) { + return GetAsciiHostPort(result); +} + +NS_IMETHODIMP +nsStandardURL::GetHost(nsACString& result) { return GetAsciiHost(result); } + +NS_IMETHODIMP +nsStandardURL::GetPort(int32_t* result) { + // should never be more than 16 bit + MOZ_ASSERT(mPort <= std::numeric_limits<uint16_t>::max()); + *result = mPort; + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetPathQueryRef(nsACString& result) { + result = Path(); + return NS_OK; +} + +// result is ASCII +NS_IMETHODIMP +nsStandardURL::GetAsciiSpec(nsACString& result) { + result = mSpec; + return NS_OK; +} + +// result is ASCII +NS_IMETHODIMP +nsStandardURL::GetAsciiHostPort(nsACString& result) { + result = Hostport(); + return NS_OK; +} + +// result is ASCII +NS_IMETHODIMP +nsStandardURL::GetAsciiHost(nsACString& result) { + result = Host(); + return NS_OK; +} + +static bool IsSpecialProtocol(const nsACString& input) { + nsACString::const_iterator start, end; + input.BeginReading(start); + nsACString::const_iterator iterator(start); + input.EndReading(end); + + while (iterator != end && *iterator != ':') { + iterator++; + } + + nsAutoCString protocol(nsDependentCSubstring(start.get(), iterator.get())); + + return protocol.LowerCaseEqualsLiteral("http") || + protocol.LowerCaseEqualsLiteral("https") || + protocol.LowerCaseEqualsLiteral("ftp") || + protocol.LowerCaseEqualsLiteral("ws") || + protocol.LowerCaseEqualsLiteral("wss") || + protocol.LowerCaseEqualsLiteral("file") || + protocol.LowerCaseEqualsLiteral("gopher"); +} + +nsresult nsStandardURL::SetSpecInternal(const nsACString& input) { + return SetSpecWithEncoding(input, nullptr); +} + +nsresult nsStandardURL::SetSpecWithEncoding(const nsACString& input, + const Encoding* encoding) { + const nsPromiseFlatCString& flat = PromiseFlatCString(input); + LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get())); + + if (input.Length() > StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + // filter out unexpected chars "\r\n\t" if necessary + nsAutoCString filteredURI; + net_FilterURIString(flat, filteredURI); + + if (filteredURI.Length() == 0) { + return NS_ERROR_MALFORMED_URI; + } + + // Make a backup of the current URL + nsStandardURL prevURL(false, false); + prevURL.CopyMembers(this, eHonorRef, ""_ns); + Clear(); + + if (IsSpecialProtocol(filteredURI)) { + // Bug 652186: Replace all backslashes with slashes when parsing paths + // Stop when we reach the query or the hash. + auto* start = filteredURI.BeginWriting(); + auto* end = filteredURI.EndWriting(); + while (start != end) { + if (*start == '?' || *start == '#') { + break; + } + if (*start == '\\') { + *start = '/'; + } + start++; + } + } + + const char* spec = filteredURI.get(); + int32_t specLength = filteredURI.Length(); + + // parse the given URL... + nsresult rv = ParseURL(spec, specLength); + if (mScheme.mLen <= 0) { + rv = NS_ERROR_MALFORMED_URI; + } + if (NS_SUCCEEDED(rv)) { + // finally, use the URLSegment member variables to build a normalized + // copy of |spec| + rv = BuildNormalizedSpec(spec, encoding); + } + + // Make sure that a URLTYPE_AUTHORITY has a non-empty hostname. + if (mURLType == URLTYPE_AUTHORITY && mHost.mLen == -1) { + rv = NS_ERROR_MALFORMED_URI; + } + + if (NS_FAILED(rv)) { + Clear(); + // If parsing the spec has failed, restore the old URL + // so we don't end up with an empty URL. + CopyMembers(&prevURL, eHonorRef, ""_ns); + return rv; + } + + if (LOG_ENABLED()) { + LOG((" spec = %s\n", mSpec.get())); + LOG((" port = %d\n", mPort)); + LOG((" scheme = (%u,%d)\n", (uint32_t)mScheme.mPos, + (int32_t)mScheme.mLen)); + LOG((" authority = (%u,%d)\n", (uint32_t)mAuthority.mPos, + (int32_t)mAuthority.mLen)); + LOG((" username = (%u,%d)\n", (uint32_t)mUsername.mPos, + (int32_t)mUsername.mLen)); + LOG((" password = (%u,%d)\n", (uint32_t)mPassword.mPos, + (int32_t)mPassword.mLen)); + LOG((" hostname = (%u,%d)\n", (uint32_t)mHost.mPos, (int32_t)mHost.mLen)); + LOG((" path = (%u,%d)\n", (uint32_t)mPath.mPos, (int32_t)mPath.mLen)); + LOG((" filepath = (%u,%d)\n", (uint32_t)mFilepath.mPos, + (int32_t)mFilepath.mLen)); + LOG((" directory = (%u,%d)\n", (uint32_t)mDirectory.mPos, + (int32_t)mDirectory.mLen)); + LOG((" basename = (%u,%d)\n", (uint32_t)mBasename.mPos, + (int32_t)mBasename.mLen)); + LOG((" extension = (%u,%d)\n", (uint32_t)mExtension.mPos, + (int32_t)mExtension.mLen)); + LOG((" query = (%u,%d)\n", (uint32_t)mQuery.mPos, + (int32_t)mQuery.mLen)); + LOG((" ref = (%u,%d)\n", (uint32_t)mRef.mPos, (int32_t)mRef.mLen)); + } + + SanityCheck(); + return rv; +} + +nsresult nsStandardURL::SetScheme(const nsACString& input) { + // Strip tabs, newlines, carriage returns from input + nsAutoCString scheme(input); + scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab()); + + LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme.get())); + + if (scheme.IsEmpty()) { + NS_WARNING("cannot remove the scheme from an url"); + return NS_ERROR_UNEXPECTED; + } + if (mScheme.mLen < 0) { + NS_WARNING("uninitialized"); + return NS_ERROR_NOT_INITIALIZED; + } + + if (!net_IsValidScheme(scheme)) { + NS_WARNING("the given url scheme contains invalid characters"); + return NS_ERROR_UNEXPECTED; + } + + if (mSpec.Length() + input.Length() - Scheme().Length() > + StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + InvalidateCache(); + + int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme); + + if (shift) { + mScheme.mLen = scheme.Length(); + ShiftFromAuthority(shift); + } + + // ensure new scheme is lowercase + // + // XXX the string code unfortunately doesn't provide a ToLowerCase + // that operates on a substring. + net_ToLowerCase((char*)mSpec.get(), mScheme.mLen); + + // If the scheme changes the default port also changes. + if (Scheme() == "http"_ns || Scheme() == "ws"_ns) { + mDefaultPort = 80; + } else if (Scheme() == "https"_ns || Scheme() == "wss"_ns) { + mDefaultPort = 443; + } + if (mPort == mDefaultPort) { + MOZ_ALWAYS_SUCCEEDS(SetPort(-1)); + } + + return NS_OK; +} + +nsresult nsStandardURL::SetUserPass(const nsACString& input) { + const nsPromiseFlatCString& userpass = PromiseFlatCString(input); + + LOG(("nsStandardURL::SetUserPass [userpass=%s]\n", userpass.get())); + + if (mURLType == URLTYPE_NO_AUTHORITY) { + if (userpass.IsEmpty()) { + return NS_OK; + } + NS_WARNING("cannot set user:pass on no-auth url"); + return NS_ERROR_UNEXPECTED; + } + if (mAuthority.mLen < 0) { + NS_WARNING("uninitialized"); + return NS_ERROR_NOT_INITIALIZED; + } + + if (mSpec.Length() + input.Length() - Userpass(true).Length() > + StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + InvalidateCache(); + + NS_ASSERTION(mHost.mLen >= 0, "uninitialized"); + + nsresult rv; + uint32_t usernamePos, passwordPos; + int32_t usernameLen, passwordLen; + + rv = mParser->ParseUserInfo(userpass.get(), userpass.Length(), &usernamePos, + &usernameLen, &passwordPos, &passwordLen); + if (NS_FAILED(rv)) { + return rv; + } + + // build new user:pass in |buf| + nsAutoCString buf; + if (usernameLen > 0 || passwordLen > 0) { + nsSegmentEncoder encoder; + bool ignoredOut; + usernameLen = encoder.EncodeSegmentCount( + userpass.get(), URLSegment(usernamePos, usernameLen), + esc_Username | esc_AlwaysCopy, buf, ignoredOut); + if (passwordLen > 0) { + buf.Append(':'); + passwordLen = encoder.EncodeSegmentCount( + userpass.get(), URLSegment(passwordPos, passwordLen), + esc_Password | esc_AlwaysCopy, buf, ignoredOut); + } else { + passwordLen = -1; + } + if (mUsername.mLen < 0 && mPassword.mLen < 0) { + buf.Append('@'); + } + } + + int32_t shift = 0; + + if (mUsername.mLen < 0 && mPassword.mLen < 0) { + // no existing user:pass + if (!buf.IsEmpty()) { + mSpec.Insert(buf, mHost.mPos); + mUsername.mPos = mHost.mPos; + shift = buf.Length(); + } + } else { + // replace existing user:pass + uint32_t userpassLen = 0; + if (mUsername.mLen > 0) { + userpassLen += mUsername.mLen; + } + if (mPassword.mLen > 0) { + userpassLen += (mPassword.mLen + 1); + } + if (buf.IsEmpty()) { + // remove `@` character too + userpassLen++; + } + mSpec.Replace(mAuthority.mPos, userpassLen, buf); + shift = buf.Length() - userpassLen; + } + if (shift) { + ShiftFromHost(shift); + MOZ_DIAGNOSTIC_ASSERT(mAuthority.mLen >= -shift); + mAuthority.mLen += shift; + } + // update positions and lengths + mUsername.mLen = usernameLen > 0 ? usernameLen : -1; + mUsername.mPos = mAuthority.mPos; + mPassword.mLen = passwordLen > 0 ? passwordLen : -1; + if (passwordLen > 0) { + if (mUsername.mLen > 0) { + mPassword.mPos = mUsername.mPos + mUsername.mLen + 1; + } else { + mPassword.mPos = mAuthority.mPos + 1; + } + } + + MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0); + return NS_OK; +} + +nsresult nsStandardURL::SetUsername(const nsACString& input) { + const nsPromiseFlatCString& username = PromiseFlatCString(input); + + LOG(("nsStandardURL::SetUsername [username=%s]\n", username.get())); + + if (mURLType == URLTYPE_NO_AUTHORITY) { + if (username.IsEmpty()) { + return NS_OK; + } + NS_WARNING("cannot set username on no-auth url"); + return NS_ERROR_UNEXPECTED; + } + + if (mSpec.Length() + input.Length() - Username().Length() > + StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + InvalidateCache(); + + // escape username if necessary + nsAutoCString buf; + nsSegmentEncoder encoder; + const nsACString& escUsername = + encoder.EncodeSegment(username, esc_Username, buf); + + int32_t shift = 0; + + if (mUsername.mLen < 0 && escUsername.IsEmpty()) { + return NS_OK; + } + + if (mUsername.mLen < 0 && mPassword.mLen < 0) { + MOZ_ASSERT(!escUsername.IsEmpty(), "Should not be empty at this point"); + mUsername.mPos = mAuthority.mPos; + mSpec.Insert(escUsername + "@"_ns, mUsername.mPos); + shift = escUsername.Length() + 1; + mUsername.mLen = escUsername.Length() > 0 ? escUsername.Length() : -1; + } else { + uint32_t pos = mUsername.mLen < 0 ? mAuthority.mPos : mUsername.mPos; + int32_t len = mUsername.mLen < 0 ? 0 : mUsername.mLen; + + if (mPassword.mLen < 0 && escUsername.IsEmpty()) { + len++; // remove the @ character too + } + shift = ReplaceSegment(pos, len, escUsername); + mUsername.mLen = escUsername.Length() > 0 ? escUsername.Length() : -1; + mUsername.mPos = pos; + } + + if (shift) { + mAuthority.mLen += shift; + ShiftFromPassword(shift); + } + + MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0); + return NS_OK; +} + +nsresult nsStandardURL::SetPassword(const nsACString& input) { + const nsPromiseFlatCString& password = PromiseFlatCString(input); + + auto clearedPassword = MakeScopeExit([&password, this]() { + // Check that if this method is called with the empty string then the + // password is definitely cleared when exiting this method. + if (password.IsEmpty()) { + MOZ_DIAGNOSTIC_ASSERT(this->Password().IsEmpty()); + } + Unused << this; // silence compiler -Wunused-lambda-capture + }); + + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + LOG(("nsStandardURL::SetPassword [password=%s]\n", password.get())); + + if (mURLType == URLTYPE_NO_AUTHORITY) { + if (password.IsEmpty()) { + return NS_OK; + } + NS_WARNING("cannot set password on no-auth url"); + return NS_ERROR_UNEXPECTED; + } + + if (mSpec.Length() + input.Length() - Password().Length() > + StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + InvalidateCache(); + + if (password.IsEmpty()) { + if (mPassword.mLen > 0) { + // cut(":password") + int32_t len = mPassword.mLen; + if (mUsername.mLen < 0) { + len++; // also cut the @ character + } + len++; // for the : character + mSpec.Cut(mPassword.mPos - 1, len); + ShiftFromHost(-len); + mAuthority.mLen -= len; + mPassword.mLen = -1; + } + MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0); + return NS_OK; + } + + // escape password if necessary + nsAutoCString buf; + nsSegmentEncoder encoder; + const nsACString& escPassword = + encoder.EncodeSegment(password, esc_Password, buf); + + int32_t shift; + + if (mPassword.mLen < 0) { + if (mUsername.mLen > 0) { + mPassword.mPos = mUsername.mPos + mUsername.mLen + 1; + mSpec.Insert(":"_ns + escPassword, mPassword.mPos - 1); + shift = escPassword.Length() + 1; + } else { + mPassword.mPos = mAuthority.mPos + 1; + mSpec.Insert(":"_ns + escPassword + "@"_ns, mPassword.mPos - 1); + shift = escPassword.Length() + 2; + } + } else { + shift = ReplaceSegment(mPassword.mPos, mPassword.mLen, escPassword); + } + + if (shift) { + mPassword.mLen = escPassword.Length(); + mAuthority.mLen += shift; + ShiftFromHost(shift); + } + + MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0); + return NS_OK; +} + +void nsStandardURL::FindHostLimit(nsACString::const_iterator& aStart, + nsACString::const_iterator& aEnd) { + for (int32_t i = 0; gHostLimitDigits[i]; ++i) { + nsACString::const_iterator c(aStart); + if (FindCharInReadable(gHostLimitDigits[i], c, aEnd)) { + aEnd = c; + } + } +} + +// If aValue only has a host part and no port number, the port +// will not be reset!!! +nsresult nsStandardURL::SetHostPort(const nsACString& aValue) { + // We cannot simply call nsIURI::SetHost because that would treat the name as + // an IPv6 address (like http:://[server:443]/). We also cannot call + // nsIURI::SetHostPort because that isn't implemented. Sadfaces. + + nsACString::const_iterator start, end; + aValue.BeginReading(start); + aValue.EndReading(end); + nsACString::const_iterator iter(start); + bool isIPv6 = false; + + FindHostLimit(start, end); + + if (*start == '[') { // IPv6 address + if (!FindCharInReadable(']', iter, end)) { + // the ] character is missing + return NS_ERROR_MALFORMED_URI; + } + // iter now at the ']' character + isIPv6 = true; + } else { + nsACString::const_iterator iter2(start); + if (FindCharInReadable(']', iter2, end)) { + // if the first char isn't [ then there should be no ] character + return NS_ERROR_MALFORMED_URI; + } + } + + FindCharInReadable(':', iter, end); + + if (!isIPv6 && iter != end) { + nsACString::const_iterator iter2(iter); + iter2++; // Skip over the first ':' character + if (FindCharInReadable(':', iter2, end)) { + // If there is more than one ':' character it suggests an IPv6 + // The format should be [2001::1]:80 where the port is optional + return NS_ERROR_MALFORMED_URI; + } + } + + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + nsresult rv = SetHost(Substring(start, iter)); + NS_ENSURE_SUCCESS(rv, rv); + + if (iter == end) { + // does not end in colon + return NS_OK; + } + + iter++; // advance over the colon + if (iter == end) { + // port number is missing + return NS_OK; + } + + nsCString portStr(Substring(iter, end)); + int32_t port = portStr.ToInteger(&rv); + if (NS_FAILED(rv)) { + // Failure parsing the port number + return NS_OK; + } + + Unused << SetPort(port); + return NS_OK; +} + +nsresult nsStandardURL::SetHost(const nsACString& input) { + nsAutoCString hostname(input); + hostname.StripTaggedASCII(ASCIIMask::MaskCRLFTab()); + + nsACString::const_iterator start, end; + hostname.BeginReading(start); + hostname.EndReading(end); + + FindHostLimit(start, end); + + // Do percent decoding on the the input. + nsAutoCString flat; + NS_UnescapeURL(hostname.BeginReading(), end - start, + esc_AlwaysCopy | esc_Host, flat); + const char* host = flat.get(); + + LOG(("nsStandardURL::SetHost [host=%s]\n", host)); + + if (mURLType == URLTYPE_NO_AUTHORITY) { + if (flat.IsEmpty()) { + return NS_OK; + } + NS_WARNING("cannot set host on no-auth url"); + return NS_ERROR_UNEXPECTED; + } + if (flat.IsEmpty()) { + // Setting an empty hostname is not allowed for + // URLTYPE_STANDARD and URLTYPE_AUTHORITY. + return NS_ERROR_UNEXPECTED; + } + + if (strlen(host) < flat.Length()) { + return NS_ERROR_MALFORMED_URI; // found embedded null + } + + // For consistency with SetSpec/nsURLParsers, don't allow spaces + // in the hostname. + if (strchr(host, ' ')) { + return NS_ERROR_MALFORMED_URI; + } + + if (mSpec.Length() + strlen(host) - Host().Length() > + StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + InvalidateCache(); + + uint32_t len; + nsAutoCString hostBuf; + nsresult rv = NormalizeIDN(flat, hostBuf); + if (NS_FAILED(rv)) { + return rv; + } + + if (!SegmentIs(mScheme, "resource") && !SegmentIs(mScheme, "chrome")) { + nsAutoCString ipString; + if (hostBuf.Length() > 0 && hostBuf.First() == '[' && + hostBuf.Last() == ']' && + ValidIPv6orHostname(hostBuf.get(), hostBuf.Length())) { + rv = (nsresult)rusturl_parse_ipv6addr(&hostBuf, &ipString); + if (NS_FAILED(rv)) { + return rv; + } + hostBuf = ipString; + } else { + if (EndsInANumber(hostBuf)) { + rv = NormalizeIPv4(hostBuf, ipString); + if (NS_FAILED(rv)) { + return rv; + } + hostBuf = ipString; + } + } + } + + // NormalizeIDN always copies if the call was successful + host = hostBuf.get(); + len = hostBuf.Length(); + + if (!ValidIPv6orHostname(host, len)) { + return NS_ERROR_MALFORMED_URI; + } + + if (mHost.mLen < 0) { + int port_length = 0; + if (mPort != -1) { + nsAutoCString buf; + buf.Assign(':'); + buf.AppendInt(mPort); + port_length = buf.Length(); + } + if (mAuthority.mLen > 0) { + mHost.mPos = mAuthority.mPos + mAuthority.mLen - port_length; + mHost.mLen = 0; + } else if (mScheme.mLen > 0) { + mHost.mPos = mScheme.mPos + mScheme.mLen + 3; + mHost.mLen = 0; + } + } + + int32_t shift = ReplaceSegment(mHost.mPos, mHost.mLen, host, len); + + if (shift) { + mHost.mLen = len; + mAuthority.mLen += shift; + ShiftFromPath(shift); + } + + // Now canonicalize the host to lowercase + net_ToLowerCase(mSpec.BeginWriting() + mHost.mPos, mHost.mLen); + return NS_OK; +} + +nsresult nsStandardURL::SetPort(int32_t port) { + LOG(("nsStandardURL::SetPort [port=%d]\n", port)); + + if ((port == mPort) || (mPort == -1 && port == mDefaultPort)) { + return NS_OK; + } + + // ports must be >= 0 and 16 bit + // -1 == use default + if (port < -1 || port > std::numeric_limits<uint16_t>::max()) { + return NS_ERROR_MALFORMED_URI; + } + + if (mURLType == URLTYPE_NO_AUTHORITY) { + NS_WARNING("cannot set port on no-auth url"); + return NS_ERROR_UNEXPECTED; + } + + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + InvalidateCache(); + if (port == mDefaultPort) { + port = -1; + } + + ReplacePortInSpec(port); + + mPort = port; + return NS_OK; +} + +/** + * Replaces the existing port in mSpec with aNewPort. + * + * The caller is responsible for: + * - Calling InvalidateCache (since our mSpec is changing). + * - Checking whether aNewPort is mDefaultPort (in which case the + * caller should pass aNewPort=-1). + */ +void nsStandardURL::ReplacePortInSpec(int32_t aNewPort) { + NS_ASSERTION(aNewPort != mDefaultPort || mDefaultPort == -1, + "Caller should check its passed-in value and pass -1 instead of " + "mDefaultPort, to avoid encoding default port into mSpec"); + + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + // Create the (possibly empty) string that we're planning to replace: + nsAutoCString buf; + if (mPort != -1) { + buf.Assign(':'); + buf.AppendInt(mPort); + } + // Find the position & length of that string: + const uint32_t replacedLen = buf.Length(); + const uint32_t replacedStart = + mAuthority.mPos + mAuthority.mLen - replacedLen; + + // Create the (possibly empty) replacement string: + if (aNewPort == -1) { + buf.Truncate(); + } else { + buf.Assign(':'); + buf.AppendInt(aNewPort); + } + // Perform the replacement: + mSpec.Replace(replacedStart, replacedLen, buf); + + // Bookkeeping to reflect the new length: + int32_t shift = buf.Length() - replacedLen; + mAuthority.mLen += shift; + ShiftFromPath(shift); +} + +nsresult nsStandardURL::SetPathQueryRef(const nsACString& input) { + const nsPromiseFlatCString& path = PromiseFlatCString(input); + LOG(("nsStandardURL::SetPathQueryRef [path=%s]\n", path.get())); + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + InvalidateCache(); + + if (!path.IsEmpty()) { + nsAutoCString spec; + + spec.Assign(mSpec.get(), mPath.mPos); + if (path.First() != '/') { + spec.Append('/'); + } + spec.Append(path); + + return SetSpecInternal(spec); + } + if (mPath.mLen >= 1) { + mSpec.Cut(mPath.mPos + 1, mPath.mLen - 1); + // these contain only a '/' + mPath.mLen = 1; + mDirectory.mLen = 1; + mFilepath.mLen = 1; + // these are no longer defined + mBasename.mLen = -1; + mExtension.mLen = -1; + mQuery.mLen = -1; + mRef.mLen = -1; + } + return NS_OK; +} + +// When updating this also update SubstitutingURL::Mutator +// Queries this list of interfaces. If none match, it queries mURI. +NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsStandardURL::Mutator, nsIURISetters, + nsIURIMutator, nsIStandardURLMutator, + nsIURLMutator, nsIFileURLMutator, + nsISerializable) + +NS_IMETHODIMP +nsStandardURL::Mutate(nsIURIMutator** aMutator) { + RefPtr<nsStandardURL::Mutator> mutator = new nsStandardURL::Mutator(); + nsresult rv = mutator->InitFromURI(this); + if (NS_FAILED(rv)) { + return rv; + } + mutator.forget(aMutator); + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::Equals(nsIURI* unknownOther, bool* result) { + return EqualsInternal(unknownOther, eHonorRef, result); +} + +NS_IMETHODIMP +nsStandardURL::EqualsExceptRef(nsIURI* unknownOther, bool* result) { + return EqualsInternal(unknownOther, eIgnoreRef, result); +} + +nsresult nsStandardURL::EqualsInternal( + nsIURI* unknownOther, nsStandardURL::RefHandlingEnum refHandlingMode, + bool* result) { + NS_ENSURE_ARG_POINTER(unknownOther); + MOZ_ASSERT(result, "null pointer"); + + RefPtr<nsStandardURL> other; + nsresult rv = + unknownOther->QueryInterface(kThisImplCID, getter_AddRefs(other)); + if (NS_FAILED(rv)) { + *result = false; + return NS_OK; + } + + // First, check whether one URIs is an nsIFileURL while the other + // is not. If that's the case, they're different. + if (mSupportsFileURL != other->mSupportsFileURL) { + *result = false; + return NS_OK; + } + + // Next check parts of a URI that, if different, automatically make the + // URIs different + if (!SegmentIs(mScheme, other->mSpec.get(), other->mScheme) || + // Check for host manually, since conversion to file will + // ignore the host! + !SegmentIs(mHost, other->mSpec.get(), other->mHost) || + !SegmentIs(mQuery, other->mSpec.get(), other->mQuery) || + !SegmentIs(mUsername, other->mSpec.get(), other->mUsername) || + !SegmentIs(mPassword, other->mSpec.get(), other->mPassword) || + Port() != other->Port()) { + // No need to compare files or other URI parts -- these are different + // beasties + *result = false; + return NS_OK; + } + + if (refHandlingMode == eHonorRef && + !SegmentIs(mRef, other->mSpec.get(), other->mRef)) { + *result = false; + return NS_OK; + } + + // Then check for exact identity of URIs. If we have it, they're equal + if (SegmentIs(mDirectory, other->mSpec.get(), other->mDirectory) && + SegmentIs(mBasename, other->mSpec.get(), other->mBasename) && + SegmentIs(mExtension, other->mSpec.get(), other->mExtension)) { + *result = true; + return NS_OK; + } + + // At this point, the URIs are not identical, but they only differ in the + // directory/filename/extension. If these are file URLs, then get the + // corresponding file objects and compare those, since two filenames that + // differ, eg, only in case could still be equal. + if (mSupportsFileURL) { + // Assume not equal for failure cases... but failures in GetFile are + // really failures, more or less, so propagate them to caller. + *result = false; + + rv = EnsureFile(); + nsresult rv2 = other->EnsureFile(); + // special case for resource:// urls that don't resolve to files + if (rv == NS_ERROR_NO_INTERFACE && rv == rv2) { + return NS_OK; + } + + if (NS_FAILED(rv)) { + LOG(("nsStandardURL::Equals [this=%p spec=%s] failed to ensure file", + this, mSpec.get())); + return rv; + } + NS_ASSERTION(mFile, "EnsureFile() lied!"); + rv = rv2; + if (NS_FAILED(rv)) { + LOG( + ("nsStandardURL::Equals [other=%p spec=%s] other failed to ensure " + "file", + other.get(), other->mSpec.get())); + return rv; + } + NS_ASSERTION(other->mFile, "EnsureFile() lied!"); + return mFile->Equals(other->mFile, result); + } + + // The URLs are not identical, and they do not correspond to the + // same file, so they are different. + *result = false; + + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::SchemeIs(const char* scheme, bool* result) { + MOZ_ASSERT(result, "null pointer"); + if (!scheme) { + *result = false; + return NS_OK; + } + + *result = SegmentIs(mScheme, scheme); + return NS_OK; +} + +/* virtual */ nsStandardURL* nsStandardURL::StartClone() { + nsStandardURL* clone = new nsStandardURL(); + return clone; +} + +nsresult nsStandardURL::Clone(nsIURI** aURI) { + return CloneInternal(eHonorRef, ""_ns, aURI); +} + +nsresult nsStandardURL::CloneInternal( + nsStandardURL::RefHandlingEnum aRefHandlingMode, const nsACString& aNewRef, + nsIURI** aClone) + +{ + RefPtr<nsStandardURL> clone = StartClone(); + if (!clone) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Copy local members into clone. + // Also copies the cached members mFile, mDisplayHost + clone->CopyMembers(this, aRefHandlingMode, aNewRef, true); + + clone.forget(aClone); + return NS_OK; +} + +nsresult nsStandardURL::CopyMembers( + nsStandardURL* source, nsStandardURL::RefHandlingEnum refHandlingMode, + const nsACString& newRef, bool copyCached) { + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + mSpec = source->mSpec; + mDefaultPort = source->mDefaultPort; + mPort = source->mPort; + mScheme = source->mScheme; + mAuthority = source->mAuthority; + mUsername = source->mUsername; + mPassword = source->mPassword; + mHost = source->mHost; + mPath = source->mPath; + mFilepath = source->mFilepath; + mDirectory = source->mDirectory; + mBasename = source->mBasename; + mExtension = source->mExtension; + mQuery = source->mQuery; + mRef = source->mRef; + mURLType = source->mURLType; + mParser = source->mParser; + mSupportsFileURL = source->mSupportsFileURL; + mCheckedIfHostA = source->mCheckedIfHostA; + mDisplayHost = source->mDisplayHost; + + if (copyCached) { + mFile = source->mFile; + } else { + InvalidateCache(true); + } + + if (refHandlingMode == eIgnoreRef) { + SetRef(""_ns); + } else if (refHandlingMode == eReplaceRef) { + SetRef(newRef); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::Resolve(const nsACString& in, nsACString& out) { + const nsPromiseFlatCString& flat = PromiseFlatCString(in); + // filter out unexpected chars "\r\n\t" if necessary + nsAutoCString buf; + net_FilterURIString(flat, buf); + + const char* relpath = buf.get(); + int32_t relpathLen = buf.Length(); + + char* result = nullptr; + + LOG(("nsStandardURL::Resolve [this=%p spec=%s relpath=%s]\n", this, + mSpec.get(), relpath)); + + NS_ASSERTION(mParser, "no parser: unitialized"); + + // NOTE: there is no need for this function to produce normalized + // output. normalization will occur when the result is used to + // initialize a nsStandardURL object. + + if (mScheme.mLen < 0) { + NS_WARNING("unable to Resolve URL: this URL not initialized"); + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv; + URLSegment scheme; + char* resultPath = nullptr; + bool relative = false; + uint32_t offset = 0; + netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL; + + nsAutoCString baseProtocol(Scheme()); + nsAutoCString protocol; + rv = net_ExtractURLScheme(buf, protocol); + + // Normally, if we parse a scheme, then it's an absolute URI. But because + // we still support a deprecated form of relative URIs such as: http:file or + // http:/path/file we can't do that for all protocols. + // So we just make sure that if there a protocol, it's the same as the + // current one, otherwise we treat it as an absolute URI. + if (NS_SUCCEEDED(rv) && protocol != baseProtocol) { + out = buf; + return NS_OK; + } + + // relative urls should never contain a host, so we always want to use + // the noauth url parser. + // use it to extract a possible scheme + uint32_t schemePos = scheme.mPos; + int32_t schemeLen = scheme.mLen; + rv = mParser->ParseURL(relpath, relpathLen, &schemePos, &schemeLen, nullptr, + nullptr, nullptr, nullptr); + + // if the parser fails (for example because there is no valid scheme) + // reset the scheme and assume a relative url + if (NS_FAILED(rv)) { + scheme.Reset(); + } + + scheme.mPos = schemePos; + scheme.mLen = schemeLen; + + protocol.Assign(Segment(scheme)); + + // We need to do backslash replacement for the following cases: + // 1. The input is an absolute path with a http/https/ftp scheme + // 2. The input is a relative path, and the base URL has a http/https/ftp + // scheme + if ((protocol.IsEmpty() && IsSpecialProtocol(baseProtocol)) || + IsSpecialProtocol(protocol)) { + auto* start = buf.BeginWriting(); + auto* end = buf.EndWriting(); + while (start != end) { + if (*start == '?' || *start == '#') { + break; + } + if (*start == '\\') { + *start = '/'; + } + start++; + } + } + + if (scheme.mLen >= 0) { + // add some flags to coalesceFlag if it is an ftp-url + // need this later on when coalescing the resulting URL + if (SegmentIs(relpath, scheme, "ftp", true)) { + coalesceFlag = + (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT | + NET_COALESCE_DOUBLE_SLASH_IS_ROOT); + } + // this URL appears to be absolute + // but try to find out more + if (SegmentIs(mScheme, relpath, scheme, true)) { + // mScheme and Scheme are the same + // but this can still be relative + if (strncmp(relpath + scheme.mPos + scheme.mLen, "://", 3) == 0) { + // now this is really absolute + // because a :// follows the scheme + result = NS_xstrdup(relpath); + } else { + // This is a deprecated form of relative urls like + // http:file or http:/path/file + // we will support it for now ... + relative = true; + offset = scheme.mLen + 1; + } + } else { + // the schemes are not the same, we are also done + // because we have to assume this is absolute + result = NS_xstrdup(relpath); + } + } else { + // add some flags to coalesceFlag if it is an ftp-url + // need this later on when coalescing the resulting URL + if (SegmentIs(mScheme, "ftp")) { + coalesceFlag = + (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT | + NET_COALESCE_DOUBLE_SLASH_IS_ROOT); + } + if (relpath[0] == '/' && relpath[1] == '/') { + // this URL //host/path is almost absolute + result = AppendToSubstring(mScheme.mPos, mScheme.mLen + 1, relpath); + } else { + // then it must be relative + relative = true; + } + } + if (relative) { + uint32_t len = 0; + const char* realrelpath = relpath + offset; + switch (*realrelpath) { + case '/': + // overwrite everything after the authority + len = mAuthority.mPos + mAuthority.mLen; + break; + case '?': + // overwrite the existing ?query and #ref + if (mQuery.mLen >= 0) { + len = mQuery.mPos - 1; + } else if (mRef.mLen >= 0) { + len = mRef.mPos - 1; + } else { + len = mPath.mPos + mPath.mLen; + } + break; + case '#': + case '\0': + // overwrite the existing #ref + if (mRef.mLen < 0) { + len = mPath.mPos + mPath.mLen; + } else { + len = mRef.mPos - 1; + } + break; + default: + if (coalesceFlag & NET_COALESCE_DOUBLE_SLASH_IS_ROOT) { + if (Filename().Equals("%2F"_ns, nsCaseInsensitiveCStringComparator)) { + // if ftp URL ends with %2F then simply + // append relative part because %2F also + // marks the root directory with ftp-urls + len = mFilepath.mPos + mFilepath.mLen; + } else { + // overwrite everything after the directory + len = mDirectory.mPos + mDirectory.mLen; + } + } else { + // overwrite everything after the directory + len = mDirectory.mPos + mDirectory.mLen; + } + } + result = AppendToSubstring(0, len, realrelpath); + // locate result path + resultPath = result + mPath.mPos; + } + if (!result) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (resultPath) { + net_CoalesceDirs(coalesceFlag, resultPath); + } else { + // locate result path + resultPath = strstr(result, "://"); + if (resultPath) { + // If there are multiple slashes after :// we must ignore them + // otherwise net_CoalesceDirs may think the host is a part of the path. + resultPath += 3; + if (protocol.IsEmpty() && Scheme() != "file") { + while (*resultPath == '/') { + resultPath++; + } + } + resultPath = strchr(resultPath, '/'); + if (resultPath) { + net_CoalesceDirs(coalesceFlag, resultPath); + } + } + } + out.Adopt(result); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetCommonBaseSpec(nsIURI* uri2, nsACString& aResult) { + NS_ENSURE_ARG_POINTER(uri2); + + // if uri's are equal, then return uri as is + bool isEquals = false; + if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals) { + return GetSpec(aResult); + } + + aResult.Truncate(); + + // check pre-path; if they don't match, then return empty string + RefPtr<nsStandardURL> stdurl2; + nsresult rv = uri2->QueryInterface(kThisImplCID, getter_AddRefs(stdurl2)); + isEquals = NS_SUCCEEDED(rv) && + SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme) && + SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost) && + SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername) && + SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword) && + (Port() == stdurl2->Port()); + if (!isEquals) { + return NS_OK; + } + + // scan for first mismatched character + const char *thisIndex, *thatIndex, *startCharPos; + startCharPos = mSpec.get() + mDirectory.mPos; + thisIndex = startCharPos; + thatIndex = stdurl2->mSpec.get() + mDirectory.mPos; + while ((*thisIndex == *thatIndex) && *thisIndex) { + thisIndex++; + thatIndex++; + } + + // backup to just after previous slash so we grab an appropriate path + // segment such as a directory (not partial segments) + // todo: also check for file matches which include '?' and '#' + while ((thisIndex != startCharPos) && (*(thisIndex - 1) != '/')) { + thisIndex--; + } + + // grab spec from beginning to thisIndex + aResult = Substring(mSpec, mScheme.mPos, thisIndex - mSpec.get()); + + return rv; +} + +NS_IMETHODIMP +nsStandardURL::GetRelativeSpec(nsIURI* uri2, nsACString& aResult) { + NS_ENSURE_ARG_POINTER(uri2); + + aResult.Truncate(); + + // if uri's are equal, then return empty string + bool isEquals = false; + if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals) { + return NS_OK; + } + + RefPtr<nsStandardURL> stdurl2; + nsresult rv = uri2->QueryInterface(kThisImplCID, getter_AddRefs(stdurl2)); + isEquals = NS_SUCCEEDED(rv) && + SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme) && + SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost) && + SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername) && + SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword) && + (Port() == stdurl2->Port()); + if (!isEquals) { + return uri2->GetSpec(aResult); + } + + // scan for first mismatched character + const char *thisIndex, *thatIndex, *startCharPos; + startCharPos = mSpec.get() + mDirectory.mPos; + thisIndex = startCharPos; + thatIndex = stdurl2->mSpec.get() + mDirectory.mPos; + +#ifdef XP_WIN + bool isFileScheme = SegmentIs(mScheme, "file"); + if (isFileScheme) { + // on windows, we need to match the first segment of the path + // if these don't match then we need to return an absolute path + // skip over any leading '/' in path + while ((*thisIndex == *thatIndex) && (*thisIndex == '/')) { + thisIndex++; + thatIndex++; + } + // look for end of first segment + while ((*thisIndex == *thatIndex) && *thisIndex && (*thisIndex != '/')) { + thisIndex++; + thatIndex++; + } + + // if we didn't match through the first segment, return absolute path + if ((*thisIndex != '/') || (*thatIndex != '/')) { + return uri2->GetSpec(aResult); + } + } +#endif + + while ((*thisIndex == *thatIndex) && *thisIndex) { + thisIndex++; + thatIndex++; + } + + // backup to just after previous slash so we grab an appropriate path + // segment such as a directory (not partial segments) + // todo: also check for file matches with '#' and '?' + while ((*(thatIndex - 1) != '/') && (thatIndex != startCharPos)) { + thatIndex--; + } + + const char* limit = mSpec.get() + mFilepath.mPos + mFilepath.mLen; + + // need to account for slashes and add corresponding "../" + for (; thisIndex <= limit && *thisIndex; ++thisIndex) { + if (*thisIndex == '/') { + aResult.AppendLiteral("../"); + } + } + + // grab spec from thisIndex to end + uint32_t startPos = stdurl2->mScheme.mPos + thatIndex - stdurl2->mSpec.get(); + aResult.Append( + Substring(stdurl2->mSpec, startPos, stdurl2->mSpec.Length() - startPos)); + + return rv; +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsIURL +//---------------------------------------------------------------------------- + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetFilePath(nsACString& result) { + result = Filepath(); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetQuery(nsACString& result) { + result = Query(); + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetHasQuery(bool* result) { + *result = (mQuery.mLen >= 0); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetRef(nsACString& result) { + result = Ref(); + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetHasRef(bool* result) { + *result = (mRef.mLen >= 0); + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetHasUserPass(bool* result) { + *result = (mUsername.mLen >= 0) || (mPassword.mLen >= 0); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetDirectory(nsACString& result) { + result = Directory(); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetFileName(nsACString& result) { + result = Filename(); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetFileBaseName(nsACString& result) { + result = Basename(); + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetFileExtension(nsACString& result) { + result = Extension(); + return NS_OK; +} + +nsresult nsStandardURL::SetFilePath(const nsACString& input) { + nsAutoCString str(input); + str.StripTaggedASCII(ASCIIMask::MaskCRLFTab()); + const char* filepath = str.get(); + + LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath)); + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + // if there isn't a filepath, then there can't be anything + // after the path either. this url is likely uninitialized. + if (mFilepath.mLen < 0) { + return SetPathQueryRef(str); + } + + if (!str.IsEmpty()) { + nsAutoCString spec; + uint32_t dirPos, basePos, extPos; + int32_t dirLen, baseLen, extLen; + nsresult rv; + + if (IsSpecialProtocol(mSpec)) { + // Bug 1873955: Replace all backslashes with slashes when parsing paths + // Stop when we reach the query or the hash. + auto* start = str.BeginWriting(); + auto* end = str.EndWriting(); + while (start != end) { + if (*start == '?' || *start == '#') { + break; + } + if (*start == '\\') { + *start = '/'; + } + start++; + } + } + + rv = mParser->ParseFilePath(filepath, str.Length(), &dirPos, &dirLen, + &basePos, &baseLen, &extPos, &extLen); + if (NS_FAILED(rv)) { + return rv; + } + + // build up new candidate spec + spec.Assign(mSpec.get(), mPath.mPos); + + // ensure leading '/' + if (filepath[dirPos] != '/') { + spec.Append('/'); + } + + nsSegmentEncoder encoder; + + // append encoded filepath components + if (dirLen > 0) { + encoder.EncodeSegment( + Substring(filepath + dirPos, filepath + dirPos + dirLen), + esc_Directory | esc_AlwaysCopy, spec); + } + if (baseLen > 0) { + encoder.EncodeSegment( + Substring(filepath + basePos, filepath + basePos + baseLen), + esc_FileBaseName | esc_AlwaysCopy, spec); + } + if (extLen >= 0) { + spec.Append('.'); + if (extLen > 0) { + encoder.EncodeSegment( + Substring(filepath + extPos, filepath + extPos + extLen), + esc_FileExtension | esc_AlwaysCopy, spec); + } + } + + // compute the ending position of the current filepath + if (mFilepath.mLen >= 0) { + uint32_t end = mFilepath.mPos + mFilepath.mLen; + if (mSpec.Length() > end) { + spec.Append(mSpec.get() + end, mSpec.Length() - end); + } + } + + return SetSpecInternal(spec); + } + if (mPath.mLen > 1) { + mSpec.Cut(mPath.mPos + 1, mFilepath.mLen - 1); + // left shift query, and ref + ShiftFromQuery(1 - mFilepath.mLen); + // One character for '/', and if we have a query or ref we add their + // length and one extra for each '?' or '#' characters + mPath.mLen = 1 + (mQuery.mLen >= 0 ? (mQuery.mLen + 1) : 0) + + (mRef.mLen >= 0 ? (mRef.mLen + 1) : 0); + // these contain only a '/' + mDirectory.mLen = 1; + mFilepath.mLen = 1; + // these are no longer defined + mBasename.mLen = -1; + mExtension.mLen = -1; + } + return NS_OK; +} + +inline bool IsUTFEncoding(const Encoding* aEncoding) { + return aEncoding == UTF_8_ENCODING || aEncoding == UTF_16BE_ENCODING || + aEncoding == UTF_16LE_ENCODING; +} + +nsresult nsStandardURL::SetQuery(const nsACString& input) { + return SetQueryWithEncoding(input, nullptr); +} + +nsresult nsStandardURL::SetQueryWithEncoding(const nsACString& input, + const Encoding* encoding) { + const nsPromiseFlatCString& flat = PromiseFlatCString(input); + const char* query = flat.get(); + + LOG(("nsStandardURL::SetQuery [query=%s]\n", query)); + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + if (IsUTFEncoding(encoding)) { + encoding = nullptr; + } + + if (mPath.mLen < 0) { + return SetPathQueryRef(flat); + } + + if (mSpec.Length() + input.Length() - Query().Length() > + StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + InvalidateCache(); + + if (flat.IsEmpty()) { + // remove existing query + if (mQuery.mLen >= 0) { + // remove query and leading '?' + mSpec.Cut(mQuery.mPos - 1, mQuery.mLen + 1); + ShiftFromRef(-(mQuery.mLen + 1)); + mPath.mLen -= (mQuery.mLen + 1); + mQuery.mPos = 0; + mQuery.mLen = -1; + } + return NS_OK; + } + + // filter out unexpected chars "\r\n\t" if necessary + nsAutoCString filteredURI(flat); + filteredURI.StripTaggedASCII(ASCIIMask::MaskCRLFTab()); + + query = filteredURI.get(); + int32_t queryLen = filteredURI.Length(); + if (query[0] == '?') { + query++; + queryLen--; + } + + if (mQuery.mLen < 0) { + if (mRef.mLen < 0) { + mQuery.mPos = mSpec.Length(); + } else { + mQuery.mPos = mRef.mPos - 1; + } + mSpec.Insert('?', mQuery.mPos); + mQuery.mPos++; + mQuery.mLen = 0; + // the insertion pushes these out by 1 + mPath.mLen++; + mRef.mPos++; + } + + // encode query if necessary + nsAutoCString buf; + bool encoded; + nsSegmentEncoder encoder(encoding); + encoder.EncodeSegmentCount(query, URLSegment(0, queryLen), esc_Query, buf, + encoded); + if (encoded) { + query = buf.get(); + queryLen = buf.Length(); + } + + int32_t shift = ReplaceSegment(mQuery.mPos, mQuery.mLen, query, queryLen); + + if (shift) { + mQuery.mLen = queryLen; + mPath.mLen += shift; + ShiftFromRef(shift); + } + return NS_OK; +} + +nsresult nsStandardURL::SetRef(const nsACString& input) { + const nsPromiseFlatCString& flat = PromiseFlatCString(input); + const char* ref = flat.get(); + + LOG(("nsStandardURL::SetRef [ref=%s]\n", ref)); + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + if (mPath.mLen < 0) { + return SetPathQueryRef(flat); + } + + if (mSpec.Length() + input.Length() - Ref().Length() > + StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + InvalidateCache(); + + if (input.IsEmpty()) { + // remove existing ref + if (mRef.mLen >= 0) { + // remove ref and leading '#' + mSpec.Cut(mRef.mPos - 1, mRef.mLen + 1); + mPath.mLen -= (mRef.mLen + 1); + mRef.mPos = 0; + mRef.mLen = -1; + } + return NS_OK; + } + + // filter out unexpected chars "\r\n\t" if necessary + nsAutoCString filteredURI(flat); + filteredURI.StripTaggedASCII(ASCIIMask::MaskCRLFTab()); + + ref = filteredURI.get(); + int32_t refLen = filteredURI.Length(); + if (ref[0] == '#') { + ref++; + refLen--; + } + + if (mRef.mLen < 0) { + mSpec.Append('#'); + ++mPath.mLen; // Include the # in the path. + mRef.mPos = mSpec.Length(); + mRef.mLen = 0; + } + + // If precent encoding is necessary, `ref` will point to `buf`'s content. + // `buf` needs to outlive any use of the `ref` pointer. + nsAutoCString buf; + // encode ref if necessary + bool encoded; + nsSegmentEncoder encoder; + encoder.EncodeSegmentCount(ref, URLSegment(0, refLen), esc_Ref, buf, encoded); + if (encoded) { + ref = buf.get(); + refLen = buf.Length(); + } + + int32_t shift = ReplaceSegment(mRef.mPos, mRef.mLen, ref, refLen); + mPath.mLen += shift; + mRef.mLen = refLen; + return NS_OK; +} + +nsresult nsStandardURL::SetFileNameInternal(const nsACString& input) { + const nsPromiseFlatCString& flat = PromiseFlatCString(input); + const char* filename = flat.get(); + + LOG(("nsStandardURL::SetFileNameInternal [filename=%s]\n", filename)); + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + + if (mPath.mLen < 0) { + return SetPathQueryRef(flat); + } + + if (mSpec.Length() + input.Length() - Filename().Length() > + StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + int32_t shift = 0; + + if (!(filename && *filename)) { + // remove the filename + if (mBasename.mLen > 0) { + if (mExtension.mLen >= 0) { + mBasename.mLen += (mExtension.mLen + 1); + } + mSpec.Cut(mBasename.mPos, mBasename.mLen); + shift = -mBasename.mLen; + mBasename.mLen = 0; + mExtension.mLen = -1; + } + } else { + nsresult rv; + uint32_t basenamePos = 0; + int32_t basenameLen = -1; + uint32_t extensionPos = 0; + int32_t extensionLen = -1; + // let the parser locate the basename and extension + rv = mParser->ParseFileName(filename, flat.Length(), &basenamePos, + &basenameLen, &extensionPos, &extensionLen); + if (NS_FAILED(rv)) { + return rv; + } + + URLSegment basename(basenamePos, basenameLen); + URLSegment extension(extensionPos, extensionLen); + + if (basename.mLen < 0) { + // remove existing filename + if (mBasename.mLen >= 0) { + uint32_t len = mBasename.mLen; + if (mExtension.mLen >= 0) { + len += (mExtension.mLen + 1); + } + mSpec.Cut(mBasename.mPos, len); + shift = -int32_t(len); + mBasename.mLen = 0; + mExtension.mLen = -1; + } + } else { + nsAutoCString newFilename; + bool ignoredOut; + nsSegmentEncoder encoder; + basename.mLen = encoder.EncodeSegmentCount( + filename, basename, esc_FileBaseName | esc_AlwaysCopy, newFilename, + ignoredOut); + if (extension.mLen >= 0) { + newFilename.Append('.'); + extension.mLen = encoder.EncodeSegmentCount( + filename, extension, esc_FileExtension | esc_AlwaysCopy, + newFilename, ignoredOut); + } + + if (mBasename.mLen < 0) { + // insert new filename + mBasename.mPos = mDirectory.mPos + mDirectory.mLen; + mSpec.Insert(newFilename, mBasename.mPos); + shift = newFilename.Length(); + } else { + // replace existing filename + uint32_t oldLen = uint32_t(mBasename.mLen); + if (mExtension.mLen >= 0) { + oldLen += (mExtension.mLen + 1); + } + mSpec.Replace(mBasename.mPos, oldLen, newFilename); + shift = newFilename.Length() - oldLen; + } + + mBasename.mLen = basename.mLen; + mExtension.mLen = extension.mLen; + if (mExtension.mLen >= 0) { + mExtension.mPos = mBasename.mPos + mBasename.mLen + 1; + } + } + } + if (shift) { + ShiftFromQuery(shift); + mFilepath.mLen += shift; + mPath.mLen += shift; + } + return NS_OK; +} + +nsresult nsStandardURL::SetFileBaseNameInternal(const nsACString& input) { + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + nsAutoCString extension; + nsresult rv = GetFileExtension(extension); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString newFileName(input); + + if (!extension.IsEmpty()) { + newFileName.Append('.'); + newFileName.Append(extension); + } + + return SetFileNameInternal(newFileName); +} + +nsresult nsStandardURL::SetFileExtensionInternal(const nsACString& input) { + auto onExitGuard = MakeScopeExit([&] { SanityCheck(); }); + nsAutoCString newFileName; + nsresult rv = GetFileBaseName(newFileName); + NS_ENSURE_SUCCESS(rv, rv); + + if (!input.IsEmpty()) { + newFileName.Append('.'); + newFileName.Append(input); + } + + return SetFileNameInternal(newFileName); +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsIFileURL +//---------------------------------------------------------------------------- + +nsresult nsStandardURL::EnsureFile() { + MOZ_ASSERT(mSupportsFileURL, + "EnsureFile() called on a URL that doesn't support files!"); + + if (mFile) { + // Nothing to do + return NS_OK; + } + + // Parse the spec if we don't have a cached result + if (mSpec.IsEmpty()) { + NS_WARNING("url not initialized"); + return NS_ERROR_NOT_INITIALIZED; + } + + if (!SegmentIs(mScheme, "file")) { + NS_WARNING("not a file URL"); + return NS_ERROR_FAILURE; + } + + return net_GetFileFromURLSpec(mSpec, getter_AddRefs(mFile)); +} + +NS_IMETHODIMP +nsStandardURL::GetFile(nsIFile** result) { + MOZ_ASSERT(mSupportsFileURL, + "GetFile() called on a URL that doesn't support files!"); + + nsresult rv = EnsureFile(); + if (NS_FAILED(rv)) { + return rv; + } + + if (LOG_ENABLED()) { + LOG(("nsStandardURL::GetFile [this=%p spec=%s resulting_path=%s]\n", this, + mSpec.get(), mFile->HumanReadablePath().get())); + } + + // clone the file, so the caller can modify it. + // XXX nsIFileURL.idl specifies that the consumer must _not_ modify the + // nsIFile returned from this method; but it seems that some folks do + // (see bug 161921). until we can be sure that all the consumers are + // behaving themselves, we'll stay on the safe side and clone the file. + // see bug 212724 about fixing the consumers. + return mFile->Clone(result); +} + +nsresult nsStandardURL::SetFile(nsIFile* file) { + NS_ENSURE_ARG_POINTER(file); + + nsresult rv; + nsAutoCString url; + + rv = net_GetURLSpecFromFile(file, url); + if (NS_FAILED(rv)) { + return rv; + } + + uint32_t oldURLType = mURLType; + uint32_t oldDefaultPort = mDefaultPort; + rv = Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, url, nullptr, nullptr); + + if (NS_FAILED(rv)) { + // Restore the old url type and default port if the call to Init fails. + mURLType = oldURLType; + mDefaultPort = oldDefaultPort; + return rv; + } + + // must clone |file| since its value is not guaranteed to remain constant + InvalidateCache(); + if (NS_FAILED(file->Clone(getter_AddRefs(mFile)))) { + NS_WARNING("nsIFile::Clone failed"); + // failure to clone is not fatal (GetFile will generate mFile) + mFile = nullptr; + } + + return NS_OK; +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsIStandardURL +//---------------------------------------------------------------------------- + +nsresult nsStandardURL::Init(uint32_t urlType, int32_t defaultPort, + const nsACString& spec, const char* charset, + nsIURI* baseURI) { + if (spec.Length() > StaticPrefs::network_standard_url_max_length() || + defaultPort > std::numeric_limits<uint16_t>::max()) { + return NS_ERROR_MALFORMED_URI; + } + + InvalidateCache(); + + switch (urlType) { + case URLTYPE_STANDARD: + mParser = net_GetStdURLParser(); + break; + case URLTYPE_AUTHORITY: + mParser = net_GetAuthURLParser(); + break; + case URLTYPE_NO_AUTHORITY: + mParser = net_GetNoAuthURLParser(); + break; + default: + MOZ_ASSERT_UNREACHABLE("bad urlType"); + return NS_ERROR_INVALID_ARG; + } + mDefaultPort = defaultPort; + mURLType = urlType; + + const auto* encoding = + charset ? Encoding::ForLabelNoReplacement(MakeStringSpan(charset)) + : nullptr; + // URI can't be encoded in UTF-16BE or UTF-16LE. Truncate encoding + // if it is one of utf encodings (since a null encoding implies + // UTF-8, this is safe even if encoding is UTF-8). + if (IsUTFEncoding(encoding)) { + encoding = nullptr; + } + + if (baseURI && net_IsAbsoluteURL(spec)) { + baseURI = nullptr; + } + + if (!baseURI) { + return SetSpecWithEncoding(spec, encoding); + } + + nsAutoCString buf; + nsresult rv = baseURI->Resolve(spec, buf); + if (NS_FAILED(rv)) { + return rv; + } + + return SetSpecWithEncoding(buf, encoding); +} + +nsresult nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort) { + InvalidateCache(); + + // should never be more than 16 bit + if (aNewDefaultPort >= std::numeric_limits<uint16_t>::max()) { + return NS_ERROR_MALFORMED_URI; + } + + // If we're already using the new default-port as a custom port, then clear + // it off of our mSpec & set mPort to -1, to indicate that we'll be using + // the default from now on (which happens to match what we already had). + if (mPort == aNewDefaultPort) { + ReplacePortInSpec(-1); + mPort = -1; + } + mDefaultPort = aNewDefaultPort; + + return NS_OK; +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsISerializable +//---------------------------------------------------------------------------- + +NS_IMETHODIMP +nsStandardURL::Read(nsIObjectInputStream* stream) { + MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsStandardURL::ReadPrivate(nsIObjectInputStream* stream) { + MOZ_ASSERT(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host"); + + // If we exit early, make sure to clear the URL so we don't fail the sanity + // check in the destructor + auto clearOnExit = MakeScopeExit([&] { Clear(); }); + + nsresult rv; + + uint32_t urlType; + rv = stream->Read32(&urlType); + if (NS_FAILED(rv)) { + return rv; + } + mURLType = urlType; + switch (mURLType) { + case URLTYPE_STANDARD: + mParser = net_GetStdURLParser(); + break; + case URLTYPE_AUTHORITY: + mParser = net_GetAuthURLParser(); + break; + case URLTYPE_NO_AUTHORITY: + mParser = net_GetNoAuthURLParser(); + break; + default: + MOZ_ASSERT_UNREACHABLE("bad urlType"); + return NS_ERROR_FAILURE; + } + + rv = stream->Read32((uint32_t*)&mPort); + if (NS_FAILED(rv)) { + return rv; + } + + rv = stream->Read32((uint32_t*)&mDefaultPort); + if (NS_FAILED(rv)) { + return rv; + } + + rv = NS_ReadOptionalCString(stream, mSpec); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mScheme); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mAuthority); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mUsername); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mPassword); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mHost); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mPath); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mFilepath); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mDirectory); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mBasename); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mExtension); + if (NS_FAILED(rv)) { + return rv; + } + + // handle forward compatibility from older serializations that included mParam + URLSegment old_param; + rv = ReadSegment(stream, old_param); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mQuery); + if (NS_FAILED(rv)) { + return rv; + } + + rv = ReadSegment(stream, mRef); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoCString oldOriginCharset; + rv = NS_ReadOptionalCString(stream, oldOriginCharset); + if (NS_FAILED(rv)) { + return rv; + } + + bool isMutable; + rv = stream->ReadBoolean(&isMutable); + if (NS_FAILED(rv)) { + return rv; + } + Unused << isMutable; + + bool supportsFileURL; + rv = stream->ReadBoolean(&supportsFileURL); + if (NS_FAILED(rv)) { + return rv; + } + mSupportsFileURL = supportsFileURL; + + // wait until object is set up, then modify path to include the param + if (old_param.mLen >= 0) { // note that mLen=0 is ";" + // If this wasn't empty, it marks characters between the end of the + // file and start of the query - mPath should include the param, + // query and ref already. Bump the mFilePath and + // directory/basename/extension components to include this. + mFilepath.Merge(mSpec, ';', old_param); + mDirectory.Merge(mSpec, ';', old_param); + mBasename.Merge(mSpec, ';', old_param); + mExtension.Merge(mSpec, ';', old_param); + } + + rv = CheckIfHostIsAscii(); + if (NS_FAILED(rv)) { + return rv; + } + + if (!IsValid()) { + return NS_ERROR_MALFORMED_URI; + } + + clearOnExit.release(); + + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::Write(nsIObjectOutputStream* stream) { + MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(), + "The spec should never be this long, we missed a check."); + nsresult rv; + + rv = stream->Write32(mURLType); + if (NS_FAILED(rv)) { + return rv; + } + + rv = stream->Write32(uint32_t(mPort)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = stream->Write32(uint32_t(mDefaultPort)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = NS_WriteOptionalStringZ(stream, mSpec.get()); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mScheme); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mAuthority); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mUsername); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mPassword); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mHost); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mPath); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mFilepath); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mDirectory); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mBasename); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mExtension); + if (NS_FAILED(rv)) { + return rv; + } + + // for backwards compatibility since we removed mParam. Note that this will + // mean that an older browser will read "" for mParam, and the param(s) will + // be part of mPath (as they after the removal of special handling). It only + // matters if you downgrade a browser to before the patch. + URLSegment empty; + rv = WriteSegment(stream, empty); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mQuery); + if (NS_FAILED(rv)) { + return rv; + } + + rv = WriteSegment(stream, mRef); + if (NS_FAILED(rv)) { + return rv; + } + + // former origin charset + rv = NS_WriteOptionalStringZ(stream, ""); + if (NS_FAILED(rv)) { + return rv; + } + + // former mMutable + rv = stream->WriteBoolean(false); + if (NS_FAILED(rv)) { + return rv; + } + + rv = stream->WriteBoolean(mSupportsFileURL); + if (NS_FAILED(rv)) { + return rv; + } + + // mDisplayHost is just a cache that can be recovered as needed. + + return NS_OK; +} + +inline ipc::StandardURLSegment ToIPCSegment( + const nsStandardURL::URLSegment& aSegment) { + return ipc::StandardURLSegment(aSegment.mPos, aSegment.mLen); +} + +[[nodiscard]] inline bool FromIPCSegment( + const nsACString& aSpec, const ipc::StandardURLSegment& aSegment, + nsStandardURL::URLSegment& aTarget) { + // This seems to be just an empty segment. + if (aSegment.length() == -1) { + aTarget = nsStandardURL::URLSegment(); + return true; + } + + // A value of -1 means an empty segment, but < -1 is undefined. + if (NS_WARN_IF(aSegment.length() < -1)) { + return false; + } + + CheckedInt<uint32_t> segmentLen = aSegment.position(); + segmentLen += aSegment.length(); + // Make sure the segment does not extend beyond the spec. + if (NS_WARN_IF(!segmentLen.isValid() || + segmentLen.value() > aSpec.Length())) { + return false; + } + + aTarget.mPos = aSegment.position(); + aTarget.mLen = aSegment.length(); + + return true; +} + +void nsStandardURL::Serialize(URIParams& aParams) { + MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(), + "The spec should never be this long, we missed a check."); + StandardURLParams params; + + params.urlType() = mURLType; + params.port() = mPort; + params.defaultPort() = mDefaultPort; + params.spec() = mSpec; + params.scheme() = ToIPCSegment(mScheme); + params.authority() = ToIPCSegment(mAuthority); + params.username() = ToIPCSegment(mUsername); + params.password() = ToIPCSegment(mPassword); + params.host() = ToIPCSegment(mHost); + params.path() = ToIPCSegment(mPath); + params.filePath() = ToIPCSegment(mFilepath); + params.directory() = ToIPCSegment(mDirectory); + params.baseName() = ToIPCSegment(mBasename); + params.extension() = ToIPCSegment(mExtension); + params.query() = ToIPCSegment(mQuery); + params.ref() = ToIPCSegment(mRef); + params.supportsFileURL() = !!mSupportsFileURL; + params.isSubstituting() = false; + // mDisplayHost is just a cache that can be recovered as needed. + + aParams = params; +} + +bool nsStandardURL::Deserialize(const URIParams& aParams) { + MOZ_ASSERT(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host"); + MOZ_ASSERT(!mFile, "Shouldn't have cached file"); + + if (aParams.type() != URIParams::TStandardURLParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + // If we exit early, make sure to clear the URL so we don't fail the sanity + // check in the destructor + auto clearOnExit = MakeScopeExit([&] { Clear(); }); + + const StandardURLParams& params = aParams.get_StandardURLParams(); + + mURLType = params.urlType(); + switch (mURLType) { + case URLTYPE_STANDARD: + mParser = net_GetStdURLParser(); + break; + case URLTYPE_AUTHORITY: + mParser = net_GetAuthURLParser(); + break; + case URLTYPE_NO_AUTHORITY: + mParser = net_GetNoAuthURLParser(); + break; + default: + MOZ_ASSERT_UNREACHABLE("bad urlType"); + return false; + } + + mPort = params.port(); + mDefaultPort = params.defaultPort(); + mSpec = params.spec(); + NS_ENSURE_TRUE( + mSpec.Length() <= StaticPrefs::network_standard_url_max_length(), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.scheme(), mScheme), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.authority(), mAuthority), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.username(), mUsername), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.password(), mPassword), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.host(), mHost), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.path(), mPath), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.filePath(), mFilepath), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.directory(), mDirectory), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.baseName(), mBasename), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.extension(), mExtension), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.query(), mQuery), false); + NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.ref(), mRef), false); + + mSupportsFileURL = params.supportsFileURL(); + + nsresult rv = CheckIfHostIsAscii(); + if (NS_FAILED(rv)) { + return false; + } + + // Some sanity checks + NS_ENSURE_TRUE(mScheme.mPos == 0, false); + NS_ENSURE_TRUE(mScheme.mLen > 0, false); + // Make sure scheme is followed by :// (3 characters) + NS_ENSURE_TRUE(mScheme.mLen < INT32_MAX - 3, false); // avoid overflow + NS_ENSURE_TRUE(mSpec.Length() >= (uint32_t)mScheme.mLen + 3, false); + NS_ENSURE_TRUE( + nsDependentCSubstring(mSpec, mScheme.mLen, 3).EqualsLiteral("://"), + false); + NS_ENSURE_TRUE(mPath.mLen != -1 && mSpec.CharAt(mPath.mPos) == '/', false); + NS_ENSURE_TRUE(mPath.mPos == mFilepath.mPos, false); + NS_ENSURE_TRUE(mQuery.mLen == -1 || + (mQuery.mPos > 0 && mSpec.CharAt(mQuery.mPos - 1) == '?'), + false); + NS_ENSURE_TRUE( + mRef.mLen == -1 || (mRef.mPos > 0 && mSpec.CharAt(mRef.mPos - 1) == '#'), + false); + + if (!IsValid()) { + return false; + } + + clearOnExit.release(); + + return true; +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsISizeOf +//---------------------------------------------------------------------------- + +size_t nsStandardURL::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mSpec.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mDisplayHost.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mParser + // - mFile +} + +size_t nsStandardURL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +} // namespace net +} // namespace mozilla + +// For unit tests. Including nsStandardURL.h seems to cause problems +nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result) { + return mozilla::net::nsStandardURL::NormalizeIPv4(host, result); +} + +// For unit tests. Including nsStandardURL.h seems to cause problems +nsresult Test_ParseIPv4Number(const nsACString& input, int32_t base, + uint32_t& number, uint32_t maxNumber) { + return mozilla::net::ParseIPv4Number(input, base, number, maxNumber); +} + +int32_t Test_ValidateIPv4Number(const nsACString& host, int32_t bases[4], + int32_t dotIndex[3], bool& onlyBase10, + int32_t length) { + return mozilla::net::ValidateIPv4Number(host, bases, dotIndex, onlyBase10, + length, false); +} diff --git a/netwerk/base/nsStandardURL.h b/netwerk/base/nsStandardURL.h new file mode 100644 index 0000000000..a4f644e722 --- /dev/null +++ b/netwerk/base/nsStandardURL.h @@ -0,0 +1,626 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsStandardURL_h__ +#define nsStandardURL_h__ + +#include <bitset> + +#include "nsString.h" +#include "nsISerializable.h" +#include "nsIFileURL.h" +#include "nsIStandardURL.h" +#include "mozilla/Encoding.h" +#include "nsCOMPtr.h" +#include "nsURLHelper.h" +#include "nsISizeOf.h" +#include "mozilla/Attributes.h" +#include "mozilla/LinkedList.h" +#include "mozilla/MemoryReporting.h" +#include "nsISensitiveInfoHiddenURI.h" +#include "nsIURIMutator.h" + +#ifdef NS_BUILD_REFCNT_LOGGING +# define DEBUG_DUMP_URLS_AT_SHUTDOWN +#endif + +class nsIBinaryInputStream; +class nsIBinaryOutputStream; +class nsIIDNService; +class nsIPrefBranch; +class nsIFile; +class nsIURLParser; + +namespace mozilla { +class Encoding; +namespace net { + +template <typename T> +class URLSegmentNumber { + T mData{0}; + bool mParity{false}; + + public: + URLSegmentNumber() = default; + explicit URLSegmentNumber(T data) : mData(data) { + mParity = CalculateParity(); + } + bool operator==(URLSegmentNumber value) const { return mData == value.mData; } + bool operator!=(URLSegmentNumber value) const { return mData != value.mData; } + bool operator>(URLSegmentNumber value) const { return mData > value.mData; } + URLSegmentNumber operator+(int32_t value) const { + return URLSegmentNumber(mData + value); + } + URLSegmentNumber operator+(uint32_t value) const { + return URLSegmentNumber(mData + value); + } + URLSegmentNumber operator-(int32_t value) const { + return URLSegmentNumber(mData - value); + } + URLSegmentNumber operator-(uint32_t value) const { + return URLSegmentNumber(mData - value); + } + URLSegmentNumber operator+=(URLSegmentNumber value) { + mData += value.mData; + mParity = CalculateParity(); + return *this; + } + URLSegmentNumber operator+=(T value) { + mData += value; + mParity = CalculateParity(); + return *this; + } + URLSegmentNumber operator-=(URLSegmentNumber value) { + mData -= value.mData; + mParity = CalculateParity(); + return *this; + } + URLSegmentNumber operator-=(T value) { + mData -= value; + mParity = CalculateParity(); + return *this; + } + operator T() const { return mData; } + URLSegmentNumber& operator=(T value) { + mData = value; + mParity = CalculateParity(); + return *this; + } + URLSegmentNumber& operator++() { + ++mData; + mParity = CalculateParity(); + return *this; + } + URLSegmentNumber operator++(int) { + URLSegmentNumber value = *this; + *this += 1; + return value; + } + bool CalculateParity() const { + std::bitset<32> bits((uint32_t)mData); + return bits.count() % 2 == 0 ? false : true; + } + bool Parity() const { return mParity; } +}; + +//----------------------------------------------------------------------------- +// standard URL implementation +//----------------------------------------------------------------------------- + +class nsStandardURL : public nsIFileURL, + public nsIStandardURL, + public nsISerializable, + public nsISizeOf, + public nsISensitiveInfoHiddenURI +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN + , + public LinkedListElement<nsStandardURL> +#endif +{ + protected: + virtual ~nsStandardURL(); + explicit nsStandardURL(bool aSupportsFileURL = false, bool aTrackURL = true); + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSIURL + NS_DECL_NSIFILEURL + NS_DECL_NSISTANDARDURL + NS_DECL_NSISERIALIZABLE + NS_DECL_NSISENSITIVEINFOHIDDENURI + + // nsISizeOf + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override; + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override; + + static void InitGlobalObjects(); + static void ShutdownGlobalObjects(); + + public: /* internal -- HPUX compiler can't handle this being private */ + // + // location and length of an url segment relative to mSpec + // + struct URLSegment { +#ifdef EARLY_BETA_OR_EARLIER + URLSegmentNumber<uint32_t> mPos{0}; + URLSegmentNumber<int32_t> mLen{-1}; +#else + uint32_t mPos{0}; + int32_t mLen{-1}; +#endif + + URLSegment() = default; + URLSegment(uint32_t pos, int32_t len) : mPos(pos), mLen(len) {} + URLSegment(const URLSegment& aCopy) = default; + void Reset() { + mPos = 0; + mLen = -1; + } + // Merge another segment following this one to it if they're contiguous + // Assumes we have something like "foo;bar" where this object is 'foo' and + // right is 'bar'. + void Merge(const nsCString& spec, const char separator, + const URLSegment& right) { + if (mLen >= 0 && *(spec.get() + mPos + mLen) == separator && + mPos + mLen + 1 == right.mPos) { + mLen += 1 + right.mLen; + } + } + }; + + // + // URL segment encoder : performs charset conversion and URL escaping. + // + class nsSegmentEncoder { + public: + explicit nsSegmentEncoder(const Encoding* encoding = nullptr); + + // Encode the given segment if necessary, and return the length of + // the encoded segment. The encoded segment is appended to |aOut| + // if and only if encoding is required. + int32_t EncodeSegmentCount(const char* str, const URLSegment& aSeg, + int16_t mask, nsCString& aOut, bool& appended, + uint32_t extraLen = 0); + + // Encode the given string if necessary, and return a reference to + // the encoded string. Returns a reference to |result| if encoding + // is required. Otherwise, a reference to |str| is returned. + const nsACString& EncodeSegment(const nsACString& str, int16_t mask, + nsCString& result); + + private: + const Encoding* mEncoding; + }; + friend class nsSegmentEncoder; + + static nsresult NormalizeIPv4(const nsACString& host, nsCString& result); + + protected: + // enum used in a few places to specify how .ref attribute should be handled + enum RefHandlingEnum { eIgnoreRef, eHonorRef, eReplaceRef }; + + // Helper to share code between Equals and EqualsExceptRef + // NOTE: *not* virtual, because no one needs to override this so far... + nsresult EqualsInternal(nsIURI* unknownOther, RefHandlingEnum refHandlingMode, + bool* result); + + virtual nsStandardURL* StartClone(); + + // Helper to share code between Clone methods. + nsresult CloneInternal(RefHandlingEnum aRefHandlingMode, + const nsACString& aNewRef, nsIURI** aClone); + // Helper method that copies member variables from the source StandardURL + // if copyCached = true, it will also copy mFile and mDisplayHost + nsresult CopyMembers(nsStandardURL* source, RefHandlingEnum mode, + const nsACString& newRef, bool copyCached = false); + + // Helper for subclass implementation of GetFile(). Subclasses that map + // URIs to files in a special way should implement this method. It should + // ensure that our mFile is initialized, if it's possible. + // returns NS_ERROR_NO_INTERFACE if the url does not map to a file + virtual nsresult EnsureFile(); + + virtual nsresult Clone(nsIURI** aURI); + virtual nsresult SetSpecInternal(const nsACString& input); + virtual nsresult SetScheme(const nsACString& input); + virtual nsresult SetUserPass(const nsACString& input); + virtual nsresult SetUsername(const nsACString& input); + virtual nsresult SetPassword(const nsACString& input); + virtual nsresult SetHostPort(const nsACString& aValue); + virtual nsresult SetHost(const nsACString& input); + virtual nsresult SetPort(int32_t port); + virtual nsresult SetPathQueryRef(const nsACString& input); + virtual nsresult SetRef(const nsACString& input); + virtual nsresult SetFilePath(const nsACString& input); + virtual nsresult SetQuery(const nsACString& input); + virtual nsresult SetQueryWithEncoding(const nsACString& input, + const Encoding* encoding); + bool Deserialize(const mozilla::ipc::URIParams&); + nsresult ReadPrivate(nsIObjectInputStream* stream); + + private: + nsresult Init(uint32_t urlType, int32_t defaultPort, const nsACString& spec, + const char* charset, nsIURI* baseURI); + nsresult SetDefaultPort(int32_t aNewDefaultPort); + nsresult SetFile(nsIFile* file); + + nsresult SetFileNameInternal(const nsACString& input); + nsresult SetFileBaseNameInternal(const nsACString& input); + nsresult SetFileExtensionInternal(const nsACString& input); + + int32_t Port() { return mPort == -1 ? mDefaultPort : mPort; } + + void ReplacePortInSpec(int32_t aNewPort); + void Clear(); + void InvalidateCache(bool invalidateCachedFile = true); + + bool ValidIPv6orHostname(const char* host, uint32_t length); + static bool IsValidOfBase(unsigned char c, const uint32_t base); + nsresult NormalizeIDN(const nsCString& host, nsCString& result); + nsresult CheckIfHostIsAscii(); + void CoalescePath(netCoalesceFlags coalesceFlag, char* path); + + uint32_t AppendSegmentToBuf(char*, uint32_t, const char*, + const URLSegment& input, URLSegment& output, + const nsCString* esc = nullptr, + bool useEsc = false, int32_t* diff = nullptr); + uint32_t AppendToBuf(char*, uint32_t, const char*, uint32_t); + + nsresult BuildNormalizedSpec(const char* spec, const Encoding* encoding); + nsresult SetSpecWithEncoding(const nsACString& input, + const Encoding* encoding); + + bool SegmentIs(const URLSegment& seg, const char* val, + bool ignoreCase = false); + bool SegmentIs(const char* spec, const URLSegment& seg, const char* val, + bool ignoreCase = false); + bool SegmentIs(const URLSegment& seg1, const char* val, + const URLSegment& seg2, bool ignoreCase = false); + + int32_t ReplaceSegment(uint32_t pos, uint32_t len, const char* val, + uint32_t valLen); + int32_t ReplaceSegment(uint32_t pos, uint32_t len, const nsACString& val); + + nsresult ParseURL(const char* spec, int32_t specLen); + nsresult ParsePath(const char* spec, uint32_t pathPos, int32_t pathLen = -1); + + char* AppendToSubstring(uint32_t pos, int32_t len, const char* tail); + + // dependent substring helpers + nsDependentCSubstring Segment(uint32_t pos, int32_t len); // see below + nsDependentCSubstring Segment(const URLSegment& s) { + return Segment(s.mPos, s.mLen); + } + + // dependent substring getters + nsDependentCSubstring Prepath(); // see below + nsDependentCSubstring Scheme() { return Segment(mScheme); } + nsDependentCSubstring Userpass(bool includeDelim = false); // see below + nsDependentCSubstring Username() { return Segment(mUsername); } + nsDependentCSubstring Password() { return Segment(mPassword); } + nsDependentCSubstring Hostport(); // see below + nsDependentCSubstring Host(); // see below + nsDependentCSubstring Path() { return Segment(mPath); } + nsDependentCSubstring Filepath() { return Segment(mFilepath); } + nsDependentCSubstring Directory() { return Segment(mDirectory); } + nsDependentCSubstring Filename(); // see below + nsDependentCSubstring Basename() { return Segment(mBasename); } + nsDependentCSubstring Extension() { return Segment(mExtension); } + nsDependentCSubstring Query() { return Segment(mQuery); } + nsDependentCSubstring Ref() { return Segment(mRef); } + + // shift the URLSegments to the right by diff + void ShiftFromAuthority(int32_t diff); + void ShiftFromUsername(int32_t diff); + void ShiftFromPassword(int32_t diff); + void ShiftFromHost(int32_t diff); + void ShiftFromPath(int32_t diff); + void ShiftFromFilepath(int32_t diff); + void ShiftFromDirectory(int32_t diff); + void ShiftFromBasename(int32_t diff); + void ShiftFromExtension(int32_t diff); + void ShiftFromQuery(int32_t diff); + void ShiftFromRef(int32_t diff); + + // fastload helper functions + nsresult ReadSegment(nsIBinaryInputStream*, URLSegment&); + nsresult WriteSegment(nsIBinaryOutputStream*, const URLSegment&); + + void FindHostLimit(nsACString::const_iterator& aStart, + nsACString::const_iterator& aEnd); + + // Asserts that the URL has sane values + void SanityCheck(); + + // Checks if the URL has a valid representation. + bool IsValid(); + + // mSpec contains the normalized version of the URL spec (UTF-8 encoded). + nsCString mSpec; + int32_t mDefaultPort{-1}; + int32_t mPort{-1}; + + // url parts (relative to mSpec) + URLSegment mScheme; + URLSegment mAuthority; + URLSegment mUsername; + URLSegment mPassword; + URLSegment mHost; + URLSegment mPath; + URLSegment mFilepath; + URLSegment mDirectory; + URLSegment mBasename; + URLSegment mExtension; + URLSegment mQuery; + URLSegment mRef; + + nsCOMPtr<nsIURLParser> mParser; + + // mFile is protected so subclasses can access it directly + protected: + nsCOMPtr<nsIFile> mFile; // cached result for nsIFileURL::GetFile + + private: + // cached result for nsIURI::GetDisplayHost + nsCString mDisplayHost; + + enum { eEncoding_Unknown, eEncoding_ASCII, eEncoding_UTF8 }; + + uint32_t mURLType : 2; // nsIStandardURL::URLTYPE_xxx + uint32_t mSupportsFileURL : 1; // QI to nsIFileURL? + uint32_t mCheckedIfHostA : 1; // If set to true, it means either that + // mDisplayHost has a been initialized, or + // that the hostname is not punycode + + // global objects. + static StaticRefPtr<nsIIDNService> gIDN; + static const char gHostLimitDigits[]; + + public: +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN + void PrintSpec() const { printf(" %s\n", mSpec.get()); } +#endif + + public: + // We make this implementation a template so that we can avoid writing + // the same code for SubstitutingURL (which extends nsStandardURL) + template <class T> + class TemplatedMutator : public nsIURIMutator, + public BaseURIMutator<T>, + public nsIStandardURLMutator, + public nsIURLMutator, + public nsIFileURLMutator, + public nsISerializable { + NS_FORWARD_SAFE_NSIURISETTERS_RET(BaseURIMutator<T>::mURI) + + [[nodiscard]] NS_IMETHOD Deserialize( + const mozilla::ipc::URIParams& aParams) override { + return BaseURIMutator<T>::InitFromIPCParams(aParams); + } + + NS_IMETHOD + Write(nsIObjectOutputStream* aOutputStream) override { + MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override { + return BaseURIMutator<T>::InitFromInputStream(aStream); + } + + [[nodiscard]] NS_IMETHOD Finalize(nsIURI** aURI) override { + BaseURIMutator<T>::mURI.forget(aURI); + return NS_OK; + } + + [[nodiscard]] NS_IMETHOD SetSpec(const nsACString& aSpec, + nsIURIMutator** aMutator) override { + if (aMutator) { + nsCOMPtr<nsIURIMutator> mutator = this; + mutator.forget(aMutator); + } + return BaseURIMutator<T>::InitFromSpec(aSpec); + } + + [[nodiscard]] NS_IMETHOD Init(uint32_t aURLType, int32_t aDefaultPort, + const nsACString& aSpec, const char* aCharset, + nsIURI* aBaseURI, + nsIURIMutator** aMutator) override { + if (aMutator) { + nsCOMPtr<nsIURIMutator> mutator = this; + mutator.forget(aMutator); + } + RefPtr<T> uri; + if (BaseURIMutator<T>::mURI) { + // We don't need a new URI object if we already have one + BaseURIMutator<T>::mURI.swap(uri); + } else { + uri = Create(); + } + nsresult rv = + uri->Init(aURLType, aDefaultPort, aSpec, aCharset, aBaseURI); + if (NS_FAILED(rv)) { + return rv; + } + BaseURIMutator<T>::mURI = std::move(uri); + return NS_OK; + } + + [[nodiscard]] NS_IMETHODIMP SetDefaultPort( + int32_t aNewDefaultPort, nsIURIMutator** aMutator) override { + if (!BaseURIMutator<T>::mURI) { + return NS_ERROR_NULL_POINTER; + } + if (aMutator) { + nsCOMPtr<nsIURIMutator> mutator = this; + mutator.forget(aMutator); + } + return BaseURIMutator<T>::mURI->SetDefaultPort(aNewDefaultPort); + } + + [[nodiscard]] NS_IMETHOD SetFileName(const nsACString& aFileName, + nsIURIMutator** aMutator) override { + if (!BaseURIMutator<T>::mURI) { + return NS_ERROR_NULL_POINTER; + } + if (aMutator) { + nsCOMPtr<nsIURIMutator> mutator = this; + mutator.forget(aMutator); + } + return BaseURIMutator<T>::mURI->SetFileNameInternal(aFileName); + } + + [[nodiscard]] NS_IMETHOD SetFileBaseName( + const nsACString& aFileBaseName, nsIURIMutator** aMutator) override { + if (!BaseURIMutator<T>::mURI) { + return NS_ERROR_NULL_POINTER; + } + if (aMutator) { + nsCOMPtr<nsIURIMutator> mutator = this; + mutator.forget(aMutator); + } + return BaseURIMutator<T>::mURI->SetFileBaseNameInternal(aFileBaseName); + } + + [[nodiscard]] NS_IMETHOD SetFileExtension( + const nsACString& aFileExtension, nsIURIMutator** aMutator) override { + if (!BaseURIMutator<T>::mURI) { + return NS_ERROR_NULL_POINTER; + } + if (aMutator) { + nsCOMPtr<nsIURIMutator> mutator = this; + mutator.forget(aMutator); + } + return BaseURIMutator<T>::mURI->SetFileExtensionInternal(aFileExtension); + } + + T* Create() override { return new T(mMarkedFileURL); } + + [[nodiscard]] NS_IMETHOD MarkFileURL() override { + mMarkedFileURL = true; + return NS_OK; + } + + [[nodiscard]] NS_IMETHOD SetFile(nsIFile* aFile) override { + RefPtr<T> uri; + if (BaseURIMutator<T>::mURI) { + // We don't need a new URI object if we already have one + BaseURIMutator<T>::mURI.swap(uri); + } else { + uri = new T(/* aSupportsFileURL = */ true); + } + + nsresult rv = uri->SetFile(aFile); + if (NS_FAILED(rv)) { + return rv; + } + BaseURIMutator<T>::mURI.swap(uri); + return NS_OK; + } + + explicit TemplatedMutator() = default; + + private: + virtual ~TemplatedMutator() = default; + + bool mMarkedFileURL = false; + + friend T; + }; + + class Mutator final : public TemplatedMutator<nsStandardURL> { + NS_DECL_ISUPPORTS + public: + explicit Mutator() = default; + + private: + virtual ~Mutator() = default; + }; + + friend BaseURIMutator<nsStandardURL>; +}; + +#define NS_THIS_STANDARDURL_IMPL_CID \ + { /* b8e3e97b-1ccd-4b45-af5a-79596770f5d7 */ \ + 0xb8e3e97b, 0x1ccd, 0x4b45, { \ + 0xaf, 0x5a, 0x79, 0x59, 0x67, 0x70, 0xf5, 0xd7 \ + } \ + } + +//----------------------------------------------------------------------------- +// Dependent substring getters +//----------------------------------------------------------------------------- + +inline nsDependentCSubstring nsStandardURL::Segment(uint32_t pos, int32_t len) { + if (len < 0) { + pos = 0; + len = 0; + } + return Substring(mSpec, pos, uint32_t(len)); +} + +inline nsDependentCSubstring nsStandardURL::Prepath() { + uint32_t len = 0; + if (mAuthority.mLen >= 0) len = mAuthority.mPos + mAuthority.mLen; + return Substring(mSpec, 0, len); +} + +inline nsDependentCSubstring nsStandardURL::Userpass(bool includeDelim) { + uint32_t pos = 0, len = 0; + if (mUsername.mLen > 0 || mPassword.mLen > 0) { + if (mUsername.mLen > 0) { + pos = mUsername.mPos; + len = mUsername.mLen; + if (mPassword.mLen >= 0) { + len += (mPassword.mLen + 1); + } + } else { + pos = mPassword.mPos - 1; + len = mPassword.mLen + 1; + } + + if (includeDelim) len++; + } + return Substring(mSpec, pos, len); +} + +inline nsDependentCSubstring nsStandardURL::Hostport() { + uint32_t pos = 0, len = 0; + if (mAuthority.mLen > 0) { + pos = mHost.mPos; + len = mAuthority.mPos + mAuthority.mLen - pos; + } + return Substring(mSpec, pos, len); +} + +inline nsDependentCSubstring nsStandardURL::Host() { + uint32_t pos = 0, len = 0; + if (mHost.mLen > 0) { + pos = mHost.mPos; + len = mHost.mLen; + if (mSpec.CharAt(pos) == '[' && mSpec.CharAt(pos + len - 1) == ']') { + pos++; + len -= 2; + } + } + return Substring(mSpec, pos, len); +} + +inline nsDependentCSubstring nsStandardURL::Filename() { + uint32_t pos = 0, len = 0; + // if there is no basename, then there can be no extension + if (mBasename.mLen > 0) { + pos = mBasename.mPos; + len = mBasename.mLen; + if (mExtension.mLen >= 0) len += (mExtension.mLen + 1); + } + return Substring(mSpec, pos, len); +} + +} // namespace net +} // namespace mozilla + +#endif // nsStandardURL_h__ diff --git a/netwerk/base/nsStreamListenerTee.cpp b/netwerk/base/nsStreamListenerTee.cpp new file mode 100644 index 0000000000..4de5db56f5 --- /dev/null +++ b/netwerk/base/nsStreamListenerTee.cpp @@ -0,0 +1,185 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsStreamListenerTee.h" +#include "nsProxyRelease.h" +#include "nsIRequest.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(nsStreamListenerTee, nsIStreamListener, nsIRequestObserver, + nsIStreamListenerTee, nsIThreadRetargetableStreamListener, + nsIMultiPartChannelListener) + +NS_IMETHODIMP +nsStreamListenerTee::OnStartRequest(nsIRequest* request) { + NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(request); + if (multiPartChannel) { + mIsMultiPart = true; + } + + nsresult rv1 = mListener->OnStartRequest(request); + nsresult rv2 = NS_OK; + if (mObserver) rv2 = mObserver->OnStartRequest(request); + + // Preserve NS_SUCCESS_XXX in rv1 in case mObserver didn't throw + return (NS_FAILED(rv2) && NS_SUCCEEDED(rv1)) ? rv2 : rv1; +} + +NS_IMETHODIMP +nsStreamListenerTee::OnStopRequest(nsIRequest* request, nsresult status) { + NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED); + // it is critical that we close out the input stream tee + if (mInputTee) { + mInputTee->SetSink(nullptr); + mInputTee = nullptr; + } + + if (!mIsMultiPart) { + // release sink on the same thread where the data was written (bug 716293) + if (mEventTarget) { + NS_ProxyRelease("nsStreamListenerTee::mSink", mEventTarget, + mSink.forget()); + } else { + mSink = nullptr; + } + } + + nsresult rv = mListener->OnStopRequest(request, status); + if (!mIsMultiPart) { + mListener = nullptr; + } + if (mObserver) { + mObserver->OnStopRequest(request, status); + if (!mIsMultiPart) { + mObserver = nullptr; + } + } + return rv; +} + +NS_IMETHODIMP +nsStreamListenerTee::OnDataAvailable(nsIRequest* request, nsIInputStream* input, + uint64_t offset, uint32_t count) { + NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mSink, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsIInputStream> tee; + nsresult rv; + + if (!mInputTee) { + if (mEventTarget) { + rv = NS_NewInputStreamTeeAsync(getter_AddRefs(tee), input, mSink, + mEventTarget); + } else { + rv = NS_NewInputStreamTee(getter_AddRefs(tee), input, mSink); + } + if (NS_FAILED(rv)) return rv; + + mInputTee = do_QueryInterface(tee, &rv); + if (NS_FAILED(rv)) return rv; + } else { + // re-initialize the input tee since the input stream may have changed. + rv = mInputTee->SetSource(input); + if (NS_FAILED(rv)) return rv; + + tee = mInputTee; + } + + return mListener->OnDataAvailable(request, tee, offset, count); +} + +NS_IMETHODIMP +nsStreamListenerTee::OnAfterLastPart(nsresult aStatus) { + // release sink on the same thread where the data was written (bug 716293) + if (mEventTarget) { + NS_ProxyRelease("nsStreamListenerTee::mSink", mEventTarget, mSink.forget()); + } else { + mSink = nullptr; + } + + if (nsCOMPtr<nsIMultiPartChannelListener> multi = + do_QueryInterface(mListener)) { + multi->OnAfterLastPart(aStatus); + } + if (!SameCOMIdentity(mListener, mObserver)) { + if (nsCOMPtr<nsIMultiPartChannelListener> multi = + do_QueryInterface(mObserver)) { + multi->OnAfterLastPart(aStatus); + } + } + + mObserver = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamListenerTee::CheckListenerChain() { + NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!"); + nsresult rv = NS_OK; + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(mListener, &rv); + if (retargetableListener) { + rv = retargetableListener->CheckListenerChain(); + } + if (NS_FAILED(rv)) { + return rv; + } + if (!mObserver) { + return rv; + } + retargetableListener = do_QueryInterface(mObserver, &rv); + if (retargetableListener) { + rv = retargetableListener->CheckListenerChain(); + } + return rv; +} + +NS_IMETHODIMP +nsStreamListenerTee::OnDataFinished(nsresult aStatus) { + nsresult rv = NS_OK; + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(mListener, &rv); + if (retargetableListener) { + rv = retargetableListener->OnDataFinished(aStatus); + } + if (NS_FAILED(rv)) { + return rv; + } + if (!mObserver) { + return rv; + } + retargetableListener = do_QueryInterface(mObserver, &rv); + if (retargetableListener) { + rv = retargetableListener->OnDataFinished(aStatus); + } + return rv; +} + +NS_IMETHODIMP +nsStreamListenerTee::Init(nsIStreamListener* listener, nsIOutputStream* sink, + nsIRequestObserver* requestObserver) { + NS_ENSURE_ARG_POINTER(listener); + NS_ENSURE_ARG_POINTER(sink); + mListener = listener; + mSink = sink; + mObserver = requestObserver; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamListenerTee::InitAsync(nsIStreamListener* listener, + nsIEventTarget* eventTarget, + nsIOutputStream* sink, + nsIRequestObserver* requestObserver) { + NS_ENSURE_ARG_POINTER(eventTarget); + mEventTarget = eventTarget; + return Init(listener, sink, requestObserver); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsStreamListenerTee.h b/netwerk/base/nsStreamListenerTee.h new file mode 100644 index 0000000000..87328e02cd --- /dev/null +++ b/netwerk/base/nsStreamListenerTee.h @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsStreamListenerTee_h__ +#define nsStreamListenerTee_h__ + +#include "nsIStreamListenerTee.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsIInputStreamTee.h" +#include "nsIOutputStream.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsIMultiPartChannel.h" + +namespace mozilla { +namespace net { + +class nsStreamListenerTee : public nsIStreamListenerTee, + public nsIMultiPartChannelListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + NS_DECL_NSISTREAMLISTENERTEE + NS_DECL_NSIMULTIPARTCHANNELLISTENER + + nsStreamListenerTee() = default; + + private: + virtual ~nsStreamListenerTee() = default; + + nsCOMPtr<nsIInputStreamTee> mInputTee; + nsCOMPtr<nsIOutputStream> mSink; + nsCOMPtr<nsIStreamListener> mListener; + nsCOMPtr<nsIRequestObserver> mObserver; + nsCOMPtr<nsIEventTarget> mEventTarget; + bool mIsMultiPart = false; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/nsStreamListenerWrapper.cpp b/netwerk/base/nsStreamListenerWrapper.cpp new file mode 100644 index 0000000000..3502f3ada3 --- /dev/null +++ b/netwerk/base/nsStreamListenerWrapper.cpp @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsStreamListenerWrapper.h" +#ifdef DEBUG +# include "MainThreadUtils.h" +#endif + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(nsStreamListenerWrapper, nsIStreamListener, + nsIRequestObserver, nsIMultiPartChannelListener, + nsIThreadRetargetableStreamListener) + +NS_IMETHODIMP +nsStreamListenerWrapper::OnAfterLastPart(nsresult aStatus) { + if (nsCOMPtr<nsIMultiPartChannelListener> listener = + do_QueryInterface(mListener)) { + nsresult rv = NS_OK; + if (nsCOMPtr<nsIMultiPartChannelListener> listener = + do_QueryInterface(mListener)) { + rv = listener->OnAfterLastPart(aStatus); + } + mListener = nullptr; + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +nsStreamListenerWrapper::CheckListenerChain() { + NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!"); + nsresult rv = NS_OK; + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(mListener, &rv); + if (retargetableListener) { + rv = retargetableListener->CheckListenerChain(); + } + return rv; +} + +NS_IMETHODIMP +nsStreamListenerWrapper::OnDataFinished(nsresult aStatus) { + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(mListener); + if (retargetableListener) { + return retargetableListener->OnDataFinished(aStatus); + } + + return NS_OK; +} +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsStreamListenerWrapper.h b/netwerk/base/nsStreamListenerWrapper.h new file mode 100644 index 0000000000..a950837297 --- /dev/null +++ b/netwerk/base/nsStreamListenerWrapper.h @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsStreamListenerWrapper_h__ +#define nsStreamListenerWrapper_h__ + +#include "nsCOMPtr.h" +#include "nsIRequest.h" +#include "nsIStreamListener.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsIMultiPartChannel.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace net { + +// Wrapper class to make replacement of nsHttpChannel's listener +// from JavaScript possible. It is workaround for bug 433711 and 682305. +class nsStreamListenerWrapper final + : public nsIMultiPartChannelListener, + public nsIThreadRetargetableStreamListener { + public: + explicit nsStreamListenerWrapper(nsIStreamListener* listener) + : mListener(listener) { + MOZ_ASSERT(mListener, "no stream listener specified"); + } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_SAFE_NSISTREAMLISTENER(mListener) + NS_DECL_NSIMULTIPARTCHANNELLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + + // Don't use NS_FORWARD_NSIREQUESTOBSERVER(mListener->) here, because we need + // to release mListener in OnStopRequest, and IDL-generated function doesn't. + NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override { + // OnStartRequest can come after OnStopRequest in certain cases (multipart + // listeners) + nsCOMPtr<nsIMultiPartChannel> multiPartChannel = + do_QueryInterface(aRequest); + if (multiPartChannel) { + mIsMulti = true; + } + return mListener->OnStartRequest(aRequest); + } + NS_IMETHOD OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) override { + nsresult rv = mListener->OnStopRequest(aRequest, aStatusCode); + if (!mIsMulti) { + // Multipart channels can call OnStartRequest again + mListener = nullptr; + } + return rv; + } + + private: + bool mIsMulti{false}; + ~nsStreamListenerWrapper() = default; + nsCOMPtr<nsIStreamListener> mListener; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsStreamListenerWrapper_h__ diff --git a/netwerk/base/nsStreamLoader.cpp b/netwerk/base/nsStreamLoader.cpp new file mode 100644 index 0000000000..f73b260de3 --- /dev/null +++ b/netwerk/base/nsStreamLoader.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsStreamLoader.h" +#include "nsIInputStream.h" +#include "nsIChannel.h" +#include "nsError.h" +#include "mozilla/ProfilerLabels.h" + +#include <limits> + +namespace mozilla { +namespace net { + +nsStreamLoader::nsStreamLoader() = default; + +NS_IMETHODIMP +nsStreamLoader::Init(nsIStreamLoaderObserver* aStreamObserver, + nsIRequestObserver* aRequestObserver) { + NS_ENSURE_ARG_POINTER(aStreamObserver); + mObserver = aStreamObserver; + mRequestObserver = aRequestObserver; + return NS_OK; +} + +nsresult nsStreamLoader::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsStreamLoader> it = new nsStreamLoader(); + return it->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsStreamLoader, nsIStreamLoader, nsIRequestObserver, + nsIStreamListener, nsIThreadRetargetableStreamListener) + +NS_IMETHODIMP +nsStreamLoader::GetNumBytesRead(uint32_t* aNumBytes) { + *aNumBytes = mBytesRead; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamLoader::GetRequest(nsIRequest** aRequest) { + nsCOMPtr<nsIRequest> req = mRequest; + req.forget(aRequest); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamLoader::OnStartRequest(nsIRequest* request) { + nsCOMPtr<nsIChannel> chan(do_QueryInterface(request)); + if (chan) { + int64_t contentLength = -1; + chan->GetContentLength(&contentLength); + if (contentLength >= 0) { + // On 64bit platforms size of uint64_t coincides with the size of size_t, + // so we want to compare with the minimum from size_t and int64_t. + if (static_cast<uint64_t>(contentLength) > + std::min(std::numeric_limits<size_t>::max(), + static_cast<size_t>(std::numeric_limits<int64_t>::max()))) { + // Too big to fit into size_t, so let's bail. + return NS_ERROR_OUT_OF_MEMORY; + } + // preallocate buffer + if (!mData.initCapacity(contentLength)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + if (mRequestObserver) { + mRequestObserver->OnStartRequest(request); + } + return NS_OK; +} + +NS_IMETHODIMP +nsStreamLoader::OnStopRequest(nsIRequest* request, nsresult aStatus) { + AUTO_PROFILER_LABEL("nsStreamLoader::OnStopRequest", NETWORK); + + if (mObserver) { + // provide nsIStreamLoader::request during call to OnStreamComplete + mRequest = request; + size_t length = mData.length(); + uint8_t* elems = mData.extractOrCopyRawBuffer(); + nsresult rv = + mObserver->OnStreamComplete(this, mContext, aStatus, length, elems); + if (rv != NS_SUCCESS_ADOPTED_DATA) { + // The observer didn't take ownership of the extracted data buffer, so + // put it back into mData. + mData.replaceRawBuffer(elems, length); + } + // done.. cleanup + ReleaseData(); + mRequest = nullptr; + mObserver = nullptr; + } + + if (mRequestObserver) { + mRequestObserver->OnStopRequest(request, aStatus); + mRequestObserver = nullptr; + } + + return NS_OK; +} + +nsresult nsStreamLoader::WriteSegmentFun(nsIInputStream* inStr, void* closure, + const char* fromSegment, + uint32_t toOffset, uint32_t count, + uint32_t* writeCount) { + nsStreamLoader* self = (nsStreamLoader*)closure; + + if (!self->mData.append(fromSegment, count)) { + self->mData.clearAndFree(); + return NS_ERROR_OUT_OF_MEMORY; + } + + *writeCount = count; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamLoader::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr, + uint64_t sourceOffset, uint32_t count) { + uint32_t countRead; + nsresult rv = inStr->ReadSegments(WriteSegmentFun, this, count, &countRead); + NS_ENSURE_SUCCESS(rv, rv); + mBytesRead += countRead; + return NS_OK; +} + +void nsStreamLoader::ReleaseData() { mData.clearAndFree(); } + +NS_IMETHODIMP +nsStreamLoader::CheckListenerChain() { return NS_OK; } + +NS_IMETHODIMP +nsStreamLoader::OnDataFinished(nsresult) { return NS_OK; } + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsStreamLoader.h b/netwerk/base/nsStreamLoader.h new file mode 100644 index 0000000000..1960c41db3 --- /dev/null +++ b/netwerk/base/nsStreamLoader.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsStreamLoader_h__ +#define nsStreamLoader_h__ + +#include "nsIThreadRetargetableStreamListener.h" +#include "nsIStreamLoader.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/Vector.h" + +class nsIRequest; + +namespace mozilla { +namespace net { + +class nsStreamLoader final : public nsIStreamLoader { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTREAMLOADER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + + nsStreamLoader(); + + static nsresult Create(REFNSIID aIID, void** aResult); + + protected: + ~nsStreamLoader() = default; + + static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*, uint32_t, + uint32_t, uint32_t*); + + // Utility method to free mData, if present, and update other state to + // reflect that no data has been allocated. + void ReleaseData(); + + nsCOMPtr<nsIStreamLoaderObserver> mObserver; + nsCOMPtr<nsISupports> mContext; // the observer's context + nsCOMPtr<nsIRequest> mRequest; + nsCOMPtr<nsIRequestObserver> mRequestObserver; + + mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed> mBytesRead; + + // Buffer to accumulate incoming data. We preallocate if contentSize is + // available. + mozilla::Vector<uint8_t, 0> mData; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsStreamLoader_h__ diff --git a/netwerk/base/nsStreamTransportService.cpp b/netwerk/base/nsStreamTransportService.cpp new file mode 100644 index 0000000000..e1369bbcb5 --- /dev/null +++ b/netwerk/base/nsStreamTransportService.cpp @@ -0,0 +1,429 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsStreamTransportService.h" +#include "ErrorList.h" +#include "nsXPCOMCIDInternal.h" +#include "nsNetSegmentUtils.h" +#include "nsTransportUtils.h" +#include "nsStreamUtils.h" +#include "nsError.h" +#include "nsNetCID.h" + +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIPipe.h" +#include "nsITransport.h" +#include "nsIObserverService.h" +#include "nsThreadPool.h" +#include "mozilla/Services.h" + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- +// nsInputStreamTransport +// +// Implements nsIInputStream as a wrapper around the real input stream. This +// allows the transport to support seeking, range-limiting, progress reporting, +// and close-when-done semantics while utilizing NS_AsyncCopy. +//----------------------------------------------------------------------------- + +class nsInputStreamTransport : public nsITransport, + public nsIAsyncInputStream, + public nsIInputStreamCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORT + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + + nsInputStreamTransport(nsIInputStream* source, bool closeWhenDone) + : mSource(source), mCloseWhenDone(closeWhenDone) { + mAsyncSource = do_QueryInterface(mSource); + } + + private: + virtual ~nsInputStreamTransport() = default; + + Mutex mMutex MOZ_UNANNOTATED{"nsInputStreamTransport::mMutex"}; + + // This value is protected by mutex. + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback; + + nsCOMPtr<nsIAsyncInputStream> mPipeIn; + + // while the copy is active, these members may only be accessed from the + // nsIInputStream implementation. + nsCOMPtr<nsITransportEventSink> mEventSink; + nsCOMPtr<nsIInputStream> mSource; + + // It can be null. + nsCOMPtr<nsIAsyncInputStream> mAsyncSource; + + int64_t mOffset{0}; + const bool mCloseWhenDone; + + // this variable serves as a lock to prevent the state of the transport + // from being modified once the copy is in progress. + bool mInProgress{false}; +}; + +NS_IMPL_ADDREF(nsInputStreamTransport); +NS_IMPL_RELEASE(nsInputStreamTransport); + +NS_INTERFACE_MAP_BEGIN(nsInputStreamTransport) + NS_INTERFACE_MAP_ENTRY(nsITransport) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, !!mAsyncSource) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, !!mAsyncSource) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransport) +NS_INTERFACE_MAP_END + +/** nsITransport **/ + +NS_IMETHODIMP +nsInputStreamTransport::OpenInputStream(uint32_t flags, uint32_t segsize, + uint32_t segcount, + nsIInputStream** result) { + NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS); + + nsresult rv; + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + // XXX if the caller requests an unbuffered stream, then perhaps + // we'd want to simply return mSource; however, then we would + // not be reading mSource on a background thread. is this ok? + + bool nonblocking = !(flags & OPEN_BLOCKING); + + net_ResolveSegmentParams(segsize, segcount); + + nsCOMPtr<nsIAsyncOutputStream> pipeOut; + NS_NewPipe2(getter_AddRefs(mPipeIn), getter_AddRefs(pipeOut), nonblocking, + true, segsize, segcount); + + mInProgress = true; + + // startup async copy process... + rv = NS_AsyncCopy(this, pipeOut, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS, + segsize); + if (NS_FAILED(rv)) { + return rv; + } + *result = do_AddRef(mPipeIn).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTransport::OpenOutputStream(uint32_t flags, uint32_t segsize, + uint32_t segcount, + nsIOutputStream** result) { + // this transport only supports reading! + MOZ_ASSERT_UNREACHABLE("nsInputStreamTransport::OpenOutputStream"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsInputStreamTransport::Close(nsresult reason) { + if (NS_SUCCEEDED(reason)) reason = NS_BASE_STREAM_CLOSED; + + return mPipeIn->CloseWithStatus(reason); +} + +NS_IMETHODIMP +nsInputStreamTransport::SetEventSink(nsITransportEventSink* sink, + nsIEventTarget* target) { + NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS); + + if (target) { + return net_NewTransportEventSinkProxy(getter_AddRefs(mEventSink), sink, + target); + } + + mEventSink = sink; + return NS_OK; +} + +/** nsIInputStream **/ + +NS_IMETHODIMP +nsInputStreamTransport::Close() { + if (mCloseWhenDone) mSource->Close(); + + // make additional reads return early... + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTransport::Available(uint64_t* result) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsInputStreamTransport::StreamStatus() { return mSource->StreamStatus(); } + +NS_IMETHODIMP +nsInputStreamTransport::Read(char* buf, uint32_t count, uint32_t* result) { + nsresult rv = mSource->Read(buf, count, result); + + if (NS_SUCCEEDED(rv)) { + mOffset += *result; + if (mEventSink) { + mEventSink->OnTransportStatus(this, NS_NET_STATUS_READING, mOffset, -1); + } + } + return rv; +} + +NS_IMETHODIMP +nsInputStreamTransport::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t count, uint32_t* result) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsInputStreamTransport::IsNonBlocking(bool* result) { + *result = false; + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +nsInputStreamTransport::CloseWithStatus(nsresult aStatus) { return Close(); } + +NS_IMETHODIMP +nsInputStreamTransport::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + NS_ENSURE_STATE(!!mAsyncSource); + + nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr; + + { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + } + + return mAsyncSource->AsyncWait(callback, aFlags, aRequestedCount, + aEventTarget); +} + +// nsIInputStreamCallback + +NS_IMETHODIMP +nsInputStreamTransport::OnInputStreamReady(nsIAsyncInputStream* aStream) { + nsCOMPtr<nsIInputStreamCallback> callback; + { + MutexAutoLock lock(mMutex); + + // We have been canceled in the meanwhile. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + callback.swap(mAsyncWaitCallback); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamReady(this); +} + +//----------------------------------------------------------------------------- +// nsStreamTransportService +//----------------------------------------------------------------------------- + +nsStreamTransportService::nsStreamTransportService() = default; + +nsStreamTransportService::~nsStreamTransportService() { + NS_ASSERTION(!mPool, "thread pool wasn't shutdown"); +} + +nsresult nsStreamTransportService::Init() { + // Can't be used multithreaded before this + MOZ_PUSH_IGNORE_THREAD_SAFETY + MOZ_ASSERT(!mPool); + mPool = new nsThreadPool(); + + // Configure the pool + mPool->SetName("StreamTrans"_ns); + mPool->SetThreadLimit(25); + mPool->SetIdleThreadLimit(5); + mPool->SetIdleThreadTimeoutRegressive(true); + mPool->SetIdleThreadTimeout(PR_SecondsToInterval(30)); + MOZ_POP_THREAD_SAFETY + + nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService(); + if (obsSvc) obsSvc->AddObserver(this, "xpcom-shutdown-threads", false); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsStreamTransportService, nsIStreamTransportService, + nsIEventTarget, nsIObserver) + +NS_IMETHODIMP +nsStreamTransportService::DispatchFromScript(nsIRunnable* task, + uint32_t flags) { + nsCOMPtr<nsIRunnable> event(task); + return Dispatch(event.forget(), flags); +} + +NS_IMETHODIMP +nsStreamTransportService::Dispatch(already_AddRefed<nsIRunnable> task, + uint32_t flags) { + nsCOMPtr<nsIRunnable> event(task); // so it gets released on failure paths + nsCOMPtr<nsIThreadPool> pool; + { + mozilla::MutexAutoLock lock(mShutdownLock); + if (mIsShutdown) { + return NS_ERROR_NOT_INITIALIZED; + } + pool = mPool; + } + NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED); + return pool->Dispatch(event.forget(), flags); +} + +NS_IMETHODIMP +nsStreamTransportService::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aDelayMs) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStreamTransportService::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStreamTransportService::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP_(bool) +nsStreamTransportService::IsOnCurrentThreadInfallible() { + nsCOMPtr<nsIThreadPool> pool; + { + mozilla::MutexAutoLock lock(mShutdownLock); + pool = mPool; + } + if (!pool) { + return false; + } + return pool->IsOnCurrentThread(); +} + +NS_IMETHODIMP +nsStreamTransportService::IsOnCurrentThread(bool* result) { + nsCOMPtr<nsIThreadPool> pool; + { + mozilla::MutexAutoLock lock(mShutdownLock); + if (mIsShutdown) { + return NS_ERROR_NOT_INITIALIZED; + } + pool = mPool; + } + NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED); + return pool->IsOnCurrentThread(result); +} + +NS_IMETHODIMP +nsStreamTransportService::CreateInputTransport(nsIInputStream* stream, + bool closeWhenDone, + nsITransport** result) { + RefPtr<nsInputStreamTransport> trans = + new nsInputStreamTransport(stream, closeWhenDone); + trans.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamTransportService::Observe(nsISupports* subject, const char* topic, + const char16_t* data) { + NS_ASSERTION(strcmp(topic, "xpcom-shutdown-threads") == 0, "oops"); + + { + nsCOMPtr<nsIThreadPool> pool; + { + mozilla::MutexAutoLock lock(mShutdownLock); + mIsShutdown = true; + pool = mPool.forget(); + } + + if (pool) { + pool->Shutdown(); + } + } + return NS_OK; +} + +class AvailableEvent final : public Runnable { + public: + AvailableEvent(nsIInputStream* stream, nsIInputAvailableCallback* callback) + : Runnable("net::AvailableEvent"), + mStream(stream), + mCallback(callback), + mDoingCallback(false), + mSize(0), + mResultForCallback(NS_OK) { + mCallbackTarget = GetCurrentSerialEventTarget(); + } + + NS_IMETHOD Run() override { + if (mDoingCallback) { + // pong + mCallback->OnInputAvailableComplete(mSize, mResultForCallback); + mCallback = nullptr; + } else { + // ping + mResultForCallback = mStream->Available(&mSize); + mStream = nullptr; + mDoingCallback = true; + + nsCOMPtr<nsIRunnable> event(this); // overly cute + mCallbackTarget->Dispatch(event.forget(), NS_DISPATCH_NORMAL); + mCallbackTarget = nullptr; + } + return NS_OK; + } + + private: + virtual ~AvailableEvent() = default; + + nsCOMPtr<nsIInputStream> mStream; + nsCOMPtr<nsIInputAvailableCallback> mCallback; + nsCOMPtr<nsIEventTarget> mCallbackTarget; + bool mDoingCallback; + uint64_t mSize; + nsresult mResultForCallback; +}; + +NS_IMETHODIMP +nsStreamTransportService::InputAvailable(nsIInputStream* stream, + nsIInputAvailableCallback* callback) { + nsCOMPtr<nsIThreadPool> pool; + { + mozilla::MutexAutoLock lock(mShutdownLock); + if (mIsShutdown) { + return NS_ERROR_NOT_INITIALIZED; + } + pool = mPool; + } + nsCOMPtr<nsIRunnable> event = new AvailableEvent(stream, callback); + return pool->Dispatch(event.forget(), NS_DISPATCH_NORMAL); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsStreamTransportService.h b/netwerk/base/nsStreamTransportService.h new file mode 100644 index 0000000000..1229c0ca44 --- /dev/null +++ b/netwerk/base/nsStreamTransportService.h @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsStreamTransportService_h__ +#define nsStreamTransportService_h__ + +#include "nsIStreamTransportService.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/DataMutex.h" +#include "mozilla/Mutex.h" + +class nsIThreadPool; + +namespace mozilla { +namespace net { + +class nsStreamTransportService final : public nsIStreamTransportService, + public nsIEventTarget, + public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTREAMTRANSPORTSERVICE + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSIOBSERVER + + nsresult Init(); + + nsStreamTransportService(); + + private: + ~nsStreamTransportService(); + + nsCOMPtr<nsIThreadPool> mPool MOZ_GUARDED_BY(mShutdownLock); + + mozilla::Mutex mShutdownLock{"nsStreamTransportService.mShutdownLock"}; + bool mIsShutdown MOZ_GUARDED_BY(mShutdownLock){false}; +}; + +} // namespace net +} // namespace mozilla +#endif diff --git a/netwerk/base/nsSyncStreamListener.cpp b/netwerk/base/nsSyncStreamListener.cpp new file mode 100644 index 0000000000..7e5ec94231 --- /dev/null +++ b/netwerk/base/nsSyncStreamListener.cpp @@ -0,0 +1,169 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/SpinEventLoopUntil.h" +#include "nsIOService.h" +#include "nsIPipe.h" +#include "nsSyncStreamListener.h" +#include "nsThreadUtils.h" +#include <algorithm> + +using namespace mozilla::net; + +nsSyncStreamListener::nsSyncStreamListener() { + MOZ_ASSERT(NS_IsMainThread()); + NS_NewPipe(getter_AddRefs(mPipeIn), getter_AddRefs(mPipeOut), + mozilla::net::nsIOService::gDefaultSegmentSize, + UINT32_MAX, // no size limit + false, false); +} + +nsresult nsSyncStreamListener::WaitForData() { + mKeepWaiting = true; + + if (!mozilla::SpinEventLoopUntil("nsSyncStreamListener::Create"_ns, + [&]() { return !mKeepWaiting; })) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsSyncStreamListener::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsSyncStreamListener, nsIStreamListener, nsIRequestObserver, + nsIInputStream, nsISyncStreamListener) + +//----------------------------------------------------------------------------- +// nsSyncStreamListener::nsISyncStreamListener +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsSyncStreamListener::GetInputStream(nsIInputStream** result) { + *result = do_AddRef(this).take(); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsSyncStreamListener::nsIStreamListener +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsSyncStreamListener::OnStartRequest(nsIRequest* request) { return NS_OK; } + +NS_IMETHODIMP +nsSyncStreamListener::OnDataAvailable(nsIRequest* request, + nsIInputStream* stream, uint64_t offset, + uint32_t count) { + uint32_t bytesWritten; + + nsresult rv = mPipeOut->WriteFrom(stream, count, &bytesWritten); + + // if we get an error, then return failure. this will cause the + // channel to be canceled, and as a result our OnStopRequest method + // will be called immediately. because of this we do not need to + // set mStatus or mKeepWaiting here. + if (NS_FAILED(rv)) return rv; + + // we expect that all data will be written to the pipe because + // the pipe was created to have "infinite" room. + NS_ASSERTION(bytesWritten == count, "did not write all data"); + + mKeepWaiting = false; // unblock Read + return NS_OK; +} + +NS_IMETHODIMP +nsSyncStreamListener::OnStopRequest(nsIRequest* request, nsresult status) { + mStatus = status; + mKeepWaiting = false; // unblock Read + mDone = true; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsSyncStreamListener::nsIInputStream +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsSyncStreamListener::Close() { + mStatus = NS_BASE_STREAM_CLOSED; + mDone = true; + + // It'd be nice if we could explicitly cancel the request at this point, + // but we don't have a reference to it, so the best we can do is close the + // pipe so that the next OnDataAvailable event will fail. + if (mPipeIn) { + mPipeIn->Close(); + mPipeIn = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSyncStreamListener::Available(uint64_t* result) { + if (NS_FAILED(mStatus)) return mStatus; + + mStatus = mPipeIn->Available(result); + if (NS_SUCCEEDED(mStatus) && (*result == 0) && !mDone) { + nsresult rv = WaitForData(); + if (NS_FAILED(rv)) { + // Note that `WaitForData` could fail `mStatus`. Do not overwrite if it's + // the case. + mStatus = NS_SUCCEEDED(mStatus) ? rv : mStatus; + } else if (NS_SUCCEEDED(mStatus)) { + mStatus = mPipeIn->Available(result); + } + } + return mStatus; +} + +NS_IMETHODIMP +nsSyncStreamListener::StreamStatus() { + if (NS_FAILED(mStatus)) { + return mStatus; + } + + mStatus = mPipeIn->StreamStatus(); + return mStatus; +} + +NS_IMETHODIMP +nsSyncStreamListener::Read(char* buf, uint32_t bufLen, uint32_t* result) { + if (mStatus == NS_BASE_STREAM_CLOSED) { + *result = 0; + return NS_OK; + } + + uint64_t avail64; + if (NS_FAILED(Available(&avail64))) return mStatus; + + uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)bufLen); + mStatus = mPipeIn->Read(buf, avail, result); + return mStatus; +} + +NS_IMETHODIMP +nsSyncStreamListener::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t count, uint32_t* result) { + if (mStatus == NS_BASE_STREAM_CLOSED) { + *result = 0; + return NS_OK; + } + + uint64_t avail64; + if (NS_FAILED(Available(&avail64))) return mStatus; + + uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)count); + mStatus = mPipeIn->ReadSegments(writer, closure, avail, result); + return mStatus; +} + +NS_IMETHODIMP +nsSyncStreamListener::IsNonBlocking(bool* result) { + *result = false; + return NS_OK; +} diff --git a/netwerk/base/nsSyncStreamListener.h b/netwerk/base/nsSyncStreamListener.h new file mode 100644 index 0000000000..9bd9908535 --- /dev/null +++ b/netwerk/base/nsSyncStreamListener.h @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSyncStreamListener_h__ +#define nsSyncStreamListener_h__ + +#include "nsISyncStreamListener.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +//----------------------------------------------------------------------------- + +class nsSyncStreamListener final : public nsISyncStreamListener, + public nsIInputStream { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSISYNCSTREAMLISTENER + NS_DECL_NSIINPUTSTREAM + + private: + // Factory method: + friend nsresult NS_NewSyncStreamListener(nsIStreamListener** result, + nsIInputStream** stream); + nsSyncStreamListener(); + ~nsSyncStreamListener() = default; + + nsresult Init(); + + nsresult WaitForData(); + + nsCOMPtr<nsIInputStream> mPipeIn; + nsCOMPtr<nsIOutputStream> mPipeOut; + nsresult mStatus{NS_OK}; + bool mKeepWaiting{false}; + bool mDone{false}; +}; + +#endif // nsSyncStreamListener_h__ diff --git a/netwerk/base/nsTransportUtils.cpp b/netwerk/base/nsTransportUtils.cpp new file mode 100644 index 0000000000..df53ead198 --- /dev/null +++ b/netwerk/base/nsTransportUtils.cpp @@ -0,0 +1,133 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsITransport.h" +#include "nsProxyRelease.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "nsTransportUtils.h" + +using namespace mozilla; + +//----------------------------------------------------------------------------- + +class nsTransportStatusEvent; + +class nsTransportEventSinkProxy : public nsITransportEventSink { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORTEVENTSINK + + nsTransportEventSinkProxy(nsITransportEventSink* sink, nsIEventTarget* target) + : mSink(sink), + mTarget(target), + mLock("nsTransportEventSinkProxy.mLock"), + mLastEvent(nullptr) { + NS_ADDREF(mSink); + } + + private: + virtual ~nsTransportEventSinkProxy() { + // our reference to mSink could be the last, so be sure to release + // it on the target thread. otherwise, we could get into trouble. + NS_ProxyRelease("nsTransportEventSinkProxy::mSink", mTarget, + dont_AddRef(mSink)); + } + + public: + nsITransportEventSink* mSink; + nsCOMPtr<nsIEventTarget> mTarget; + Mutex mLock MOZ_UNANNOTATED; + nsTransportStatusEvent* mLastEvent; +}; + +class nsTransportStatusEvent : public Runnable { + public: + nsTransportStatusEvent(nsTransportEventSinkProxy* proxy, + nsITransport* transport, nsresult status, + int64_t progress, int64_t progressMax) + : Runnable("nsTransportStatusEvent"), + mProxy(proxy), + mTransport(transport), + mStatus(status), + mProgress(progress), + mProgressMax(progressMax) {} + + ~nsTransportStatusEvent() { + auto ReleaseTransport = [transport(std::move(mTransport))]() mutable {}; + if (!net::OnSocketThread()) { + net::gSocketTransportService->Dispatch(NS_NewRunnableFunction( + "nsHttpConnection::~nsHttpConnection", std::move(ReleaseTransport))); + } + } + + NS_IMETHOD Run() override { + // since this event is being handled, we need to clear the proxy's ref. + // if not coalescing all, then last event may not equal self! + { + MutexAutoLock lock(mProxy->mLock); + if (mProxy->mLastEvent == this) mProxy->mLastEvent = nullptr; + } + + mProxy->mSink->OnTransportStatus(mTransport, mStatus, mProgress, + mProgressMax); + return NS_OK; + } + + RefPtr<nsTransportEventSinkProxy> mProxy; + + // parameters to OnTransportStatus + nsCOMPtr<nsITransport> mTransport; + nsresult mStatus; + int64_t mProgress; + int64_t mProgressMax; +}; + +NS_IMPL_ISUPPORTS(nsTransportEventSinkProxy, nsITransportEventSink) + +NS_IMETHODIMP +nsTransportEventSinkProxy::OnTransportStatus(nsITransport* transport, + nsresult status, int64_t progress, + int64_t progressMax) { + nsresult rv = NS_OK; + RefPtr<nsTransportStatusEvent> event; + { + MutexAutoLock lock(mLock); + + // try to coalesce events! ;-) + if (mLastEvent && (mLastEvent->mStatus == status)) { + mLastEvent->mStatus = status; + mLastEvent->mProgress = progress; + mLastEvent->mProgressMax = progressMax; + } else { + event = new nsTransportStatusEvent(this, transport, status, progress, + progressMax); + if (!event) rv = NS_ERROR_OUT_OF_MEMORY; + mLastEvent = event; // weak ref + } + } + if (event) { + rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("unable to post transport status event"); + + MutexAutoLock lock(mLock); // cleanup.. don't reference anymore! + mLastEvent = nullptr; + } + } + return rv; +} + +//----------------------------------------------------------------------------- + +nsresult net_NewTransportEventSinkProxy(nsITransportEventSink** result, + nsITransportEventSink* sink, + nsIEventTarget* target) { + RefPtr<nsTransportEventSinkProxy> res = + new nsTransportEventSinkProxy(sink, target); + res.forget(result); + return NS_OK; +} diff --git a/netwerk/base/nsTransportUtils.h b/netwerk/base/nsTransportUtils.h new file mode 100644 index 0000000000..141b9e4eda --- /dev/null +++ b/netwerk/base/nsTransportUtils.h @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTransportUtils_h__ +#define nsTransportUtils_h__ + +#include "nsITransport.h" + +/** + * This function returns a proxy object for a transport event sink instance. + * The transport event sink will be called on the thread indicated by the + * given event target. Like events are automatically coalesced. This means + * that for example if the status value is the same from event to event, and + * the previous event has not yet been delivered, then only one event will + * be delivered. The progress reported will be that from the second event. + + * Coalescing events can help prevent a backlog of unprocessed transport + * events in the case that the target thread is overworked. + */ +nsresult net_NewTransportEventSinkProxy(nsITransportEventSink** aResult, + nsITransportEventSink* aSink, + nsIEventTarget* aTarget); + +#endif // nsTransportUtils_h__ diff --git a/netwerk/base/nsUDPSocket.cpp b/netwerk/base/nsUDPSocket.cpp new file mode 100644 index 0000000000..991c28acf8 --- /dev/null +++ b/netwerk/base/nsUDPSocket.cpp @@ -0,0 +1,1553 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/Telemetry.h" + +#include "nsQueryObject.h" +#include "nsSocketTransport2.h" +#include "nsUDPSocket.h" +#include "nsProxyRelease.h" +#include "nsError.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIOService.h" +#include "prnetdb.h" +#include "prio.h" +#include "nsNetAddr.h" +#include "nsNetSegmentUtils.h" +#include "IOActivityMonitor.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "prerror.h" +#include "nsThreadUtils.h" +#include "nsIDNSRecord.h" +#include "nsIDNSService.h" +#include "nsICancelable.h" +#include "nsIPipe.h" +#include "nsWrapperCacheInlines.h" +#include "HttpConnectionUDP.h" +#include "mozilla/ProfilerBandwidthCounter.h" +#include "mozilla/StaticPrefs_network.h" + +#if defined(FUZZING) +# include "FuzzyLayer.h" +# include "mozilla/StaticPrefs_fuzzing.h" +#endif + +namespace mozilla { +namespace net { + +static const uint32_t UDP_PACKET_CHUNK_SIZE = 1400; + +//----------------------------------------------------------------------------- + +using nsUDPSocketFunc = void (nsUDPSocket::*)(); + +static nsresult PostEvent(nsUDPSocket* s, nsUDPSocketFunc func) { + if (!gSocketTransportService) return NS_ERROR_FAILURE; + + return gSocketTransportService->Dispatch( + NewRunnableMethod("net::PostEvent", s, func), NS_DISPATCH_NORMAL); +} + +static nsresult ResolveHost(const nsACString& host, + const OriginAttributes& aOriginAttributes, + nsIDNSListener* listener) { + nsresult rv; + + nsCOMPtr<nsIDNSService> dns = + do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsICancelable> tmpOutstanding; + return dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, + listener, nullptr, aOriginAttributes, + getter_AddRefs(tmpOutstanding)); +} + +static nsresult CheckIOStatus(const NetAddr* aAddr) { + MOZ_ASSERT(gIOService); + + if (gIOService->IsNetTearingDown()) { + return NS_ERROR_FAILURE; + } + + if (gIOService->IsOffline() && + (StaticPrefs::network_disable_localhost_when_offline() || + !aAddr->IsLoopbackAddr())) { + return NS_ERROR_OFFLINE; + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- + +class SetSocketOptionRunnable : public Runnable { + public: + SetSocketOptionRunnable(nsUDPSocket* aSocket, const PRSocketOptionData& aOpt) + : Runnable("net::SetSocketOptionRunnable"), + mSocket(aSocket), + mOpt(aOpt) {} + + NS_IMETHOD Run() override { return mSocket->SetSocketOption(mOpt); } + + private: + RefPtr<nsUDPSocket> mSocket; + PRSocketOptionData mOpt; +}; + +//----------------------------------------------------------------------------- +// nsUDPOutputStream impl +//----------------------------------------------------------------------------- +NS_IMPL_ISUPPORTS(nsUDPOutputStream, nsIOutputStream) + +nsUDPOutputStream::nsUDPOutputStream(nsUDPSocket* aSocket, PRFileDesc* aFD, + PRNetAddr& aPrClientAddr) + : mSocket(aSocket), + mFD(aFD), + mPrClientAddr(aPrClientAddr), + mIsClosed(false) {} + +NS_IMETHODIMP nsUDPOutputStream::Close() { + if (mIsClosed) return NS_BASE_STREAM_CLOSED; + + mIsClosed = true; + return NS_OK; +} + +NS_IMETHODIMP nsUDPOutputStream::Flush() { return NS_OK; } + +NS_IMETHODIMP nsUDPOutputStream::StreamStatus() { + return mIsClosed ? NS_BASE_STREAM_CLOSED : NS_OK; +} + +NS_IMETHODIMP nsUDPOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* _retval) { + if (mIsClosed) return NS_BASE_STREAM_CLOSED; + + *_retval = 0; + int32_t count = + PR_SendTo(mFD, aBuf, aCount, 0, &mPrClientAddr, PR_INTERVAL_NO_WAIT); + if (count < 0) { + PRErrorCode code = PR_GetError(); + return ErrorAccordingToNSPR(code); + } + + *_retval = count; + + mSocket->AddOutputBytes(count); + + return NS_OK; +} + +NS_IMETHODIMP nsUDPOutputStream::WriteFrom(nsIInputStream* aFromStream, + uint32_t aCount, uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsUDPOutputStream::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, uint32_t aCount, + uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsUDPOutputStream::IsNonBlocking(bool* _retval) { + *_retval = true; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsUDPMessage impl +//----------------------------------------------------------------------------- +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsUDPMessage) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsUDPMessage) + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsUDPMessage) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsUDPMessage) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIUDPMessage) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsUDPMessage) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJsobj) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsUDPMessage) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsUDPMessage) + tmp->mJsobj = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +nsUDPMessage::nsUDPMessage(NetAddr* aAddr, nsIOutputStream* aOutputStream, + FallibleTArray<uint8_t>&& aData) + : mOutputStream(aOutputStream), mData(std::move(aData)) { + memcpy(&mAddr, aAddr, sizeof(NetAddr)); +} + +nsUDPMessage::~nsUDPMessage() { DropJSObjects(this); } + +NS_IMETHODIMP +nsUDPMessage::GetFromAddr(nsINetAddr** aFromAddr) { + NS_ENSURE_ARG_POINTER(aFromAddr); + + nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr); + result.forget(aFromAddr); + + return NS_OK; +} + +NS_IMETHODIMP +nsUDPMessage::GetData(nsACString& aData) { + aData.Assign(reinterpret_cast<const char*>(mData.Elements()), mData.Length()); + return NS_OK; +} + +NS_IMETHODIMP +nsUDPMessage::GetOutputStream(nsIOutputStream** aOutputStream) { + NS_ENSURE_ARG_POINTER(aOutputStream); + *aOutputStream = do_AddRef(mOutputStream).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsUDPMessage::GetRawData(JSContext* cx, JS::MutableHandle<JS::Value> aRawData) { + if (!mJsobj) { + ErrorResult error; + mJsobj = dom::Uint8Array::Create(cx, nullptr, mData, error); + if (error.Failed()) { + return error.StealNSResult(); + } + HoldJSObjects(this); + } + aRawData.setObject(*mJsobj); + return NS_OK; +} + +FallibleTArray<uint8_t>& nsUDPMessage::GetDataAsTArray() { return mData; } + +//----------------------------------------------------------------------------- +// nsUDPSocket +//----------------------------------------------------------------------------- + +nsUDPSocket::nsUDPSocket() { + // we want to be able to access the STS directly, and it may not have been + // constructed yet. the STS constructor sets gSocketTransportService. + if (!gSocketTransportService) { + // This call can fail if we're offline, for example. + nsCOMPtr<nsISocketTransportService> sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + } + + mSts = gSocketTransportService; +} + +nsUDPSocket::~nsUDPSocket() { CloseSocket(); } + +void nsUDPSocket::AddOutputBytes(int32_t aBytes) { + mByteWriteCount += aBytes; + profiler_count_bandwidth_written_bytes(aBytes); +} + +void nsUDPSocket::OnMsgClose() { + UDPSOCKET_LOG(("nsUDPSocket::OnMsgClose [this=%p]\n", this)); + + if (NS_FAILED(mCondition)) return; + + // tear down socket. this signals the STS to detach our socket handler. + mCondition = NS_BINDING_ABORTED; + + // if we are attached, then socket transport service will call our + // OnSocketDetached method automatically. Otherwise, we have to call it + // (and thus close the socket) manually. + if (!mAttached) OnSocketDetached(mFD); +} + +void nsUDPSocket::OnMsgAttach() { + UDPSOCKET_LOG(("nsUDPSocket::OnMsgAttach [this=%p]\n", this)); + + if (NS_FAILED(mCondition)) return; + + mCondition = TryAttach(); + + // if we hit an error while trying to attach then bail... + if (NS_FAILED(mCondition)) { + UDPSOCKET_LOG(("nsUDPSocket::OnMsgAttach: TryAttach FAILED err=0x%" PRIx32 + " [this=%p]\n", + static_cast<uint32_t>(mCondition), this)); + NS_ASSERTION(!mAttached, "should not be attached already"); + OnSocketDetached(mFD); + } +} + +nsresult nsUDPSocket::TryAttach() { + nsresult rv; + + if (!gSocketTransportService) return NS_ERROR_FAILURE; + + rv = CheckIOStatus(&mAddr); + if (NS_FAILED(rv)) { + return rv; + } + + // + // find out if it is going to be ok to attach another socket to the STS. + // if not then we have to wait for the STS to tell us that it is ok. + // the notification is asynchronous, which means that when we could be + // in a race to call AttachSocket once notified. for this reason, when + // we get notified, we just re-enter this function. as a result, we are + // sure to ask again before calling AttachSocket. in this way we deal + // with the race condition. though it isn't the most elegant solution, + // it is far simpler than trying to build a system that would guarantee + // FIFO ordering (which wouldn't even be that valuable IMO). see bug + // 194402 for more info. + // + if (!gSocketTransportService->CanAttachSocket()) { + nsCOMPtr<nsIRunnable> event = NewRunnableMethod( + "net::nsUDPSocket::OnMsgAttach", this, &nsUDPSocket::OnMsgAttach); + + nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event); + if (NS_FAILED(rv)) return rv; + } + + // + // ok, we can now attach our socket to the STS for polling + // + rv = gSocketTransportService->AttachSocket(mFD, this); + if (NS_FAILED(rv)) return rv; + + mAttached = true; + + // + // now, configure our poll flags for listening... + // + mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT); + return NS_OK; +} + +namespace { +//----------------------------------------------------------------------------- +// UDPMessageProxy +//----------------------------------------------------------------------------- +class UDPMessageProxy final : public nsIUDPMessage { + public: + UDPMessageProxy(NetAddr* aAddr, nsIOutputStream* aOutputStream, + FallibleTArray<uint8_t>&& aData) + : mOutputStream(aOutputStream), mData(std::move(aData)) { + memcpy(&mAddr, aAddr, sizeof(mAddr)); + } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUDPMESSAGE + + private: + ~UDPMessageProxy() = default; + + NetAddr mAddr; + nsCOMPtr<nsIOutputStream> mOutputStream; + FallibleTArray<uint8_t> mData; +}; + +NS_IMPL_ISUPPORTS(UDPMessageProxy, nsIUDPMessage) + +NS_IMETHODIMP +UDPMessageProxy::GetFromAddr(nsINetAddr** aFromAddr) { + NS_ENSURE_ARG_POINTER(aFromAddr); + + nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr); + result.forget(aFromAddr); + + return NS_OK; +} + +NS_IMETHODIMP +UDPMessageProxy::GetData(nsACString& aData) { + aData.Assign(reinterpret_cast<const char*>(mData.Elements()), mData.Length()); + return NS_OK; +} + +FallibleTArray<uint8_t>& UDPMessageProxy::GetDataAsTArray() { return mData; } + +NS_IMETHODIMP +UDPMessageProxy::GetRawData(JSContext* cx, + JS::MutableHandle<JS::Value> aRawData) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +UDPMessageProxy::GetOutputStream(nsIOutputStream** aOutputStream) { + NS_ENSURE_ARG_POINTER(aOutputStream); + *aOutputStream = do_AddRef(mOutputStream).take(); + return NS_OK; +} + +} // anonymous namespace + +//----------------------------------------------------------------------------- +// nsUDPSocket::nsASocketHandler +//----------------------------------------------------------------------------- + +void nsUDPSocket::OnSocketReady(PRFileDesc* fd, int16_t outFlags) { + UDPSOCKET_LOG( + ("nsUDPSocket::OnSocketReady: flags=%d [this=%p]\n", outFlags, this)); + NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops"); + NS_ASSERTION(mFD == fd, "wrong file descriptor"); + NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached"); + + if (outFlags & (PR_POLL_HUP | PR_POLL_NVAL)) { + NS_WARNING("error polling on listening socket"); + mCondition = NS_ERROR_UNEXPECTED; + return; + } + + if (mSyncListener) { + mSyncListener->OnPacketReceived(this); + return; + } + + PRNetAddr prClientAddr; + int32_t count; + // Bug 1252755 - use 9216 bytes to allign with nICEr and transportlayer to + // support the maximum size of jumbo frames + char buff[9216]; + count = PR_RecvFrom(mFD, buff, sizeof(buff), 0, &prClientAddr, + PR_INTERVAL_NO_WAIT); + if (count < 0) { + UDPSOCKET_LOG( + ("nsUDPSocket::OnSocketReady: PR_RecvFrom failed [this=%p]\n", this)); + return; + } + mByteReadCount += count; + profiler_count_bandwidth_read_bytes(count); + + FallibleTArray<uint8_t> data; + if (!data.AppendElements(buff, count, fallible)) { + UDPSOCKET_LOG(( + "nsUDPSocket::OnSocketReady: AppendElements FAILED [this=%p]\n", this)); + mCondition = NS_ERROR_UNEXPECTED; + return; + } + + nsCOMPtr<nsIAsyncInputStream> pipeIn; + nsCOMPtr<nsIAsyncOutputStream> pipeOut; + + uint32_t segsize = UDP_PACKET_CHUNK_SIZE; + uint32_t segcount = 0; + net_ResolveSegmentParams(segsize, segcount); + NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true, + segsize, segcount); + + RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prClientAddr); + nsresult rv = NS_AsyncCopy(pipeIn, os, mSts, NS_ASYNCCOPY_VIA_READSEGMENTS, + UDP_PACKET_CHUNK_SIZE); + + if (NS_FAILED(rv)) { + return; + } + + NetAddr netAddr(&prClientAddr); + nsCOMPtr<nsIUDPMessage> message = + new UDPMessageProxy(&netAddr, pipeOut, std::move(data)); + mListener->OnPacketReceived(this, message); +} + +void nsUDPSocket::OnSocketDetached(PRFileDesc* fd) { + UDPSOCKET_LOG(("nsUDPSocket::OnSocketDetached [this=%p]\n", this)); + // force a failure condition if none set; maybe the STS is shutting down :-/ + if (NS_SUCCEEDED(mCondition)) mCondition = NS_ERROR_ABORT; + + if (mFD) { + NS_ASSERTION(mFD == fd, "wrong file descriptor"); + CloseSocket(); + } + + if (mSyncListener) { + mSyncListener->OnStopListening(this, mCondition); + mSyncListener = nullptr; + } else if (mListener) { + // need to atomically clear mListener. see our Close() method. + RefPtr<nsIUDPSocketListener> listener = nullptr; + { + MutexAutoLock lock(mLock); + listener = ToRefPtr(std::move(mListener)); + } + + if (listener) { + listener->OnStopListening(this, mCondition); + NS_ProxyRelease("nsUDPSocket::mListener", mListenerTarget, + listener.forget()); + } + } +} + +void nsUDPSocket::IsLocal(bool* aIsLocal) { + // If bound to loopback, this UDP socket only accepts local connections. + *aIsLocal = mAddr.IsLoopbackAddr(); +} + +nsresult nsUDPSocket::GetRemoteAddr(NetAddr* addr) { + if (!mSyncListener) { + return NS_ERROR_FAILURE; + } + RefPtr<HttpConnectionUDP> connUDP = do_QueryObject(mSyncListener); + if (!connUDP) { + return NS_ERROR_FAILURE; + } + return connUDP->GetPeerAddr(addr); +} + +//----------------------------------------------------------------------------- +// nsSocket::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsUDPSocket, nsIUDPSocket) + +//----------------------------------------------------------------------------- +// nsSocket::nsISocket +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsUDPSocket::Init(int32_t aPort, bool aLoopbackOnly, nsIPrincipal* aPrincipal, + bool aAddressReuse, uint8_t aOptionalArgc) { + NetAddr addr; + + if (aPort < 0) aPort = 0; + + addr.raw.family = AF_INET; + addr.inet.port = htons(aPort); + + if (aLoopbackOnly) { + addr.inet.ip = htonl(INADDR_LOOPBACK); + } else { + addr.inet.ip = htonl(INADDR_ANY); + } + + return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc); +} + +NS_IMETHODIMP +nsUDPSocket::Init2(const nsACString& aAddr, int32_t aPort, + nsIPrincipal* aPrincipal, bool aAddressReuse, + uint8_t aOptionalArgc) { + if (NS_WARN_IF(aAddr.IsEmpty())) { + return NS_ERROR_INVALID_ARG; + } + + if (aPort < 0) { + aPort = 0; + } + + NetAddr addr; + if (NS_FAILED(addr.InitFromString(aAddr, uint16_t(aPort)))) { + return NS_ERROR_FAILURE; + } + + if (addr.raw.family != PR_AF_INET && addr.raw.family != PR_AF_INET6) { + MOZ_ASSERT_UNREACHABLE("Dont accept address other than IPv4 and IPv6"); + return NS_ERROR_ILLEGAL_VALUE; + } + + return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc); +} + +NS_IMETHODIMP +nsUDPSocket::InitWithAddress(const NetAddr* aAddr, nsIPrincipal* aPrincipal, + bool aAddressReuse, uint8_t aOptionalArgc) { + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + + nsresult rv; + + rv = CheckIOStatus(aAddr); + if (NS_FAILED(rv)) { + return rv; + } + + bool addressReuse = (aOptionalArgc == 1) ? aAddressReuse : true; + + if (aPrincipal) { + mOriginAttributes = aPrincipal->OriginAttributesRef(); + } + // + // configure listening socket... + // + + mFD = PR_OpenUDPSocket(aAddr->raw.family); + if (!mFD) { + NS_WARNING("unable to create UDP socket"); + return NS_ERROR_FAILURE; + } + +#ifdef FUZZING + if (StaticPrefs::fuzzing_necko_enabled()) { + rv = AttachFuzzyIOLayer(mFD); + if (NS_FAILED(rv)) { + UDPSOCKET_LOG(("Failed to attach fuzzing IOLayer [rv=%" PRIx32 "].\n", + static_cast<uint32_t>(rv))); + return rv; + } + UDPSOCKET_LOG(("Successfully attached fuzzing IOLayer.\n")); + } +#endif + + uint16_t port; + if (NS_FAILED(aAddr->GetPort(&port))) { + NS_WARNING("invalid bind address"); + goto fail; + } + + PRSocketOptionData opt; + + // Linux kernel will sometimes hand out a used port if we bind + // to port 0 with SO_REUSEADDR + if (port) { + opt.option = PR_SockOpt_Reuseaddr; + opt.value.reuse_addr = addressReuse; + PR_SetSocketOption(mFD, &opt); + } + + opt.option = PR_SockOpt_Nonblocking; + opt.value.non_blocking = true; + PR_SetSocketOption(mFD, &opt); + + PRNetAddr addr; + // Temporary work around for IPv6 until bug 1330490 is fixed + memset(&addr, 0, sizeof(addr)); + NetAddrToPRNetAddr(aAddr, &addr); + + if (PR_Bind(mFD, &addr) != PR_SUCCESS) { + NS_WARNING("failed to bind socket"); + goto fail; + } + + // get the resulting socket address, which may be different than what + // we passed to bind. + if (PR_GetSockName(mFD, &addr) != PR_SUCCESS) { + NS_WARNING("cannot get socket name"); + goto fail; + } + + PRNetAddrToNetAddr(&addr, &mAddr); + + // create proxy via IOActivityMonitor + IOActivityMonitor::MonitorSocket(mFD); + + // wait until AsyncListen is called before polling the socket for + // client connections. + return NS_OK; + +fail: + Close(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsUDPSocket::Connect(const NetAddr* aAddr) { + UDPSOCKET_LOG(("nsUDPSocket::Connect [this=%p]\n", this)); + + NS_ENSURE_ARG(aAddr); + + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv; + + rv = CheckIOStatus(aAddr); + if (NS_FAILED(rv)) { + return rv; + } + + bool onSTSThread = false; + mSts->IsOnCurrentThread(&onSTSThread); + NS_ASSERTION(onSTSThread, "NOT ON STS THREAD"); + if (!onSTSThread) { + return NS_ERROR_FAILURE; + } + + PRNetAddr prAddr; + memset(&prAddr, 0, sizeof(prAddr)); + NetAddrToPRNetAddr(aAddr, &prAddr); + + if (PR_Connect(mFD, &prAddr, PR_INTERVAL_NO_WAIT) != PR_SUCCESS) { + NS_WARNING("Cannot PR_Connect"); + return NS_ERROR_FAILURE; + } + PR_SetFDInheritable(mFD, false); + + // get the resulting socket address, which may have been updated. + PRNetAddr addr; + if (PR_GetSockName(mFD, &addr) != PR_SUCCESS) { + NS_WARNING("cannot get socket name"); + return NS_ERROR_FAILURE; + } + + PRNetAddrToNetAddr(&addr, &mAddr); + + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::Close() { + { + MutexAutoLock lock(mLock); + // we want to proxy the close operation to the socket thread if a listener + // has been set. otherwise, we should just close the socket here... + if (!mListener && !mSyncListener) { + // Here we want to go directly with closing the socket since some tests + // expects this happen synchronously. + CloseSocket(); + + return NS_OK; + } + } + return PostEvent(this, &nsUDPSocket::OnMsgClose); +} + +NS_IMETHODIMP +nsUDPSocket::GetPort(int32_t* aResult) { + // no need to enter the lock here + uint16_t result; + nsresult rv = mAddr.GetPort(&result); + *aResult = static_cast<int32_t>(result); + return rv; +} + +NS_IMETHODIMP +nsUDPSocket::GetLocalAddr(nsINetAddr** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr); + result.forget(aResult); + + return NS_OK; +} + +void nsUDPSocket::CloseSocket() { + if (mFD) { + if (gIOService->IsNetTearingDown() && + ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) > + gSocketTransportService->MaxTimeForPrClosePref())) { + // If shutdown last to long, let the socket leak and do not close it. + UDPSOCKET_LOG(("Intentional leak")); + } else { + PRIntervalTime closeStarted = 0; + if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + closeStarted = PR_IntervalNow(); + } + + PR_Close(mFD); + + if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + PRIntervalTime now = PR_IntervalNow(); + if (gIOService->IsNetTearingDown()) { + Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_SHUTDOWN, + PR_IntervalToMilliseconds(now - closeStarted)); + + } else if (PR_IntervalToSeconds( + now - gIOService->LastConnectivityChange()) < 60) { + Telemetry::Accumulate( + Telemetry::PRCLOSE_UDP_BLOCKING_TIME_CONNECTIVITY_CHANGE, + PR_IntervalToMilliseconds(now - closeStarted)); + + } else if (PR_IntervalToSeconds( + now - gIOService->LastNetworkLinkChange()) < 60) { + Telemetry::Accumulate( + Telemetry::PRCLOSE_UDP_BLOCKING_TIME_LINK_CHANGE, + PR_IntervalToMilliseconds(now - closeStarted)); + + } else if (PR_IntervalToSeconds( + now - gIOService->LastOfflineStateChange()) < 60) { + Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_OFFLINE, + PR_IntervalToMilliseconds(now - closeStarted)); + + } else { + Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_NORMAL, + PR_IntervalToMilliseconds(now - closeStarted)); + } + } + } + mFD = nullptr; + } +} + +NS_IMETHODIMP +nsUDPSocket::GetAddress(NetAddr* aResult) { + // no need to enter the lock here + memcpy(aResult, &mAddr, sizeof(mAddr)); + return NS_OK; +} + +namespace { +//----------------------------------------------------------------------------- +// SocketListenerProxy +//----------------------------------------------------------------------------- +class SocketListenerProxy final : public nsIUDPSocketListener { + ~SocketListenerProxy() = default; + + public: + explicit SocketListenerProxy(nsIUDPSocketListener* aListener) + : mListener(new nsMainThreadPtrHolder<nsIUDPSocketListener>( + "SocketListenerProxy::mListener", aListener)), + mTarget(GetCurrentSerialEventTarget()) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUDPSOCKETLISTENER + + class OnPacketReceivedRunnable : public Runnable { + public: + OnPacketReceivedRunnable( + const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener, + nsIUDPSocket* aSocket, nsIUDPMessage* aMessage) + : Runnable("net::SocketListenerProxy::OnPacketReceivedRunnable"), + mListener(aListener), + mSocket(aSocket), + mMessage(aMessage) {} + + NS_DECL_NSIRUNNABLE + + private: + nsMainThreadPtrHandle<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIUDPSocket> mSocket; + nsCOMPtr<nsIUDPMessage> mMessage; + }; + + class OnStopListeningRunnable : public Runnable { + public: + OnStopListeningRunnable( + const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener, + nsIUDPSocket* aSocket, nsresult aStatus) + : Runnable("net::SocketListenerProxy::OnStopListeningRunnable"), + mListener(aListener), + mSocket(aSocket), + mStatus(aStatus) {} + + NS_DECL_NSIRUNNABLE + + private: + nsMainThreadPtrHandle<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIUDPSocket> mSocket; + nsresult mStatus; + }; + + private: + nsMainThreadPtrHandle<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIEventTarget> mTarget; +}; + +NS_IMPL_ISUPPORTS(SocketListenerProxy, nsIUDPSocketListener) + +NS_IMETHODIMP +SocketListenerProxy::OnPacketReceived(nsIUDPSocket* aSocket, + nsIUDPMessage* aMessage) { + RefPtr<OnPacketReceivedRunnable> r = + new OnPacketReceivedRunnable(mListener, aSocket, aMessage); + return mTarget->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +SocketListenerProxy::OnStopListening(nsIUDPSocket* aSocket, nsresult aStatus) { + RefPtr<OnStopListeningRunnable> r = + new OnStopListeningRunnable(mListener, aSocket, aStatus); + return mTarget->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +SocketListenerProxy::OnPacketReceivedRunnable::Run() { + NetAddr netAddr; + nsCOMPtr<nsINetAddr> nsAddr; + mMessage->GetFromAddr(getter_AddRefs(nsAddr)); + nsAddr->GetNetAddr(&netAddr); + + nsCOMPtr<nsIOutputStream> outputStream; + mMessage->GetOutputStream(getter_AddRefs(outputStream)); + + FallibleTArray<uint8_t>& data = mMessage->GetDataAsTArray(); + + nsCOMPtr<nsIUDPMessage> message = + new nsUDPMessage(&netAddr, outputStream, std::move(data)); + mListener->OnPacketReceived(mSocket, message); + return NS_OK; +} + +NS_IMETHODIMP +SocketListenerProxy::OnStopListeningRunnable::Run() { + mListener->OnStopListening(mSocket, mStatus); + return NS_OK; +} + +class SocketListenerProxyBackground final : public nsIUDPSocketListener { + ~SocketListenerProxyBackground() = default; + + public: + explicit SocketListenerProxyBackground(nsIUDPSocketListener* aListener) + : mListener(aListener), mTarget(GetCurrentSerialEventTarget()) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUDPSOCKETLISTENER + + class OnPacketReceivedRunnable : public Runnable { + public: + OnPacketReceivedRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener, + nsIUDPSocket* aSocket, nsIUDPMessage* aMessage) + : Runnable( + "net::SocketListenerProxyBackground::OnPacketReceivedRunnable"), + mListener(aListener), + mSocket(aSocket), + mMessage(aMessage) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIUDPSocket> mSocket; + nsCOMPtr<nsIUDPMessage> mMessage; + }; + + class OnStopListeningRunnable : public Runnable { + public: + OnStopListeningRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener, + nsIUDPSocket* aSocket, nsresult aStatus) + : Runnable( + "net::SocketListenerProxyBackground::OnStopListeningRunnable"), + mListener(aListener), + mSocket(aSocket), + mStatus(aStatus) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIUDPSocket> mSocket; + nsresult mStatus; + }; + + private: + nsCOMPtr<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIEventTarget> mTarget; +}; + +NS_IMPL_ISUPPORTS(SocketListenerProxyBackground, nsIUDPSocketListener) + +NS_IMETHODIMP +SocketListenerProxyBackground::OnPacketReceived(nsIUDPSocket* aSocket, + nsIUDPMessage* aMessage) { + RefPtr<OnPacketReceivedRunnable> r = + new OnPacketReceivedRunnable(mListener, aSocket, aMessage); + return mTarget->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +SocketListenerProxyBackground::OnStopListening(nsIUDPSocket* aSocket, + nsresult aStatus) { + RefPtr<OnStopListeningRunnable> r = + new OnStopListeningRunnable(mListener, aSocket, aStatus); + return mTarget->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +SocketListenerProxyBackground::OnPacketReceivedRunnable::Run() { + NetAddr netAddr; + nsCOMPtr<nsINetAddr> nsAddr; + mMessage->GetFromAddr(getter_AddRefs(nsAddr)); + nsAddr->GetNetAddr(&netAddr); + + nsCOMPtr<nsIOutputStream> outputStream; + mMessage->GetOutputStream(getter_AddRefs(outputStream)); + + FallibleTArray<uint8_t>& data = mMessage->GetDataAsTArray(); + + UDPSOCKET_LOG(("%s [this=%p], len %zu", __FUNCTION__, this, data.Length())); + nsCOMPtr<nsIUDPMessage> message = + new UDPMessageProxy(&netAddr, outputStream, std::move(data)); + mListener->OnPacketReceived(mSocket, message); + return NS_OK; +} + +NS_IMETHODIMP +SocketListenerProxyBackground::OnStopListeningRunnable::Run() { + mListener->OnStopListening(mSocket, mStatus); + return NS_OK; +} + +class PendingSend : public nsIDNSListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + PendingSend(nsUDPSocket* aSocket, uint16_t aPort, + FallibleTArray<uint8_t>&& aData) + : mSocket(aSocket), mPort(aPort), mData(std::move(aData)) {} + + private: + virtual ~PendingSend() = default; + + RefPtr<nsUDPSocket> mSocket; + uint16_t mPort; + FallibleTArray<uint8_t> mData; +}; + +NS_IMPL_ISUPPORTS(PendingSend, nsIDNSListener) + +NS_IMETHODIMP +PendingSend::OnLookupComplete(nsICancelable* request, nsIDNSRecord* aRecord, + nsresult status) { + if (NS_FAILED(status)) { + NS_WARNING("Failed to send UDP packet due to DNS lookup failure"); + return NS_OK; + } + + nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord); + MOZ_ASSERT(rec); + NetAddr addr; + if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) { + uint32_t count; + nsresult rv = mSocket->SendWithAddress(&addr, mData.Elements(), + mData.Length(), &count); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +class PendingSendStream : public nsIDNSListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + PendingSendStream(nsUDPSocket* aSocket, uint16_t aPort, + nsIInputStream* aStream) + : mSocket(aSocket), mPort(aPort), mStream(aStream) {} + + private: + virtual ~PendingSendStream() = default; + + RefPtr<nsUDPSocket> mSocket; + uint16_t mPort; + nsCOMPtr<nsIInputStream> mStream; +}; + +NS_IMPL_ISUPPORTS(PendingSendStream, nsIDNSListener) + +NS_IMETHODIMP +PendingSendStream::OnLookupComplete(nsICancelable* request, + nsIDNSRecord* aRecord, nsresult status) { + if (NS_FAILED(status)) { + NS_WARNING("Failed to send UDP packet due to DNS lookup failure"); + return NS_OK; + } + + nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord); + MOZ_ASSERT(rec); + NetAddr addr; + if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) { + nsresult rv = mSocket->SendBinaryStreamWithAddress(&addr, mStream); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +class SendRequestRunnable : public Runnable { + public: + SendRequestRunnable(nsUDPSocket* aSocket, const NetAddr& aAddr, + FallibleTArray<uint8_t>&& aData) + : Runnable("net::SendRequestRunnable"), + mSocket(aSocket), + mAddr(aAddr), + mData(std::move(aData)) {} + + NS_DECL_NSIRUNNABLE + + private: + RefPtr<nsUDPSocket> mSocket; + const NetAddr mAddr; + FallibleTArray<uint8_t> mData; +}; + +NS_IMETHODIMP +SendRequestRunnable::Run() { + uint32_t count; + mSocket->SendWithAddress(&mAddr, mData.Elements(), mData.Length(), &count); + return NS_OK; +} + +} // namespace + +NS_IMETHODIMP +nsUDPSocket::AsyncListen(nsIUDPSocketListener* aListener) { + // ensuring mFD implies ensuring mLock + NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(mSyncListener == nullptr, NS_ERROR_IN_PROGRESS); + { + MutexAutoLock lock(mLock); + mListenerTarget = GetCurrentSerialEventTarget(); + if (NS_IsMainThread()) { + // PNecko usage + mListener = new SocketListenerProxy(aListener); + } else { + // PBackground usage from dom/media/webrtc/transport + mListener = new SocketListenerProxyBackground(aListener); + } + } + return PostEvent(this, &nsUDPSocket::OnMsgAttach); +} + +NS_IMETHODIMP +nsUDPSocket::SyncListen(nsIUDPSocketSyncListener* aListener) { + // ensuring mFD implies ensuring mLock + NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(mSyncListener == nullptr, NS_ERROR_IN_PROGRESS); + + mSyncListener = aListener; + + return PostEvent(this, &nsUDPSocket::OnMsgAttach); +} + +NS_IMETHODIMP +nsUDPSocket::Send(const nsACString& aHost, uint16_t aPort, + const nsTArray<uint8_t>& aData, uint32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = 0; + + FallibleTArray<uint8_t> fallibleArray; + if (!fallibleArray.InsertElementsAt(0, aData, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<nsIDNSListener> listener = + new PendingSend(this, aPort, std::move(fallibleArray)); + + nsresult rv = ResolveHost(aHost, mOriginAttributes, listener); + NS_ENSURE_SUCCESS(rv, rv); + + *_retval = aData.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::SendWithAddr(nsINetAddr* aAddr, const nsTArray<uint8_t>& aData, + uint32_t* _retval) { + NS_ENSURE_ARG(aAddr); + NS_ENSURE_ARG_POINTER(_retval); + + NetAddr netAddr; + aAddr->GetNetAddr(&netAddr); + return SendWithAddress(&netAddr, aData.Elements(), aData.Length(), _retval); +} + +NS_IMETHODIMP +nsUDPSocket::SendWithAddress(const NetAddr* aAddr, const uint8_t* aData, + uint32_t aLength, uint32_t* _retval) { + NS_ENSURE_ARG(aAddr); + NS_ENSURE_ARG_POINTER(_retval); + + if (StaticPrefs::network_http_http3_block_loopback_ipv6_addr() && + aAddr->raw.family == AF_INET6 && aAddr->IsLoopbackAddr()) { + return NS_ERROR_CONNECTION_REFUSED; + } + + *_retval = 0; + + PRNetAddr prAddr; + NetAddrToPRNetAddr(aAddr, &prAddr); + + bool onSTSThread = false; + mSts->IsOnCurrentThread(&onSTSThread); + + if (onSTSThread) { + MutexAutoLock lock(mLock); + if (!mFD) { + // socket is not initialized or has been closed + return NS_ERROR_FAILURE; + } + int32_t count = + PR_SendTo(mFD, aData, aLength, 0, &prAddr, PR_INTERVAL_NO_WAIT); + if (count < 0) { + PRErrorCode code = PR_GetError(); + return ErrorAccordingToNSPR(code); + } + this->AddOutputBytes(count); + *_retval = count; + } else { + FallibleTArray<uint8_t> fallibleArray; + if (!fallibleArray.AppendElements(aData, aLength, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = mSts->Dispatch( + new SendRequestRunnable(this, *aAddr, std::move(fallibleArray)), + NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + *_retval = aLength; + } + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::SendBinaryStream(const nsACString& aHost, uint16_t aPort, + nsIInputStream* aStream) { + NS_ENSURE_ARG(aStream); + + nsCOMPtr<nsIDNSListener> listener = + new PendingSendStream(this, aPort, aStream); + + return ResolveHost(aHost, mOriginAttributes, listener); +} + +NS_IMETHODIMP +nsUDPSocket::SendBinaryStreamWithAddress(const NetAddr* aAddr, + nsIInputStream* aStream) { + NS_ENSURE_ARG(aAddr); + NS_ENSURE_ARG(aStream); + + PRNetAddr prAddr; + PR_InitializeNetAddr(PR_IpAddrAny, 0, &prAddr); + NetAddrToPRNetAddr(aAddr, &prAddr); + + RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prAddr); + return NS_AsyncCopy(aStream, os, mSts, NS_ASYNCCOPY_VIA_READSEGMENTS, + UDP_PACKET_CHUNK_SIZE); +} + +NS_IMETHODIMP +nsUDPSocket::RecvWithAddr(NetAddr* addr, nsTArray<uint8_t>& aData) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + PRNetAddr prAddr; + int32_t count; + char buff[9216]; + count = PR_RecvFrom(mFD, buff, sizeof(buff), 0, &prAddr, PR_INTERVAL_NO_WAIT); + if (count < 0) { + UDPSOCKET_LOG( + ("nsUDPSocket::RecvWithAddr: PR_RecvFrom failed [this=%p]\n", this)); + return NS_OK; + } + mByteReadCount += count; + profiler_count_bandwidth_read_bytes(count); + PRNetAddrToNetAddr(&prAddr, addr); + + if (!aData.AppendElements(buff, count, fallible)) { + UDPSOCKET_LOG(( + "nsUDPSocket::OnSocketReady: AppendElements FAILED [this=%p]\n", this)); + mCondition = NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult nsUDPSocket::SetSocketOption(const PRSocketOptionData& aOpt) { + bool onSTSThread = false; + mSts->IsOnCurrentThread(&onSTSThread); + + if (!onSTSThread) { + // Dispatch to STS thread and re-enter this method there + nsCOMPtr<nsIRunnable> runnable = new SetSocketOptionRunnable(this, aOpt); + nsresult rv = mSts->Dispatch(runnable, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (PR_SetSocketOption(mFD, &aOpt) != PR_SUCCESS) { + UDPSOCKET_LOG( + ("nsUDPSocket::SetSocketOption [this=%p] failed for type %d, " + "error %d\n", + this, aOpt.option, PR_GetError())); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::JoinMulticast(const nsACString& aAddr, const nsACString& aIface) { + if (NS_WARN_IF(aAddr.IsEmpty())) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRNetAddr prAddr; + if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + PRNetAddr prIface; + if (aIface.IsEmpty()) { + PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface); + } else { + if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) { + return NS_ERROR_FAILURE; + } + } + + return JoinMulticastInternal(prAddr, prIface); +} + +NS_IMETHODIMP +nsUDPSocket::JoinMulticastAddr(const NetAddr aAddr, const NetAddr* aIface) { + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRNetAddr prAddr; + NetAddrToPRNetAddr(&aAddr, &prAddr); + + PRNetAddr prIface; + if (!aIface) { + PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface); + } else { + NetAddrToPRNetAddr(aIface, &prIface); + } + + return JoinMulticastInternal(prAddr, prIface); +} + +nsresult nsUDPSocket::JoinMulticastInternal(const PRNetAddr& aAddr, + const PRNetAddr& aIface) { + PRSocketOptionData opt; + + opt.option = PR_SockOpt_AddMember; + opt.value.add_member.mcaddr = aAddr; + opt.value.add_member.ifaddr = aIface; + + nsresult rv = SetSocketOption(opt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::LeaveMulticast(const nsACString& aAddr, const nsACString& aIface) { + if (NS_WARN_IF(aAddr.IsEmpty())) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRNetAddr prAddr; + if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + PRNetAddr prIface; + if (aIface.IsEmpty()) { + PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface); + } else { + if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) { + return NS_ERROR_FAILURE; + } + } + + return LeaveMulticastInternal(prAddr, prIface); +} + +NS_IMETHODIMP +nsUDPSocket::LeaveMulticastAddr(const NetAddr aAddr, const NetAddr* aIface) { + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRNetAddr prAddr; + NetAddrToPRNetAddr(&aAddr, &prAddr); + + PRNetAddr prIface; + if (!aIface) { + PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface); + } else { + NetAddrToPRNetAddr(aIface, &prIface); + } + + return LeaveMulticastInternal(prAddr, prIface); +} + +nsresult nsUDPSocket::LeaveMulticastInternal(const PRNetAddr& aAddr, + const PRNetAddr& aIface) { + PRSocketOptionData opt; + + opt.option = PR_SockOpt_DropMember; + opt.value.drop_member.mcaddr = aAddr; + opt.value.drop_member.ifaddr = aIface; + + nsresult rv = SetSocketOption(opt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::GetMulticastLoopback(bool* aLoopback) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsUDPSocket::SetMulticastLoopback(bool aLoopback) { + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRSocketOptionData opt; + + opt.option = PR_SockOpt_McastLoopback; + opt.value.mcast_loopback = aLoopback; + + nsresult rv = SetSocketOption(opt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::GetRecvBufferSize(int* size) { + // Bug 1252759 - missing support for GetSocketOption + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsUDPSocket::SetRecvBufferSize(int size) { + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRSocketOptionData opt; + + opt.option = PR_SockOpt_RecvBufferSize; + opt.value.recv_buffer_size = size; + + nsresult rv = SetSocketOption(opt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::GetDontFragment(bool* dontFragment) { + // Bug 1252759 - missing support for GetSocketOption + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsUDPSocket::SetDontFragment(bool dontFragment) { + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRSocketOptionData opt; + opt.option = PR_SockOpt_DontFrag; + opt.value.dont_fragment = dontFragment; + + nsresult rv = SetSocketOption(opt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::GetSendBufferSize(int* size) { + // Bug 1252759 - missing support for GetSocketOption + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsUDPSocket::SetSendBufferSize(int size) { + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRSocketOptionData opt; + + opt.option = PR_SockOpt_SendBufferSize; + opt.value.send_buffer_size = size; + + nsresult rv = SetSocketOption(opt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::GetMulticastInterface(nsACString& aIface) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsUDPSocket::GetMulticastInterfaceAddr(NetAddr* aIface) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsUDPSocket::SetMulticastInterface(const nsACString& aIface) { + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRNetAddr prIface; + if (aIface.IsEmpty()) { + PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface); + } else { + if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) { + return NS_ERROR_FAILURE; + } + } + + return SetMulticastInterfaceInternal(prIface); +} + +NS_IMETHODIMP +nsUDPSocket::SetMulticastInterfaceAddr(NetAddr aIface) { + if (NS_WARN_IF(!mFD)) { + return NS_ERROR_NOT_INITIALIZED; + } + + PRNetAddr prIface; + NetAddrToPRNetAddr(&aIface, &prIface); + + return SetMulticastInterfaceInternal(prIface); +} + +nsresult nsUDPSocket::SetMulticastInterfaceInternal(const PRNetAddr& aIface) { + PRSocketOptionData opt; + + opt.option = PR_SockOpt_McastInterface; + opt.value.mcast_if = aIface; + + nsresult rv = SetSocketOption(opt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsUDPSocket.h b/netwerk/base/nsUDPSocket.h new file mode 100644 index 0000000000..bd616d5206 --- /dev/null +++ b/netwerk/base/nsUDPSocket.h @@ -0,0 +1,118 @@ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsUDPSocket_h__ +#define nsUDPSocket_h__ + +#include "nsIUDPSocket.h" +#include "mozilla/Mutex.h" +#include "mozilla/net/DNS.h" +#include "nsIOutputStream.h" +#include "nsASocketHandler.h" +#include "nsCycleCollectionParticipant.h" + +//----------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +class nsSocketTransportService; + +class nsUDPSocket final : public nsASocketHandler, public nsIUDPSocket { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUDPSOCKET + + // nsASocketHandler methods: + virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override; + virtual void OnSocketDetached(PRFileDesc* fd) override; + virtual void IsLocal(bool* aIsLocal) override; + virtual nsresult GetRemoteAddr(NetAddr* addr) override; + + uint64_t ByteCountSent() override { return mByteWriteCount; } + uint64_t ByteCountReceived() override { return mByteReadCount; } + + void AddOutputBytes(int32_t aBytes); + + nsUDPSocket(); + + private: + virtual ~nsUDPSocket(); + + void OnMsgClose(); + void OnMsgAttach(); + + // try attaching our socket (mFD) to the STS's poll list. + nsresult TryAttach(); + + friend class SetSocketOptionRunnable; + nsresult SetSocketOption(const PRSocketOptionData& aOpt); + nsresult JoinMulticastInternal(const PRNetAddr& aAddr, + const PRNetAddr& aIface); + nsresult LeaveMulticastInternal(const PRNetAddr& aAddr, + const PRNetAddr& aIface); + nsresult SetMulticastInterfaceInternal(const PRNetAddr& aIface); + + void CloseSocket(); + + // lock protects access to mListener; + // so mListener is not cleared while being used/locked. + Mutex mLock MOZ_UNANNOTATED{"nsUDPSocket.mLock"}; + PRFileDesc* mFD{nullptr}; + NetAddr mAddr; + OriginAttributes mOriginAttributes; + nsCOMPtr<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIUDPSocketSyncListener> mSyncListener; + nsCOMPtr<nsIEventTarget> mListenerTarget; + bool mAttached{false}; + RefPtr<nsSocketTransportService> mSts; + + uint64_t mByteReadCount{0}; + uint64_t mByteWriteCount{0}; +}; + +//----------------------------------------------------------------------------- + +class nsUDPMessage : public nsIUDPMessage { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsUDPMessage) + NS_DECL_NSIUDPMESSAGE + + nsUDPMessage(NetAddr* aAddr, nsIOutputStream* aOutputStream, + FallibleTArray<uint8_t>&& aData); + + private: + virtual ~nsUDPMessage(); + + NetAddr mAddr; + nsCOMPtr<nsIOutputStream> mOutputStream; + FallibleTArray<uint8_t> mData; + JS::Heap<JSObject*> mJsobj; +}; + +//----------------------------------------------------------------------------- + +class nsUDPOutputStream : public nsIOutputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + + nsUDPOutputStream(nsUDPSocket* aSocket, PRFileDesc* aFD, + PRNetAddr& aPrClientAddr); + + private: + virtual ~nsUDPOutputStream() = default; + + RefPtr<nsUDPSocket> mSocket; + PRFileDesc* mFD; + PRNetAddr mPrClientAddr; + bool mIsClosed; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsUDPSocket_h__ diff --git a/netwerk/base/nsURIHashKey.h b/netwerk/base/nsURIHashKey.h new file mode 100644 index 0000000000..fb98ae0d21 --- /dev/null +++ b/netwerk/base/nsURIHashKey.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsURIHashKey_h__ +#define nsURIHashKey_h__ + +#include <utility> + +#include "PLDHashTable.h" +#include "mozilla/Unused.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsIURI.h" + +/** + * Hashtable key class to use with nsTHashtable/nsBaseHashtable + */ +class nsURIHashKey : public PLDHashEntryHdr { + public: + typedef nsIURI* KeyType; + typedef const nsIURI* KeyTypePointer; + + nsURIHashKey() { MOZ_COUNT_CTOR(nsURIHashKey); } + explicit nsURIHashKey(const nsIURI* aKey) : mKey(const_cast<nsIURI*>(aKey)) { + MOZ_COUNT_CTOR(nsURIHashKey); + } + nsURIHashKey(nsURIHashKey&& toMove) + : PLDHashEntryHdr(std::move(toMove)), mKey(std::move(toMove.mKey)) { + MOZ_COUNT_CTOR(nsURIHashKey); + } + MOZ_COUNTED_DTOR(nsURIHashKey) + + nsURIHashKey& operator=(const nsURIHashKey& aOther) { + mKey = aOther.mKey; + return *this; + } + + nsIURI* GetKey() const { return mKey; } + + bool KeyEquals(const nsIURI* aKey) const { + bool eq; + if (!mKey) { + return !aKey; + } + if (NS_SUCCEEDED(mKey->Equals(const_cast<nsIURI*>(aKey), &eq))) { + return eq; + } + return false; + } + + static const nsIURI* KeyToPointer(nsIURI* aKey) { return aKey; } + static PLDHashNumber HashKey(const nsIURI* aKey) { + if (!aKey) { + // If the key is null, return hash for empty string. + return mozilla::HashString(""_ns); + } + nsAutoCString spec; + // If GetSpec() fails, ignoring the failure and proceeding with an + // empty |spec| seems like the best thing to do. + mozilla::Unused << const_cast<nsIURI*>(aKey)->GetSpec(spec); + return mozilla::HashString(spec); + } + + enum { ALLOW_MEMMOVE = true }; + + protected: + nsCOMPtr<nsIURI> mKey; +}; + +#endif // nsURIHashKey_h__ diff --git a/netwerk/base/nsURLHelper.cpp b/netwerk/base/nsURLHelper.cpp new file mode 100644 index 0000000000..3850c6865a --- /dev/null +++ b/netwerk/base/nsURLHelper.cpp @@ -0,0 +1,1356 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsURLHelper.h" + +#include "mozilla/Encoding.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/TextUtils.h" + +#include <algorithm> +#include <iterator> + +#include "nsASCIIMask.h" +#include "nsIFile.h" +#include "nsIURLParser.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "nsNetCID.h" +#include "mozilla/Preferences.h" +#include "prnetdb.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Tokenizer.h" +#include "nsEscape.h" +#include "nsDOMString.h" +#include "mozilla/net/rust_helper.h" +#include "mozilla/net/DNS.h" + +using namespace mozilla; + +//---------------------------------------------------------------------------- +// Init/Shutdown +//---------------------------------------------------------------------------- + +static bool gInitialized = false; +static StaticRefPtr<nsIURLParser> gNoAuthURLParser; +static StaticRefPtr<nsIURLParser> gAuthURLParser; +static StaticRefPtr<nsIURLParser> gStdURLParser; + +static void InitGlobals() { + nsCOMPtr<nsIURLParser> parser; + + parser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID); + NS_ASSERTION(parser, "failed getting 'noauth' url parser"); + if (parser) { + gNoAuthURLParser = parser; + } + + parser = do_GetService(NS_AUTHURLPARSER_CONTRACTID); + NS_ASSERTION(parser, "failed getting 'auth' url parser"); + if (parser) { + gAuthURLParser = parser; + } + + parser = do_GetService(NS_STDURLPARSER_CONTRACTID); + NS_ASSERTION(parser, "failed getting 'std' url parser"); + if (parser) { + gStdURLParser = parser; + } + + gInitialized = true; +} + +void net_ShutdownURLHelper() { + if (gInitialized) { + gInitialized = false; + } + gNoAuthURLParser = nullptr; + gAuthURLParser = nullptr; + gStdURLParser = nullptr; +} + +//---------------------------------------------------------------------------- +// nsIURLParser getters +//---------------------------------------------------------------------------- + +nsIURLParser* net_GetAuthURLParser() { + if (!gInitialized) InitGlobals(); + return gAuthURLParser; +} + +nsIURLParser* net_GetNoAuthURLParser() { + if (!gInitialized) InitGlobals(); + return gNoAuthURLParser; +} + +nsIURLParser* net_GetStdURLParser() { + if (!gInitialized) InitGlobals(); + return gStdURLParser; +} + +//--------------------------------------------------------------------------- +// GetFileFromURLSpec implementations +//--------------------------------------------------------------------------- +nsresult net_GetURLSpecFromDir(nsIFile* aFile, nsACString& result) { + nsAutoCString escPath; + nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath); + if (NS_FAILED(rv)) return rv; + + if (escPath.Last() != '/') { + escPath += '/'; + } + + result = escPath; + return NS_OK; +} + +nsresult net_GetURLSpecFromFile(nsIFile* aFile, nsACString& result) { + nsAutoCString escPath; + nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath); + if (NS_FAILED(rv)) return rv; + + // if this file references a directory, then we need to ensure that the + // URL ends with a slash. this is important since it affects the rules + // for relative URL resolution when this URL is used as a base URL. + // if the file does not exist, then we make no assumption about its type, + // and simply leave the URL unmodified. + if (escPath.Last() != '/') { + bool dir; + rv = aFile->IsDirectory(&dir); + if (NS_SUCCEEDED(rv) && dir) escPath += '/'; + } + + result = escPath; + return NS_OK; +} + +//---------------------------------------------------------------------------- +// file:// URL parsing +//---------------------------------------------------------------------------- + +nsresult net_ParseFileURL(const nsACString& inURL, nsACString& outDirectory, + nsACString& outFileBaseName, + nsACString& outFileExtension) { + nsresult rv; + + if (inURL.Length() > + (uint32_t)StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + outDirectory.Truncate(); + outFileBaseName.Truncate(); + outFileExtension.Truncate(); + + const nsPromiseFlatCString& flatURL = PromiseFlatCString(inURL); + const char* url = flatURL.get(); + + nsAutoCString scheme; + rv = net_ExtractURLScheme(flatURL, scheme); + if (NS_FAILED(rv)) return rv; + + if (!scheme.EqualsLiteral("file")) { + NS_ERROR("must be a file:// url"); + return NS_ERROR_UNEXPECTED; + } + + nsIURLParser* parser = net_GetNoAuthURLParser(); + NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED); + + uint32_t pathPos, filepathPos, directoryPos, basenamePos, extensionPos; + int32_t pathLen, filepathLen, directoryLen, basenameLen, extensionLen; + + // invoke the parser to extract the URL path + rv = parser->ParseURL(url, flatURL.Length(), nullptr, + nullptr, // don't care about scheme + nullptr, nullptr, // don't care about authority + &pathPos, &pathLen); + if (NS_FAILED(rv)) return rv; + + // invoke the parser to extract filepath from the path + rv = parser->ParsePath(url + pathPos, pathLen, &filepathPos, &filepathLen, + nullptr, nullptr, // don't care about query + nullptr, nullptr); // don't care about ref + if (NS_FAILED(rv)) return rv; + + filepathPos += pathPos; + + // invoke the parser to extract the directory and filename from filepath + rv = parser->ParseFilePath(url + filepathPos, filepathLen, &directoryPos, + &directoryLen, &basenamePos, &basenameLen, + &extensionPos, &extensionLen); + if (NS_FAILED(rv)) return rv; + + if (directoryLen > 0) { + outDirectory = Substring(inURL, filepathPos + directoryPos, directoryLen); + } + if (basenameLen > 0) { + outFileBaseName = Substring(inURL, filepathPos + basenamePos, basenameLen); + } + if (extensionLen > 0) { + outFileExtension = + Substring(inURL, filepathPos + extensionPos, extensionLen); + } + // since we are using a no-auth url parser, there will never be a host + // XXX not strictly true... file://localhost/foo/bar.html is a valid URL + + return NS_OK; +} + +//---------------------------------------------------------------------------- +// path manipulation functions +//---------------------------------------------------------------------------- + +// Replace all /./ with a / while resolving URLs +// But only till #? +void net_CoalesceDirs(netCoalesceFlags flags, char* path) { + /* Stolen from the old netlib's mkparse.c. + * + * modifies a url of the form /foo/../foo1 -> /foo1 + * and /foo/./foo1 -> /foo/foo1 + * and /foo/foo1/.. -> /foo/ + */ + char* fwdPtr = path; + char* urlPtr = path; + char* lastslash = path; + uint32_t traversal = 0; + uint32_t special_ftp_len = 0; + + /* Remember if this url is a special ftp one: */ + if (flags & NET_COALESCE_DOUBLE_SLASH_IS_ROOT) { + /* some schemes (for example ftp) have the speciality that + the path can begin // or /%2F to mark the root of the + servers filesystem, a simple / only marks the root relative + to the user loging in. We remember the length of the marker */ + if (nsCRT::strncasecmp(path, "/%2F", 4) == 0) { + special_ftp_len = 4; + } else if (strncmp(path, "//", 2) == 0) { + special_ftp_len = 2; + } + } + + /* find the last slash before # or ? */ + for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#'); ++fwdPtr) { + } + + /* found nothing, but go back one only */ + /* if there is something to go back to */ + if (fwdPtr != path && *fwdPtr == '\0') { + --fwdPtr; + } + + /* search the slash */ + for (; (fwdPtr != path) && (*fwdPtr != '/'); --fwdPtr) { + } + lastslash = fwdPtr; + fwdPtr = path; + + /* replace all %2E or %2e with . in the path */ + /* but stop at lastchar if non null */ + for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#') && + (*lastslash == '\0' || fwdPtr != lastslash); + ++fwdPtr) { + if (*fwdPtr == '%' && *(fwdPtr + 1) == '2' && + (*(fwdPtr + 2) == 'E' || *(fwdPtr + 2) == 'e')) { + *urlPtr++ = '.'; + ++fwdPtr; + ++fwdPtr; + } else { + *urlPtr++ = *fwdPtr; + } + } + // Copy remaining stuff past the #?; + for (; *fwdPtr != '\0'; ++fwdPtr) { + *urlPtr++ = *fwdPtr; + } + *urlPtr = '\0'; // terminate the url + + // start again, this time for real + fwdPtr = path; + urlPtr = path; + + for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#'); ++fwdPtr) { + if (*fwdPtr == '/' && *(fwdPtr + 1) == '.' && *(fwdPtr + 2) == '/') { + // remove . followed by slash + ++fwdPtr; + } else if (*fwdPtr == '/' && *(fwdPtr + 1) == '.' && *(fwdPtr + 2) == '.' && + (*(fwdPtr + 3) == '/' || + *(fwdPtr + 3) == '\0' || // This will take care of + *(fwdPtr + 3) == '?' || // something like foo/bar/..#sometag + *(fwdPtr + 3) == '#')) { + // remove foo/.. + // reverse the urlPtr to the previous slash if possible + // if url does not allow relative root then drop .. above root + // otherwise retain them in the path + if (traversal > 0 || !(flags & NET_COALESCE_ALLOW_RELATIVE_ROOT)) { + if (urlPtr != path) urlPtr--; // we must be going back at least by one + for (; *urlPtr != '/' && urlPtr != path; urlPtr--) { + ; // null body + } + --traversal; // count back + // forward the fwdPtr past the ../ + fwdPtr += 2; + // if we have reached the beginning of the path + // while searching for the previous / and we remember + // that it is an url that begins with /%2F then + // advance urlPtr again by 3 chars because /%2F already + // marks the root of the path + if (urlPtr == path && special_ftp_len > 3) { + ++urlPtr; + ++urlPtr; + ++urlPtr; + } + // special case if we have reached the end + // to preserve the last / + if (*fwdPtr == '.' && *(fwdPtr + 1) == '\0') ++urlPtr; + } else { + // there are to much /.. in this path, just copy them instead. + // forward the urlPtr past the /.. and copying it + + // However if we remember it is an url that starts with + // /%2F and urlPtr just points at the "F" of "/%2F" then do + // not overwrite it with the /, just copy .. and move forward + // urlPtr. + if (special_ftp_len > 3 && urlPtr == path + special_ftp_len - 1) { + ++urlPtr; + } else { + *urlPtr++ = *fwdPtr; + } + ++fwdPtr; + *urlPtr++ = *fwdPtr; + ++fwdPtr; + *urlPtr++ = *fwdPtr; + } + } else { + // count the hierachie, but only if we do not have reached + // the root of some special urls with a special root marker + if (*fwdPtr == '/' && *(fwdPtr + 1) != '.' && + (special_ftp_len != 2 || *(fwdPtr + 1) != '/')) { + traversal++; + } + // copy the url incrementaly + *urlPtr++ = *fwdPtr; + } + } + + /* + * Now lets remove trailing . case + * /foo/foo1/. -> /foo/foo1/ + */ + + if ((urlPtr > (path + 1)) && (*(urlPtr - 1) == '.') && + (*(urlPtr - 2) == '/')) { + urlPtr--; + } + + // Copy remaining stuff past the #?; + for (; *fwdPtr != '\0'; ++fwdPtr) { + *urlPtr++ = *fwdPtr; + } + *urlPtr = '\0'; // terminate the url +} + +//---------------------------------------------------------------------------- +// scheme fu +//---------------------------------------------------------------------------- + +static bool net_IsValidSchemeChar(const char aChar) { + return mozilla::net::rust_net_is_valid_scheme_char(aChar); +} + +/* Extract URI-Scheme if possible */ +nsresult net_ExtractURLScheme(const nsACString& inURI, nsACString& scheme) { + nsACString::const_iterator start, end; + inURI.BeginReading(start); + inURI.EndReading(end); + + // Strip C0 and space from begining + while (start != end) { + if ((uint8_t)*start > 0x20) { + break; + } + start++; + } + + Tokenizer p(Substring(start, end), "\r\n\t"); + p.Record(); + if (!p.CheckChar(IsAsciiAlpha)) { + // First char must be alpha + return NS_ERROR_MALFORMED_URI; + } + + while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) { + // Skip valid scheme characters or \r\n\t + } + + if (!p.CheckChar(':')) { + return NS_ERROR_MALFORMED_URI; + } + + p.Claim(scheme); + scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab()); + ToLowerCase(scheme); + return NS_OK; +} + +bool net_IsValidScheme(const nsACString& scheme) { + return mozilla::net::rust_net_is_valid_scheme(&scheme); +} + +bool net_IsAbsoluteURL(const nsACString& uri) { + nsACString::const_iterator start, end; + uri.BeginReading(start); + uri.EndReading(end); + + // Strip C0 and space from begining + while (start != end) { + if ((uint8_t)*start > 0x20) { + break; + } + start++; + } + + Tokenizer p(Substring(start, end), "\r\n\t"); + + // First char must be alpha + if (!p.CheckChar(IsAsciiAlpha)) { + return false; + } + + while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) { + // Skip valid scheme characters or \r\n\t + } + if (!p.CheckChar(':')) { + return false; + } + p.SkipWhites(); + + if (!p.CheckChar('/')) { + return false; + } + p.SkipWhites(); + + if (p.CheckChar('/')) { + // aSpec is really absolute. Ignore aBaseURI in this case + return true; + } + return false; +} + +void net_FilterURIString(const nsACString& input, nsACString& result) { + result.Truncate(); + + const auto* start = input.BeginReading(); + const auto* end = input.EndReading(); + + // Trim off leading and trailing invalid chars. + auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; }; + const auto* newStart = std::find_if(start, end, charFilter); + const auto* newEnd = + std::find_if(std::reverse_iterator<decltype(end)>(end), + std::reverse_iterator<decltype(newStart)>(newStart), + charFilter) + .base(); + + // Check if chars need to be stripped. + bool needsStrip = false; + const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab(); + for (const auto* itr = start; itr != end; ++itr) { + if (ASCIIMask::IsMasked(mask, *itr)) { + needsStrip = true; + break; + } + } + + // Just use the passed in string rather than creating new copies if no + // changes are necessary. + if (newStart == start && newEnd == end && !needsStrip) { + result = input; + return; + } + + result.Assign(Substring(newStart, newEnd)); + if (needsStrip) { + result.StripTaggedASCII(mask); + } +} + +nsresult net_FilterAndEscapeURI(const nsACString& aInput, uint32_t aFlags, + const ASCIIMaskArray& aFilterMask, + nsACString& aResult) { + aResult.Truncate(); + + const auto* start = aInput.BeginReading(); + const auto* end = aInput.EndReading(); + + // Trim off leading and trailing invalid chars. + auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; }; + const auto* newStart = std::find_if(start, end, charFilter); + const auto* newEnd = + std::find_if(std::reverse_iterator<decltype(end)>(end), + std::reverse_iterator<decltype(newStart)>(newStart), + charFilter) + .base(); + + return NS_EscapeAndFilterURL(Substring(newStart, newEnd), aFlags, + &aFilterMask, aResult, fallible); +} + +#if defined(XP_WIN) +bool net_NormalizeFileURL(const nsACString& aURL, nsCString& aResultBuf) { + bool writing = false; + + nsACString::const_iterator beginIter, endIter; + aURL.BeginReading(beginIter); + aURL.EndReading(endIter); + + const char *s, *begin = beginIter.get(); + + for (s = begin; s != endIter.get(); ++s) { + if (*s == '\\') { + writing = true; + if (s > begin) aResultBuf.Append(begin, s - begin); + aResultBuf += '/'; + begin = s + 1; + } + if (*s == '#') { + // Don't normalize any backslashes following the hash. + s = endIter.get(); + break; + } + } + if (writing && s > begin) aResultBuf.Append(begin, s - begin); + + return writing; +} +#endif + +//---------------------------------------------------------------------------- +// miscellaneous (i.e., stuff that should really be elsewhere) +//---------------------------------------------------------------------------- + +static inline void ToLower(char& c) { + if ((unsigned)(c - 'A') <= (unsigned)('Z' - 'A')) c += 'a' - 'A'; +} + +void net_ToLowerCase(char* str, uint32_t length) { + for (char* end = str + length; str < end; ++str) ToLower(*str); +} + +void net_ToLowerCase(char* str) { + for (; *str; ++str) ToLower(*str); +} + +char* net_FindCharInSet(const char* iter, const char* stop, const char* set) { + for (; iter != stop && *iter; ++iter) { + for (const char* s = set; *s; ++s) { + if (*iter == *s) return (char*)iter; + } + } + return (char*)iter; +} + +char* net_FindCharNotInSet(const char* iter, const char* stop, + const char* set) { +repeat: + for (const char* s = set; *s; ++s) { + if (*iter == *s) { + if (++iter == stop) break; + goto repeat; + } + } + return (char*)iter; +} + +char* net_RFindCharNotInSet(const char* stop, const char* iter, + const char* set) { + --iter; + --stop; + + if (iter == stop) return (char*)iter; + +repeat: + for (const char* s = set; *s; ++s) { + if (*iter == *s) { + if (--iter == stop) break; + goto repeat; + } + } + return (char*)iter; +} + +#define HTTP_LWS " \t" + +// Return the index of the closing quote of the string, if any +static uint32_t net_FindStringEnd(const nsCString& flatStr, + uint32_t stringStart, char stringDelim) { + NS_ASSERTION(stringStart < flatStr.Length() && + flatStr.CharAt(stringStart) == stringDelim && + (stringDelim == '"' || stringDelim == '\''), + "Invalid stringStart"); + + const char set[] = {stringDelim, '\\', '\0'}; + do { + // stringStart points to either the start quote or the last + // escaped char (the char following a '\\') + + // Write to searchStart here, so that when we get back to the + // top of the loop right outside this one we search from the + // right place. + uint32_t stringEnd = flatStr.FindCharInSet(set, stringStart + 1); + if (stringEnd == uint32_t(kNotFound)) return flatStr.Length(); + + if (flatStr.CharAt(stringEnd) == '\\') { + // Hit a backslash-escaped char. Need to skip over it. + stringStart = stringEnd + 1; + if (stringStart == flatStr.Length()) return stringStart; + + // Go back to looking for the next escape or the string end + continue; + } + + return stringEnd; + + } while (true); + + MOZ_ASSERT_UNREACHABLE("How did we get here?"); + return flatStr.Length(); +} + +static uint32_t net_FindMediaDelimiter(const nsCString& flatStr, + uint32_t searchStart, char delimiter) { + do { + // searchStart points to the spot from which we should start looking + // for the delimiter. + const char delimStr[] = {delimiter, '"', '\0'}; + uint32_t curDelimPos = flatStr.FindCharInSet(delimStr, searchStart); + if (curDelimPos == uint32_t(kNotFound)) return flatStr.Length(); + + char ch = flatStr.CharAt(curDelimPos); + if (ch == delimiter) { + // Found delimiter + return curDelimPos; + } + + // We hit the start of a quoted string. Look for its end. + searchStart = net_FindStringEnd(flatStr, curDelimPos, ch); + if (searchStart == flatStr.Length()) return searchStart; + + ++searchStart; + + // searchStart now points to the first char after the end of the + // string, so just go back to the top of the loop and look for + // |delimiter| again. + } while (true); + + MOZ_ASSERT_UNREACHABLE("How did we get here?"); + return flatStr.Length(); +} + +// aOffset should be added to aCharsetStart and aCharsetEnd if this +// function sets them. +static void net_ParseMediaType(const nsACString& aMediaTypeStr, + nsACString& aContentType, + nsACString& aContentCharset, int32_t aOffset, + bool* aHadCharset, int32_t* aCharsetStart, + int32_t* aCharsetEnd, bool aStrict) { + const nsCString& flatStr = PromiseFlatCString(aMediaTypeStr); + const char* start = flatStr.get(); + const char* end = start + flatStr.Length(); + + // Trim LWS leading and trailing whitespace from type. + const char* type = net_FindCharNotInSet(start, end, HTTP_LWS); + const char* typeEnd = net_FindCharInSet(type, end, HTTP_LWS ";"); + + const char* charset = ""; + const char* charsetEnd = charset; + int32_t charsetParamStart = 0; + int32_t charsetParamEnd = 0; + + uint32_t consumed = typeEnd - type; + + // Iterate over parameters + bool typeHasCharset = false; + uint32_t paramStart = flatStr.FindChar(';', typeEnd - start); + if (paramStart != uint32_t(kNotFound)) { + // We have parameters. Iterate over them. + uint32_t curParamStart = paramStart + 1; + do { + uint32_t curParamEnd = + net_FindMediaDelimiter(flatStr, curParamStart, ';'); + + const char* paramName = net_FindCharNotInSet( + start + curParamStart, start + curParamEnd, HTTP_LWS); + static const char charsetStr[] = "charset="; + if (nsCRT::strncasecmp(paramName, charsetStr, sizeof(charsetStr) - 1) == + 0) { + charset = paramName + sizeof(charsetStr) - 1; + charsetEnd = start + curParamEnd; + typeHasCharset = true; + charsetParamStart = curParamStart - 1; + charsetParamEnd = curParamEnd; + } + + consumed = curParamEnd; + curParamStart = curParamEnd + 1; + } while (curParamStart < flatStr.Length()); + } + + bool charsetNeedsQuotedStringUnescaping = false; + if (typeHasCharset) { + // Trim LWS leading and trailing whitespace from charset. + charset = net_FindCharNotInSet(charset, charsetEnd, HTTP_LWS); + if (*charset == '"') { + charsetNeedsQuotedStringUnescaping = true; + charsetEnd = + start + net_FindStringEnd(flatStr, charset - start, *charset); + charset++; + NS_ASSERTION(charsetEnd >= charset, "Bad charset parsing"); + } else { + charsetEnd = net_FindCharInSet(charset, charsetEnd, HTTP_LWS ";"); + } + } + + // if the server sent "*/*", it is meaningless, so do not store it. + // also, if type is the same as aContentType, then just update the + // charset. however, if charset is empty and aContentType hasn't + // changed, then don't wipe-out an existing aContentCharset. We + // also want to reject a mime-type if it does not include a slash. + // some servers give junk after the charset parameter, which may + // include a comma, so this check makes us a bit more tolerant. + + if (type != typeEnd && memchr(type, '/', typeEnd - type) != nullptr && + (aStrict ? (net_FindCharNotInSet(start + consumed, end, HTTP_LWS) == end) + : (strncmp(type, "*/*", typeEnd - type) != 0))) { + // Common case here is that aContentType is empty + bool eq = !aContentType.IsEmpty() && + aContentType.Equals(Substring(type, typeEnd), + nsCaseInsensitiveCStringComparator); + if (!eq) { + aContentType.Assign(type, typeEnd - type); + ToLowerCase(aContentType); + } + + if ((!eq && *aHadCharset) || typeHasCharset) { + *aHadCharset = true; + if (charsetNeedsQuotedStringUnescaping) { + // parameters using the "quoted-string" syntax need + // backslash-escapes to be unescaped (see RFC 2616 Section 2.2) + aContentCharset.Truncate(); + for (const char* c = charset; c != charsetEnd; c++) { + if (*c == '\\' && c + 1 != charsetEnd) { + // eat escape + c++; + } + aContentCharset.Append(*c); + } + } else { + aContentCharset.Assign(charset, charsetEnd - charset); + } + if (typeHasCharset) { + *aCharsetStart = charsetParamStart + aOffset; + *aCharsetEnd = charsetParamEnd + aOffset; + } + } + // Only set a new charset position if this is a different type + // from the last one we had and it doesn't already have a + // charset param. If this is the same type, we probably want + // to leave the charset position on its first occurrence. + if (!eq && !typeHasCharset) { + int32_t charsetStart = int32_t(paramStart); + if (charsetStart == kNotFound) charsetStart = flatStr.Length(); + + *aCharsetEnd = *aCharsetStart = charsetStart + aOffset; + } + } +} + +#undef HTTP_LWS + +void net_ParseContentType(const nsACString& aHeaderStr, + nsACString& aContentType, nsACString& aContentCharset, + bool* aHadCharset) { + int32_t dummy1, dummy2; + net_ParseContentType(aHeaderStr, aContentType, aContentCharset, aHadCharset, + &dummy1, &dummy2); +} + +void net_ParseContentType(const nsACString& aHeaderStr, + nsACString& aContentType, nsACString& aContentCharset, + bool* aHadCharset, int32_t* aCharsetStart, + int32_t* aCharsetEnd) { + // + // Augmented BNF (from RFC 2616 section 3.7): + // + // header-value = media-type *( LWS "," LWS media-type ) + // media-type = type "/" subtype *( LWS ";" LWS parameter ) + // type = token + // subtype = token + // parameter = attribute "=" value + // attribute = token + // value = token | quoted-string + // + // + // Examples: + // + // text/html + // text/html, text/html + // text/html,text/html; charset=ISO-8859-1 + // text/html,text/html; charset="ISO-8859-1" + // text/html;charset=ISO-8859-1, text/html + // text/html;charset='ISO-8859-1', text/html + // application/octet-stream + // + + *aHadCharset = false; + const nsCString& flatStr = PromiseFlatCString(aHeaderStr); + + // iterate over media-types. Note that ',' characters can happen + // inside quoted strings, so we need to watch out for that. + uint32_t curTypeStart = 0; + do { + // curTypeStart points to the start of the current media-type. We want + // to look for its end. + uint32_t curTypeEnd = net_FindMediaDelimiter(flatStr, curTypeStart, ','); + + // At this point curTypeEnd points to the spot where the media-type + // starting at curTypeEnd ends. Time to parse that! + net_ParseMediaType( + Substring(flatStr, curTypeStart, curTypeEnd - curTypeStart), + aContentType, aContentCharset, curTypeStart, aHadCharset, aCharsetStart, + aCharsetEnd, false); + + // And let's move on to the next media-type + curTypeStart = curTypeEnd + 1; + } while (curTypeStart < flatStr.Length()); +} + +void net_ParseRequestContentType(const nsACString& aHeaderStr, + nsACString& aContentType, + nsACString& aContentCharset, + bool* aHadCharset) { + // + // Augmented BNF (from RFC 7231 section 3.1.1.1): + // + // media-type = type "/" subtype *( OWS ";" OWS parameter ) + // type = token + // subtype = token + // parameter = token "=" ( token / quoted-string ) + // + // Examples: + // + // text/html + // text/html; charset=ISO-8859-1 + // text/html; charset="ISO-8859-1" + // application/octet-stream + // + + aContentType.Truncate(); + aContentCharset.Truncate(); + *aHadCharset = false; + const nsCString& flatStr = PromiseFlatCString(aHeaderStr); + + // At this point curTypeEnd points to the spot where the media-type + // starting at curTypeEnd ends. Time to parse that! + nsAutoCString contentType, contentCharset; + bool hadCharset = false; + int32_t dummy1, dummy2; + uint32_t typeEnd = net_FindMediaDelimiter(flatStr, 0, ','); + if (typeEnd != flatStr.Length()) { + // We have some stuff left at the end, so this is not a valid + // request Content-Type header. + return; + } + net_ParseMediaType(flatStr, contentType, contentCharset, 0, &hadCharset, + &dummy1, &dummy2, true); + + aContentType = contentType; + aContentCharset = contentCharset; + *aHadCharset = hadCharset; +} + +bool net_IsValidHostName(const nsACString& host) { + // The host name is limited to 253 ascii characters. + if (host.Length() > 253) { + return false; + } + + const char* end = host.EndReading(); + // Use explicit whitelists to select which characters we are + // willing to send to lower-level DNS logic. This is more + // self-documenting, and can also be slightly faster than the + // blacklist approach, since DNS names are the common case, and + // the commonest characters will tend to be near the start of + // the list. + + // Whitelist for DNS names (RFC 1035) with extra characters added + // for pragmatic reasons "$+_" + // see https://bugzilla.mozilla.org/show_bug.cgi?id=355181#c2 + if (net_FindCharNotInSet(host.BeginReading(), end, + "abcdefghijklmnopqrstuvwxyz" + ".-0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ$+_") == end) { + return true; + } + + // Might be a valid IPv6 link-local address containing a percent sign + return mozilla::net::HostIsIPLiteral(host); +} + +bool net_IsValidIPv4Addr(const nsACString& aAddr) { + return mozilla::net::rust_net_is_valid_ipv4_addr(&aAddr); +} + +bool net_IsValidIPv6Addr(const nsACString& aAddr) { + return mozilla::net::rust_net_is_valid_ipv6_addr(&aAddr); +} + +bool net_GetDefaultStatusTextForCode(uint16_t aCode, nsACString& aOutText) { + switch (aCode) { + // start with the most common + case 200: + aOutText.AssignLiteral("OK"); + break; + case 404: + aOutText.AssignLiteral("Not Found"); + break; + case 301: + aOutText.AssignLiteral("Moved Permanently"); + break; + case 304: + aOutText.AssignLiteral("Not Modified"); + break; + case 307: + aOutText.AssignLiteral("Temporary Redirect"); + break; + case 500: + aOutText.AssignLiteral("Internal Server Error"); + break; + + // also well known + case 100: + aOutText.AssignLiteral("Continue"); + break; + case 101: + aOutText.AssignLiteral("Switching Protocols"); + break; + case 201: + aOutText.AssignLiteral("Created"); + break; + case 202: + aOutText.AssignLiteral("Accepted"); + break; + case 203: + aOutText.AssignLiteral("Non Authoritative"); + break; + case 204: + aOutText.AssignLiteral("No Content"); + break; + case 205: + aOutText.AssignLiteral("Reset Content"); + break; + case 206: + aOutText.AssignLiteral("Partial Content"); + break; + case 207: + aOutText.AssignLiteral("Multi-Status"); + break; + case 208: + aOutText.AssignLiteral("Already Reported"); + break; + case 300: + aOutText.AssignLiteral("Multiple Choices"); + break; + case 302: + aOutText.AssignLiteral("Found"); + break; + case 303: + aOutText.AssignLiteral("See Other"); + break; + case 305: + aOutText.AssignLiteral("Use Proxy"); + break; + case 308: + aOutText.AssignLiteral("Permanent Redirect"); + break; + case 400: + aOutText.AssignLiteral("Bad Request"); + break; + case 401: + aOutText.AssignLiteral("Unauthorized"); + break; + case 402: + aOutText.AssignLiteral("Payment Required"); + break; + case 403: + aOutText.AssignLiteral("Forbidden"); + break; + case 405: + aOutText.AssignLiteral("Method Not Allowed"); + break; + case 406: + aOutText.AssignLiteral("Not Acceptable"); + break; + case 407: + aOutText.AssignLiteral("Proxy Authentication Required"); + break; + case 408: + aOutText.AssignLiteral("Request Timeout"); + break; + case 409: + aOutText.AssignLiteral("Conflict"); + break; + case 410: + aOutText.AssignLiteral("Gone"); + break; + case 411: + aOutText.AssignLiteral("Length Required"); + break; + case 412: + aOutText.AssignLiteral("Precondition Failed"); + break; + case 413: + aOutText.AssignLiteral("Request Entity Too Large"); + break; + case 414: + aOutText.AssignLiteral("Request URI Too Long"); + break; + case 415: + aOutText.AssignLiteral("Unsupported Media Type"); + break; + case 416: + aOutText.AssignLiteral("Requested Range Not Satisfiable"); + break; + case 417: + aOutText.AssignLiteral("Expectation Failed"); + break; + case 418: + aOutText.AssignLiteral("I'm a teapot"); + break; + case 421: + aOutText.AssignLiteral("Misdirected Request"); + break; + case 422: + aOutText.AssignLiteral("Unprocessable Entity"); + break; + case 423: + aOutText.AssignLiteral("Locked"); + break; + case 424: + aOutText.AssignLiteral("Failed Dependency"); + break; + case 425: + aOutText.AssignLiteral("Too Early"); + break; + case 426: + aOutText.AssignLiteral("Upgrade Required"); + break; + case 428: + aOutText.AssignLiteral("Precondition Required"); + break; + case 429: + aOutText.AssignLiteral("Too Many Requests"); + break; + case 431: + aOutText.AssignLiteral("Request Header Fields Too Large"); + break; + case 451: + aOutText.AssignLiteral("Unavailable For Legal Reasons"); + break; + case 501: + aOutText.AssignLiteral("Not Implemented"); + break; + case 502: + aOutText.AssignLiteral("Bad Gateway"); + break; + case 503: + aOutText.AssignLiteral("Service Unavailable"); + break; + case 504: + aOutText.AssignLiteral("Gateway Timeout"); + break; + case 505: + aOutText.AssignLiteral("HTTP Version Unsupported"); + break; + case 506: + aOutText.AssignLiteral("Variant Also Negotiates"); + break; + case 507: + aOutText.AssignLiteral("Insufficient Storage "); + break; + case 508: + aOutText.AssignLiteral("Loop Detected"); + break; + case 510: + aOutText.AssignLiteral("Not Extended"); + break; + case 511: + aOutText.AssignLiteral("Network Authentication Required"); + break; + default: + aOutText.AssignLiteral("No Reason Phrase"); + return false; + } + return true; +} + +namespace mozilla { +static auto MakeNameMatcher(const nsAString& aName) { + return [&aName](const auto& param) { return param.mKey.Equals(aName); }; +} + +bool URLParams::Has(const nsAString& aName) { + return std::any_of(mParams.cbegin(), mParams.cend(), MakeNameMatcher(aName)); +} + +bool URLParams::Has(const nsAString& aName, const nsAString& aValue) { + return std::any_of( + mParams.cbegin(), mParams.cend(), [&aName, &aValue](const auto& param) { + return param.mKey.Equals(aName) && param.mValue.Equals(aValue); + }); +} + +void URLParams::Get(const nsAString& aName, nsString& aRetval) { + SetDOMStringToNull(aRetval); + + const auto end = mParams.cend(); + const auto it = std::find_if(mParams.cbegin(), end, MakeNameMatcher(aName)); + if (it != end) { + aRetval.Assign(it->mValue); + } +} + +void URLParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval) { + aRetval.Clear(); + + for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) { + if (mParams[i].mKey.Equals(aName)) { + aRetval.AppendElement(mParams[i].mValue); + } + } +} + +void URLParams::Append(const nsAString& aName, const nsAString& aValue) { + Param* param = mParams.AppendElement(); + param->mKey = aName; + param->mValue = aValue; +} + +void URLParams::Set(const nsAString& aName, const nsAString& aValue) { + Param* param = nullptr; + for (uint32_t i = 0, len = mParams.Length(); i < len;) { + if (!mParams[i].mKey.Equals(aName)) { + ++i; + continue; + } + if (!param) { + param = &mParams[i]; + ++i; + continue; + } + // Remove duplicates. + mParams.RemoveElementAt(i); + --len; + } + + if (!param) { + param = mParams.AppendElement(); + param->mKey = aName; + } + + param->mValue = aValue; +} + +void URLParams::Delete(const nsAString& aName) { + mParams.RemoveElementsBy( + [&aName](const auto& param) { return param.mKey.Equals(aName); }); +} + +void URLParams::Delete(const nsAString& aName, const nsAString& aValue) { + mParams.RemoveElementsBy([&aName, &aValue](const auto& param) { + return param.mKey.Equals(aName) && param.mValue.Equals(aValue); + }); +} + +/* static */ +void URLParams::ConvertString(const nsACString& aInput, nsAString& aOutput) { + if (NS_FAILED(UTF_8_ENCODING->DecodeWithoutBOMHandling(aInput, aOutput))) { + MOZ_CRASH("Out of memory when converting URL params."); + } +} + +/* static */ +void URLParams::DecodeString(const nsACString& aInput, nsAString& aOutput) { + const char* const end = aInput.EndReading(); + + nsAutoCString unescaped; + + for (const char* iter = aInput.BeginReading(); iter != end;) { + // replace '+' with U+0020 + if (*iter == '+') { + unescaped.Append(' '); + ++iter; + continue; + } + + // Percent decode algorithm + if (*iter == '%') { + const char* const first = iter + 1; + const char* const second = first + 1; + + const auto asciiHexDigit = [](char x) { + return (x >= 0x41 && x <= 0x46) || (x >= 0x61 && x <= 0x66) || + (x >= 0x30 && x <= 0x39); + }; + + const auto hexDigit = [](char x) { + return x >= 0x30 && x <= 0x39 + ? x - 0x30 + : (x >= 0x41 && x <= 0x46 ? x - 0x37 : x - 0x57); + }; + + if (first != end && second != end && asciiHexDigit(*first) && + asciiHexDigit(*second)) { + unescaped.Append(hexDigit(*first) * 16 + hexDigit(*second)); + iter = second + 1; + } else { + unescaped.Append('%'); + ++iter; + } + + continue; + } + + unescaped.Append(*iter); + ++iter; + } + + // XXX It seems rather wasteful to first decode into a UTF-8 nsCString and + // then convert the whole string to UTF-16, at least if we exceed the inline + // storage size. + ConvertString(unescaped, aOutput); +} + +/* static */ +bool URLParams::ParseNextInternal(const char*& aStart, const char* const aEnd, + nsAString* aOutDecodedName, + nsAString* aOutDecodedValue) { + nsDependentCSubstring string; + + const char* const iter = std::find(aStart, aEnd, '&'); + if (iter != aEnd) { + string.Rebind(aStart, iter); + aStart = iter + 1; + } else { + string.Rebind(aStart, aEnd); + aStart = aEnd; + } + + if (string.IsEmpty()) { + return false; + } + + const auto* const eqStart = string.BeginReading(); + const auto* const eqEnd = string.EndReading(); + const auto* const eqIter = std::find(eqStart, eqEnd, '='); + + nsDependentCSubstring name; + nsDependentCSubstring value; + + if (eqIter != eqEnd) { + name.Rebind(eqStart, eqIter); + value.Rebind(eqIter + 1, eqEnd); + } else { + name.Rebind(string, 0); + } + + DecodeString(name, *aOutDecodedName); + DecodeString(value, *aOutDecodedValue); + + return true; +} + +/* static */ +bool URLParams::Extract(const nsACString& aInput, const nsAString& aName, + nsAString& aValue) { + aValue.SetIsVoid(true); + return !URLParams::Parse( + aInput, [&aName, &aValue](const nsAString& name, nsString&& value) { + if (aName == name) { + aValue = std::move(value); + return false; + } + return true; + }); +} + +void URLParams::ParseInput(const nsACString& aInput) { + // Remove all the existing data before parsing a new input. + DeleteAll(); + + URLParams::Parse(aInput, [this](nsString&& name, nsString&& value) { + mParams.AppendElement(Param{std::move(name), std::move(value)}); + return true; + }); +} + +namespace { + +void SerializeString(const nsCString& aInput, nsAString& aValue) { + const unsigned char* p = (const unsigned char*)aInput.get(); + const unsigned char* end = p + aInput.Length(); + + while (p != end) { + // ' ' to '+' + if (*p == 0x20) { + aValue.Append(0x2B); + // Percent Encode algorithm + } else if (*p == 0x2A || *p == 0x2D || *p == 0x2E || + (*p >= 0x30 && *p <= 0x39) || (*p >= 0x41 && *p <= 0x5A) || + *p == 0x5F || (*p >= 0x61 && *p <= 0x7A)) { + aValue.Append(*p); + } else { + aValue.AppendPrintf("%%%.2X", *p); + } + + ++p; + } +} + +} // namespace + +void URLParams::Serialize(nsAString& aValue, bool aEncode) const { + aValue.Truncate(); + bool first = true; + + for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) { + if (first) { + first = false; + } else { + aValue.Append('&'); + } + + // XXX Actually, it's not necessary to build a new string object. Generally, + // such cases could just convert each codepoint one-by-one. + if (aEncode) { + SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mKey), aValue); + aValue.Append('='); + SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mValue), aValue); + } else { + aValue.Append(mParams[i].mKey); + aValue.Append('='); + aValue.Append(mParams[i].mValue); + } + } +} + +void URLParams::Sort() { + mParams.StableSort([](const Param& lhs, const Param& rhs) { + return Compare(lhs.mKey, rhs.mKey); + }); +} + +} // namespace mozilla diff --git a/netwerk/base/nsURLHelper.h b/netwerk/base/nsURLHelper.h new file mode 100644 index 0000000000..f5ccc8bac6 --- /dev/null +++ b/netwerk/base/nsURLHelper.h @@ -0,0 +1,372 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsURLHelper_h__ +#define nsURLHelper_h__ + +#include "nsString.h" +#include "nsTArray.h" +#include "nsASCIIMask.h" + +class nsIFile; +class nsIURLParser; + +enum netCoalesceFlags { + NET_COALESCE_NORMAL = 0, + + /** + * retains /../ that reach above dir root (useful for FTP + * servers in which the root of the FTP URL is not necessarily + * the root of the FTP filesystem). + */ + NET_COALESCE_ALLOW_RELATIVE_ROOT = 1 << 0, + + /** + * recognizes /%2F and // as markers for the root directory + * and handles them properly. + */ + NET_COALESCE_DOUBLE_SLASH_IS_ROOT = 1 << 1 +}; + +//---------------------------------------------------------------------------- +// This module contains some private helper functions related to URL parsing. +//---------------------------------------------------------------------------- + +/* shutdown frees URL parser */ +void net_ShutdownURLHelper(); +#ifdef XP_MACOSX +void net_ShutdownURLHelperOSX(); +#endif + +/* access URL parsers */ +nsIURLParser* net_GetAuthURLParser(); +nsIURLParser* net_GetNoAuthURLParser(); +nsIURLParser* net_GetStdURLParser(); + +/* convert between nsIFile and file:// URL spec + * net_GetURLSpecFromFile does an extra stat, so callers should + * avoid it if possible in favor of net_GetURLSpecFromActualFile + * and net_GetURLSpecFromDir */ +nsresult net_GetURLSpecFromFile(nsIFile*, nsACString&); +nsresult net_GetURLSpecFromDir(nsIFile*, nsACString&); +nsresult net_GetURLSpecFromActualFile(nsIFile*, nsACString&); +nsresult net_GetFileFromURLSpec(const nsACString&, nsIFile**); + +/* extract file path components from file:// URL */ +nsresult net_ParseFileURL(const nsACString& inURL, nsACString& outDirectory, + nsACString& outFileBaseName, + nsACString& outFileExtension); + +/* handle .. in dirs while resolving URLs (path is UTF-8) */ +void net_CoalesceDirs(netCoalesceFlags flags, char* path); + +/** + * Check if a URL is absolute + * + * @param inURL URL spec + * @return true if the given spec represents an absolute URL + */ +bool net_IsAbsoluteURL(const nsACString& uri); + +/** + * Extract URI-Scheme if possible + * + * @param inURI URI spec + * @param scheme scheme copied to this buffer on return. Is lowercase. + */ +nsresult net_ExtractURLScheme(const nsACString& inURI, nsACString& scheme); + +/* check that the given scheme conforms to RFC 2396 */ +bool net_IsValidScheme(const nsACString& scheme); + +/** + * This function strips out all C0 controls and space at the beginning and end + * of the URL and filters out \r, \n, \t from the middle of the URL. This makes + * it safe to call on things like javascript: urls or data: urls, where we may + * in fact run into whitespace that is not properly encoded. + * + * @param input the URL spec we want to filter + * @param result the out param to write to if filtering happens + */ +void net_FilterURIString(const nsACString& input, nsACString& result); + +/** + * This function performs character stripping just like net_FilterURIString, + * with the added benefit of also performing percent escaping of dissallowed + * characters, all in one pass. Saving one pass is very important when operating + * on really large strings. + * + * @param aInput the URL spec we want to filter + * @param aFlags the flags which control which characters we escape + * @param aFilterMask a mask of characters that should excluded from the result + * @param aResult the out param to write to if filtering happens + */ +nsresult net_FilterAndEscapeURI(const nsACString& aInput, uint32_t aFlags, + const ASCIIMaskArray& aFilterMask, + nsACString& aResult); + +#if defined(XP_WIN) +/** + * On Win32 and OS/2 system's a back-slash in a file:// URL is equivalent to a + * forward-slash. This function maps any back-slashes to forward-slashes. + * + * @param aURL + * The URL string to normalize (UTF-8 encoded). This can be a + * relative URL segment. + * @param aResultBuf + * The resulting string is appended to this string. If the input URL + * is already normalized, then aResultBuf is unchanged. + * + * @returns false if aURL is already normalized. Otherwise, returns true. + */ +bool net_NormalizeFileURL(const nsACString& aURL, nsCString& aResultBuf); +#endif + +/***************************************************************************** + * generic string routines follow (XXX move to someplace more generic). + */ + +/* convert to lower case */ +void net_ToLowerCase(char* str, uint32_t length); +void net_ToLowerCase(char* str); + +/** + * returns pointer to first character of |str| in the given set. if not found, + * then |end| is returned. stops prematurely if a null byte is encountered, + * and returns the address of the null byte. + */ +char* net_FindCharInSet(const char* iter, const char* stop, const char* set); + +/** + * returns pointer to first character of |str| NOT in the given set. if all + * characters are in the given set, then |end| is returned. if '\0' is not + * included in |set|, then stops prematurely if a null byte is encountered, + * and returns the address of the null byte. + */ +char* net_FindCharNotInSet(const char* iter, const char* stop, const char* set); + +/** + * returns pointer to last character of |str| NOT in the given set. if all + * characters are in the given set, then |str - 1| is returned. + */ +char* net_RFindCharNotInSet(const char* stop, const char* iter, + const char* set); + +/** + * Parses a content-type header and returns the content type and + * charset (if any). aCharset is not modified if no charset is + * specified in anywhere in aHeaderStr. In that case (no charset + * specified), aHadCharset is set to false. Otherwise, it's set to + * true. Note that aContentCharset can be empty even if aHadCharset + * is true. + * + * This parsing is suitable for HTTP request. Use net_ParseContentType + * for parsing this header in HTTP responses. + */ +void net_ParseRequestContentType(const nsACString& aHeaderStr, + nsACString& aContentType, + nsACString& aContentCharset, + bool* aHadCharset); + +/** + * Parses a content-type header and returns the content type and + * charset (if any). aCharset is not modified if no charset is + * specified in anywhere in aHeaderStr. In that case (no charset + * specified), aHadCharset is set to false. Otherwise, it's set to + * true. Note that aContentCharset can be empty even if aHadCharset + * is true. + */ +void net_ParseContentType(const nsACString& aHeaderStr, + nsACString& aContentType, nsACString& aContentCharset, + bool* aHadCharset); +/** + * As above, but also returns the start and end indexes for the charset + * parameter in aHeaderStr. These are indices for the entire parameter, NOT + * just the value. If there is "effectively" no charset parameter (e.g. if an + * earlier type with one is overridden by a later type without one), + * *aHadCharset will be true but *aCharsetStart will be set to -1. Note that + * it's possible to have aContentCharset empty and *aHadCharset true when + * *aCharsetStart is nonnegative; this corresponds to charset="". + */ +void net_ParseContentType(const nsACString& aHeaderStr, + nsACString& aContentType, nsACString& aContentCharset, + bool* aHadCharset, int32_t* aCharsetStart, + int32_t* aCharsetEnd); + +/* inline versions */ + +/* remember the 64-bit platforms ;-) */ +#define NET_MAX_ADDRESS ((char*)UINTPTR_MAX) + +inline char* net_FindCharInSet(const char* str, const char* set) { + return net_FindCharInSet(str, NET_MAX_ADDRESS, set); +} +inline char* net_FindCharNotInSet(const char* str, const char* set) { + return net_FindCharNotInSet(str, NET_MAX_ADDRESS, set); +} +inline char* net_RFindCharNotInSet(const char* str, const char* set) { + return net_RFindCharNotInSet(str, str + strlen(str), set); +} + +/** + * This function returns true if the given hostname does not include any + * restricted characters. Otherwise, false is returned. + */ +bool net_IsValidHostName(const nsACString& host); + +/** + * Checks whether the IPv4 address is valid according to RFC 3986 section 3.2.2. + */ +bool net_IsValidIPv4Addr(const nsACString& aAddr); + +/** + * Checks whether the IPv6 address is valid according to RFC 3986 section 3.2.2. + */ +bool net_IsValidIPv6Addr(const nsACString& aAddr); + +/** + * Returns the default status text for a given HTTP status code (useful if HTTP2 + * does not provide one, for instance). + */ +bool net_GetDefaultStatusTextForCode(uint16_t aCode, nsACString& aOutText); + +namespace mozilla { +/** + * A class for handling form-urlencoded query strings. + * + * Manages an ordered list of name-value pairs, and allows conversion from and + * to the string representation. + * + * In addition, there are static functions for handling one-shot use cases. + */ +class URLParams final { + public: + /** + * \brief Parses a query string and calls a parameter handler for each + * name/value pair. The parameter handler can stop processing early by + * returning false. + * + * \param aInput the query string to parse + * \param aParamHandler the parameter handler as desribed above + * \tparam ParamHandler a function type compatible with signature + * bool(nsString, nsString) + * + * \return false if the parameter handler returned false for any parameter, + * true otherwise + */ + template <typename ParamHandler> + static bool Parse(const nsACString& aInput, ParamHandler aParamHandler) { + const char* start = aInput.BeginReading(); + const char* const end = aInput.EndReading(); + + while (start != end) { + nsAutoString decodedName; + nsAutoString decodedValue; + + if (!ParseNextInternal(start, end, &decodedName, &decodedValue)) { + continue; + } + + if (!aParamHandler(std::move(decodedName), std::move(decodedValue))) { + return false; + } + } + return true; + } + + /** + * \brief Parses a query string and returns the value of a single parameter + * specified by name. + * + * If there are multiple parameters with the same name, the value of the first + * is returned. + * + * \param aInput the query string to parse + * \param aName the name of the parameter to extract + * \param[out] aValue will be assigned the parameter value, set to void if + * there is no match \return true iff there was a parameter with with name + * \paramref aName + */ + static bool Extract(const nsACString& aInput, const nsAString& aName, + nsAString& aValue); + + /** + * \brief Resets the state of this instance and parses a new query string. + * + * \param aInput the query string to parse + */ + void ParseInput(const nsACString& aInput); + + /** + * Serializes the current state to a query string. + * + * \param[out] aValue will be assigned the result of the serialization + * \param aEncode If this is true, the serialization will encode the string. + */ + void Serialize(nsAString& aValue, bool aEncode) const; + + void Get(const nsAString& aName, nsString& aRetval); + + void GetAll(const nsAString& aName, nsTArray<nsString>& aRetval); + + /** + * \brief Sets the value of a given parameter. + * + * If one or more parameters of the name exist, the value of the first is + * replaced, and all further parameters of the name are deleted. Otherwise, + * the behaviour is the same as \ref Append. + */ + void Set(const nsAString& aName, const nsAString& aValue); + + void Append(const nsAString& aName, const nsAString& aValue); + + bool Has(const nsAString& aName); + + bool Has(const nsAString& aName, const nsAString& aValue); + + /** + * \brief Deletes all parameters with the given name. + */ + void Delete(const nsAString& aName); + + void Delete(const nsAString& aName, const nsAString& aValue); + + void DeleteAll() { mParams.Clear(); } + + uint32_t Length() const { return mParams.Length(); } + + const nsAString& GetKeyAtIndex(uint32_t aIndex) const { + MOZ_ASSERT(aIndex < mParams.Length()); + return mParams[aIndex].mKey; + } + + const nsAString& GetValueAtIndex(uint32_t aIndex) const { + MOZ_ASSERT(aIndex < mParams.Length()); + return mParams[aIndex].mValue; + } + + /** + * \brief Performs a stable sort of the parameters, maintaining the order of + * multiple parameters with the same name. + */ + void Sort(); + + private: + static void DecodeString(const nsACString& aInput, nsAString& aOutput); + static void ConvertString(const nsACString& aInput, nsAString& aOutput); + static bool ParseNextInternal(const char*& aStart, const char* aEnd, + nsAString* aOutDecodedName, + nsAString* aOutDecodedValue); + + struct Param { + nsString mKey; + nsString mValue; + }; + + nsTArray<Param> mParams; +}; +} // namespace mozilla + +#endif // !nsURLHelper_h__ diff --git a/netwerk/base/nsURLHelperOSX.cpp b/netwerk/base/nsURLHelperOSX.cpp new file mode 100644 index 0000000000..9a8fbad9a2 --- /dev/null +++ b/netwerk/base/nsURLHelperOSX.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Mac OS X-specific local file uri parsing */ +#include "nsURLHelper.h" +#include "nsEscape.h" +#include "nsIFile.h" +#include "nsTArray.h" +#include "nsReadableUtils.h" +#include <Carbon/Carbon.h> + +static nsTArray<nsCString>* gVolumeList = nullptr; + +static bool pathBeginsWithVolName(const nsACString& path, + nsACString& firstPathComponent) { + // Return whether the 1st path component in path (escaped) is equal to the + // name of a mounted volume. Return the 1st path component (unescaped) in any + // case. This needs to be done as quickly as possible, so we cache a list of + // volume names. + // XXX Register an event handler to detect drives being mounted/unmounted? + + if (!gVolumeList) { + gVolumeList = new nsTArray<nsCString>; + if (!gVolumeList) { + return false; // out of memory + } + } + + // Cache a list of volume names + if (!gVolumeList->Length()) { + OSErr err; + ItemCount volumeIndex = 1; + + do { + HFSUniStr255 volName; + FSRef rootDirectory; + err = ::FSGetVolumeInfo(0, volumeIndex, nullptr, kFSVolInfoNone, nullptr, + &volName, &rootDirectory); + if (err == noErr) { + NS_ConvertUTF16toUTF8 volNameStr( + Substring((char16_t*)volName.unicode, + (char16_t*)volName.unicode + volName.length)); + gVolumeList->AppendElement(volNameStr); + volumeIndex++; + } + } while (err == noErr); + } + + // Extract the first component of the path + nsACString::const_iterator start; + path.BeginReading(start); + start.advance(1); // path begins with '/' + nsACString::const_iterator directory_end; + path.EndReading(directory_end); + nsACString::const_iterator component_end(start); + FindCharInReadable('/', component_end, directory_end); + + nsAutoCString flatComponent((Substring(start, component_end))); + NS_UnescapeURL(flatComponent); + int32_t foundIndex = gVolumeList->IndexOf(flatComponent); + firstPathComponent = flatComponent; + return (foundIndex != -1); +} + +void net_ShutdownURLHelperOSX() { + delete gVolumeList; + gVolumeList = nullptr; +} + +static nsresult convertHFSPathtoPOSIX(const nsACString& hfsPath, + nsACString& posixPath) { + // Use CFURL to do the conversion. We don't want to do this by simply + // using SwapSlashColon - we need the charset mapped from MacRoman + // to UTF-8, and we need "/Volumes" (or whatever - Apple says this is subject + // to change) prepended if the path is not on the boot drive. + + CFStringRef pathStrRef = CFStringCreateWithCString( + nullptr, PromiseFlatCString(hfsPath).get(), kCFStringEncodingMacRoman); + if (!pathStrRef) return NS_ERROR_FAILURE; + + nsresult rv = NS_ERROR_FAILURE; + CFURLRef urlRef = CFURLCreateWithFileSystemPath(nullptr, pathStrRef, + kCFURLHFSPathStyle, true); + if (urlRef) { + UInt8 pathBuf[PATH_MAX]; + if (CFURLGetFileSystemRepresentation(urlRef, true, pathBuf, + sizeof(pathBuf))) { + posixPath = (char*)pathBuf; + rv = NS_OK; + } + } + CFRelease(pathStrRef); + if (urlRef) CFRelease(urlRef); + return rv; +} + +static void SwapSlashColon(char* s) { + while (*s) { + if (*s == '/') + *s = ':'; + else if (*s == ':') + *s = '/'; + s++; + } +} + +nsresult net_GetURLSpecFromActualFile(nsIFile* aFile, nsACString& result) { + // NOTE: This is identical to the implementation in nsURLHelperUnix.cpp + + nsresult rv; + nsAutoCString ePath; + + // construct URL spec from native file path + rv = aFile->GetNativePath(ePath); + if (NS_FAILED(rv)) return rv; + + nsAutoCString escPath; + constexpr auto prefix = "file://"_ns; + + // Escape the path with the directory mask + if (NS_EscapeURL(ePath.get(), ePath.Length(), esc_Directory + esc_Forced, + escPath)) + escPath.Insert(prefix, 0); + else + escPath.Assign(prefix + ePath); + + // esc_Directory does not escape the semicolons, so if a filename + // contains semicolons we need to manually escape them. + // This replacement should be removed in bug #473280 + escPath.ReplaceSubstring(";", "%3b"); + + result = escPath; + return NS_OK; +} + +nsresult net_GetFileFromURLSpec(const nsACString& aURL, nsIFile** result) { + // NOTE: See also the implementation in nsURLHelperUnix.cpp + // This matches it except for the HFS path handling. + + nsresult rv; + + nsCOMPtr<nsIFile> localFile; + rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localFile)); + if (NS_FAILED(rv)) return rv; + + nsAutoCString directory, fileBaseName, fileExtension, path; + bool bHFSPath = false; + + rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension); + if (NS_FAILED(rv)) return rv; + + if (!directory.IsEmpty()) { + NS_EscapeURL(directory, esc_Directory | esc_AlwaysCopy, path); + + // The canonical form of file URLs on OSX use POSIX paths: + // file:///path-name. + // But, we still encounter file URLs that use HFS paths: + // file:///volume-name/path-name + // Determine that here and normalize HFS paths to POSIX. + nsAutoCString possibleVolName; + if (pathBeginsWithVolName(directory, possibleVolName)) { + // Though we know it begins with a volume name, it could still + // be a valid POSIX path if the boot drive is named "Mac HD" + // and there is a directory "Mac HD" at its root. If such a + // directory doesn't exist, we'll assume this is an HFS path. + FSRef testRef; + possibleVolName.InsertLiteral("/", 0); + if (::FSPathMakeRef((UInt8*)possibleVolName.get(), &testRef, nullptr) != + noErr) + bHFSPath = true; + } + + if (bHFSPath) { + // "%2F"s need to become slashes, while all other slashes need to + // become colons. If we start out by changing "%2F"s to colons, we + // can reply on SwapSlashColon() to do what we need + path.ReplaceSubstring("%2F", ":"); + path.Cut(0, 1); // directory begins with '/' + SwapSlashColon((char*)path.get()); + // At this point, path is an HFS path made using the same + // algorithm as nsURLHelperMac. We'll convert to POSIX below. + } + } + if (!fileBaseName.IsEmpty()) + NS_EscapeURL(fileBaseName, esc_FileBaseName | esc_AlwaysCopy, path); + if (!fileExtension.IsEmpty()) { + path += '.'; + NS_EscapeURL(fileExtension, esc_FileExtension | esc_AlwaysCopy, path); + } + + NS_UnescapeURL(path); + if (path.Length() != strlen(path.get())) return NS_ERROR_FILE_INVALID_PATH; + + if (bHFSPath) convertHFSPathtoPOSIX(path, path); + + // assuming path is encoded in the native charset + rv = localFile->InitWithNativePath(path); + if (NS_FAILED(rv)) return rv; + + localFile.forget(result); + return NS_OK; +} diff --git a/netwerk/base/nsURLHelperUnix.cpp b/netwerk/base/nsURLHelperUnix.cpp new file mode 100644 index 0000000000..abb8b0cfeb --- /dev/null +++ b/netwerk/base/nsURLHelperUnix.cpp @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Unix-specific local file uri parsing */ +#include "nsURLHelper.h" +#include "nsEscape.h" +#include "nsIFile.h" +#include "nsNativeCharsetUtils.h" +#include "mozilla/Utf8.h" + +using mozilla::IsUtf8; + +nsresult net_GetURLSpecFromActualFile(nsIFile* aFile, nsACString& result) { + nsresult rv; + nsAutoCString nativePath, ePath; + nsAutoString path; + + rv = aFile->GetNativePath(nativePath); + if (NS_FAILED(rv)) return rv; + + // Convert to unicode and back to check correct conversion to native charset + NS_CopyNativeToUnicode(nativePath, path); + NS_CopyUnicodeToNative(path, ePath); + + // Use UTF8 version if conversion was successful + if (nativePath == ePath) { + CopyUTF16toUTF8(path, ePath); + } else { + ePath = nativePath; + } + + nsAutoCString escPath; + constexpr auto prefix = "file://"_ns; + + // Escape the path with the directory mask + if (NS_EscapeURL(ePath.get(), -1, esc_Directory + esc_Forced, escPath)) { + escPath.Insert(prefix, 0); + } else { + escPath.Assign(prefix + ePath); + } + + // esc_Directory does not escape the semicolons, so if a filename + // contains semicolons we need to manually escape them. + // This replacement should be removed in bug #473280 + escPath.ReplaceSubstring(";", "%3b"); + result = escPath; + return NS_OK; +} + +nsresult net_GetFileFromURLSpec(const nsACString& aURL, nsIFile** result) { + // NOTE: See also the implementation in nsURLHelperOSX.cpp, + // which is based on this. + + nsresult rv; + + nsCOMPtr<nsIFile> localFile; + rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localFile)); + if (NS_FAILED(rv)) return rv; + + nsAutoCString directory, fileBaseName, fileExtension, path; + + rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension); + if (NS_FAILED(rv)) return rv; + + if (!directory.IsEmpty()) { + rv = NS_EscapeURL(directory, esc_Directory | esc_AlwaysCopy, path, + mozilla::fallible); + if (NS_FAILED(rv)) return rv; + } + if (!fileBaseName.IsEmpty()) { + rv = NS_EscapeURL(fileBaseName, esc_FileBaseName | esc_AlwaysCopy, path, + mozilla::fallible); + if (NS_FAILED(rv)) return rv; + } + if (!fileExtension.IsEmpty()) { + path += '.'; + rv = NS_EscapeURL(fileExtension, esc_FileExtension | esc_AlwaysCopy, path, + mozilla::fallible); + if (NS_FAILED(rv)) return rv; + } + + NS_UnescapeURL(path); + if (path.Length() != strlen(path.get())) return NS_ERROR_FILE_INVALID_PATH; + + if (IsUtf8(path)) { + // speed up the start-up where UTF-8 is the native charset + // (e.g. on recent Linux distributions) + if (NS_IsNativeUTF8()) { + rv = localFile->InitWithNativePath(path); + } else { + rv = localFile->InitWithPath(NS_ConvertUTF8toUTF16(path)); + } + // XXX In rare cases, a valid UTF-8 string can be valid as a native + // encoding (e.g. 0xC5 0x83 is valid both as UTF-8 and Windows-125x). + // However, the chance is very low that a meaningful word in a legacy + // encoding is valid as UTF-8. + } else { + // if path is not in UTF-8, assume it is encoded in the native charset + rv = localFile->InitWithNativePath(path); + } + + if (NS_FAILED(rv)) return rv; + + localFile.forget(result); + return NS_OK; +} diff --git a/netwerk/base/nsURLHelperWin.cpp b/netwerk/base/nsURLHelperWin.cpp new file mode 100644 index 0000000000..7a4f53978e --- /dev/null +++ b/netwerk/base/nsURLHelperWin.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Windows-specific local file uri parsing */ +#include "nsComponentManagerUtils.h" +#include "nsURLHelper.h" +#include "nsEscape.h" +#include "nsIFile.h" +#include <windows.h> +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Utf8.h" + +using namespace mozilla; + +nsresult net_GetURLSpecFromActualFile(nsIFile* aFile, nsACString& result) { + nsresult rv; + nsAutoString path; + + // construct URL spec from file path + rv = aFile->GetPath(path); + if (NS_FAILED(rv)) return rv; + + // Replace \ with / to convert to an url + path.ReplaceChar(char16_t(0x5Cu), char16_t(0x2Fu)); + + nsAutoCString escPath; + + // Windows Desktop paths begin with a drive letter, so need an 'extra' + // slash at the begining + // C:\Windows => file:///C:/Windows + constexpr auto prefix = "file:///"_ns; + + // Escape the path with the directory mask + NS_ConvertUTF16toUTF8 ePath(path); + if (NS_EscapeURL(ePath.get(), -1, esc_Directory + esc_Forced, escPath)) + escPath.Insert(prefix, 0); + else + escPath.Assign(prefix + ePath); + + // esc_Directory does not escape the semicolons, so if a filename + // contains semicolons we need to manually escape them. + // This replacement should be removed in bug #473280 + escPath.ReplaceSubstring(";", "%3b"); + + result = escPath; + return NS_OK; +} + +nsresult net_GetFileFromURLSpec(const nsACString& aURL, nsIFile** result) { + nsresult rv; + + if (aURL.Length() > StaticPrefs::network_standard_url_max_length()) { + return NS_ERROR_MALFORMED_URI; + } + + nsCOMPtr<nsIFile> localFile(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + NS_ERROR("Only nsIFile supported right now"); + return rv; + } + + const nsACString* specPtr; + + nsAutoCString buf; + if (net_NormalizeFileURL(aURL, buf)) + specPtr = &buf; + else + specPtr = &aURL; + + nsAutoCString directory, fileBaseName, fileExtension; + + rv = net_ParseFileURL(*specPtr, directory, fileBaseName, fileExtension); + if (NS_FAILED(rv)) return rv; + + nsAutoCString path; + + if (!directory.IsEmpty()) { + NS_EscapeURL(directory, esc_Directory | esc_AlwaysCopy, path); + if (path.Length() > 2 && path.CharAt(2) == '|') path.SetCharAt(':', 2); + path.ReplaceChar('/', '\\'); + } + if (!fileBaseName.IsEmpty()) + NS_EscapeURL(fileBaseName, esc_FileBaseName | esc_AlwaysCopy, path); + if (!fileExtension.IsEmpty()) { + path += '.'; + NS_EscapeURL(fileExtension, esc_FileExtension | esc_AlwaysCopy, path); + } + + NS_UnescapeURL(path); + if (path.Length() != strlen(path.get())) return NS_ERROR_FILE_INVALID_PATH; + + // remove leading '\' + if (path.CharAt(0) == '\\') path.Cut(0, 1); + + if (IsUtf8(path)) rv = localFile->InitWithPath(NS_ConvertUTF8toUTF16(path)); + // XXX In rare cases, a valid UTF-8 string can be valid as a native + // encoding (e.g. 0xC5 0x83 is valid both as UTF-8 and Windows-125x). + // However, the chance is very low that a meaningful word in a legacy + // encoding is valid as UTF-8. + else + // if path is not in UTF-8, assume it is encoded in the native charset + rv = localFile->InitWithNativePath(path); + + if (NS_FAILED(rv)) return rv; + + localFile.forget(result); + return NS_OK; +} diff --git a/netwerk/base/nsURLParsers.cpp b/netwerk/base/nsURLParsers.cpp new file mode 100644 index 0000000000..7dc6567d84 --- /dev/null +++ b/netwerk/base/nsURLParsers.cpp @@ -0,0 +1,644 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <string.h> + +#include "mozilla/RangedPtr.h" +#include "mozilla/TextUtils.h" + +#include "nsCRTGlue.h" +#include "nsURLParsers.h" +#include "nsURLHelper.h" +#include "nsString.h" + +using namespace mozilla; + +//---------------------------------------------------------------------------- + +static uint32_t CountConsecutiveSlashes(const char* str, int32_t len) { + RangedPtr<const char> p(str, len); + uint32_t count = 0; + while (len-- && *p++ == '/') ++count; + return count; +} + +//---------------------------------------------------------------------------- +// nsBaseURLParser implementation +//---------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsAuthURLParser, nsIURLParser) +NS_IMPL_ISUPPORTS(nsNoAuthURLParser, nsIURLParser) + +#define SET_RESULT(component, pos, len) \ + PR_BEGIN_MACRO \ + if (component##Pos) *component##Pos = uint32_t(pos); \ + if (component##Len) *component##Len = int32_t(len); \ + PR_END_MACRO + +#define OFFSET_RESULT(component, offset) \ + PR_BEGIN_MACRO \ + if (component##Pos) *component##Pos += (offset); \ + PR_END_MACRO + +NS_IMETHODIMP +nsBaseURLParser::ParseURL(const char* spec, int32_t specLen, + uint32_t* schemePos, int32_t* schemeLen, + uint32_t* authorityPos, int32_t* authorityLen, + uint32_t* pathPos, int32_t* pathLen) { + if (NS_WARN_IF(!spec)) { + return NS_ERROR_INVALID_POINTER; + } + + if (specLen < 0) specLen = strlen(spec); + + const char* stop = nullptr; + const char* colon = nullptr; + const char* slash = nullptr; + const char* p = spec; + uint32_t offset = 0; + int32_t len = specLen; + + // skip leading whitespace + while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') { + spec++; + specLen--; + offset++; + + p++; + len--; + } + + for (; len && *p && !colon && !slash; ++p, --len) { + switch (*p) { + case ':': + if (!colon) colon = p; + break; + case '/': // start of filepath + case '?': // start of query + case '#': // start of ref + if (!slash) slash = p; + break; + case '@': // username@hostname + case '[': // start of IPv6 address literal + if (!stop) stop = p; + break; + } + } + // disregard the first colon if it follows an '@' or a '[' + if (colon && stop && colon > stop) colon = nullptr; + + // if the spec only contained whitespace ... + if (specLen == 0) { + SET_RESULT(scheme, 0, -1); + SET_RESULT(authority, 0, 0); + SET_RESULT(path, 0, 0); + return NS_OK; + } + + // ignore trailing whitespace and control characters + for (p = spec + specLen - 1; ((unsigned char)*p <= ' ') && (p != spec); --p) { + ; + } + + specLen = p - spec + 1; + + if (colon && (colon < slash || !slash)) { + // + // spec = <scheme>:/<the-rest> + // + // or + // + // spec = <scheme>:<authority> + // spec = <scheme>:<path-no-slashes> + // + if (!net_IsValidScheme(nsDependentCSubstring(spec, colon - spec))) { + return NS_ERROR_MALFORMED_URI; + } + SET_RESULT(scheme, offset, colon - spec); + if (authorityLen || pathLen) { + uint32_t schemeLen = colon + 1 - spec; + offset += schemeLen; + ParseAfterScheme(colon + 1, specLen - schemeLen, authorityPos, + authorityLen, pathPos, pathLen); + OFFSET_RESULT(authority, offset); + OFFSET_RESULT(path, offset); + } + } else { + // + // spec = <authority-no-port-or-password>/<path> + // spec = <path> + // + // or + // + // spec = <authority-no-port-or-password>/<path-with-colon> + // spec = <path-with-colon> + // + // or + // + // spec = <authority-no-port-or-password> + // spec = <path-no-slashes-or-colon> + // + SET_RESULT(scheme, 0, -1); + if (authorityLen || pathLen) { + ParseAfterScheme(spec, specLen, authorityPos, authorityLen, pathPos, + pathLen); + OFFSET_RESULT(authority, offset); + OFFSET_RESULT(path, offset); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsBaseURLParser::ParseAuthority(const char* auth, int32_t authLen, + uint32_t* usernamePos, int32_t* usernameLen, + uint32_t* passwordPos, int32_t* passwordLen, + uint32_t* hostnamePos, int32_t* hostnameLen, + int32_t* port) { + if (NS_WARN_IF(!auth)) { + return NS_ERROR_INVALID_POINTER; + } + + if (authLen < 0) authLen = strlen(auth); + + SET_RESULT(username, 0, -1); + SET_RESULT(password, 0, -1); + SET_RESULT(hostname, 0, authLen); + if (port) *port = -1; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseURLParser::ParseUserInfo(const char* userinfo, int32_t userinfoLen, + uint32_t* usernamePos, int32_t* usernameLen, + uint32_t* passwordPos, int32_t* passwordLen) { + SET_RESULT(username, 0, -1); + SET_RESULT(password, 0, -1); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseURLParser::ParseServerInfo(const char* serverinfo, int32_t serverinfoLen, + uint32_t* hostnamePos, int32_t* hostnameLen, + int32_t* port) { + SET_RESULT(hostname, 0, -1); + if (port) *port = -1; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseURLParser::ParsePath(const char* path, int32_t pathLen, + uint32_t* filepathPos, int32_t* filepathLen, + uint32_t* queryPos, int32_t* queryLen, + uint32_t* refPos, int32_t* refLen) { + if (NS_WARN_IF(!path)) { + return NS_ERROR_INVALID_POINTER; + } + + if (pathLen < 0) pathLen = strlen(path); + + // path = [/]<segment1>/<segment2>/<...>/<segmentN>?<query>#<ref> + + // XXX PL_strnpbrk would be nice, but it's buggy + + // search for first occurrence of either ? or # + const char *query_beg = nullptr, *query_end = nullptr; + const char* ref_beg = nullptr; + const char* p = nullptr; + for (p = path; p < path + pathLen; ++p) { + // only match the query string if it precedes the reference fragment + if (!ref_beg && !query_beg && *p == '?') { + query_beg = p + 1; + } else if (*p == '#') { + ref_beg = p + 1; + if (query_beg) query_end = p; + break; + } + } + + if (query_beg) { + if (query_end) { + SET_RESULT(query, query_beg - path, query_end - query_beg); + } else { + SET_RESULT(query, query_beg - path, pathLen - (query_beg - path)); + } + } else { + SET_RESULT(query, 0, -1); + } + + if (ref_beg) { + SET_RESULT(ref, ref_beg - path, pathLen - (ref_beg - path)); + } else { + SET_RESULT(ref, 0, -1); + } + + const char* end; + if (query_beg) { + end = query_beg - 1; + } else if (ref_beg) { + end = ref_beg - 1; + } else { + end = path + pathLen; + } + + // an empty file path is no file path + if (end != path) { + SET_RESULT(filepath, 0, end - path); + } else { + SET_RESULT(filepath, 0, -1); + } + return NS_OK; +} + +NS_IMETHODIMP +nsBaseURLParser::ParseFilePath(const char* filepath, int32_t filepathLen, + uint32_t* directoryPos, int32_t* directoryLen, + uint32_t* basenamePos, int32_t* basenameLen, + uint32_t* extensionPos, int32_t* extensionLen) { + if (NS_WARN_IF(!filepath)) { + return NS_ERROR_INVALID_POINTER; + } + + if (filepathLen < 0) filepathLen = strlen(filepath); + + if (filepathLen == 0) { + SET_RESULT(directory, 0, -1); + SET_RESULT(basename, 0, 0); // assume a zero length file basename + SET_RESULT(extension, 0, -1); + return NS_OK; + } + + const char* p; + const char* end = filepath + filepathLen; + + // search backwards for filename + for (p = end - 1; *p != '/' && p > filepath; --p) { + ; + } + if (*p == '/') { + // catch /.. and /. + if ((p + 1 < end && *(p + 1) == '.') && + (p + 2 == end || (*(p + 2) == '.' && p + 3 == end))) { + p = end - 1; + } + // filepath = <directory><filename>.<extension> + SET_RESULT(directory, 0, p - filepath + 1); + ParseFileName(p + 1, end - (p + 1), basenamePos, basenameLen, extensionPos, + extensionLen); + OFFSET_RESULT(basename, p + 1 - filepath); + OFFSET_RESULT(extension, p + 1 - filepath); + } else { + // filepath = <filename>.<extension> + SET_RESULT(directory, 0, -1); + ParseFileName(filepath, filepathLen, basenamePos, basenameLen, extensionPos, + extensionLen); + } + return NS_OK; +} + +nsresult nsBaseURLParser::ParseFileName( + const char* filename, int32_t filenameLen, uint32_t* basenamePos, + int32_t* basenameLen, uint32_t* extensionPos, int32_t* extensionLen) { + if (NS_WARN_IF(!filename)) { + return NS_ERROR_INVALID_POINTER; + } + + if (filenameLen < 0) filenameLen = strlen(filename); + + // no extension if filename ends with a '.' + if (filename[filenameLen - 1] != '.') { + // ignore '.' at the beginning + for (const char* p = filename + filenameLen - 1; p > filename; --p) { + if (*p == '.') { + // filename = <basename.extension> + SET_RESULT(basename, 0, p - filename); + SET_RESULT(extension, p + 1 - filename, + filenameLen - (p - filename + 1)); + return NS_OK; + } + } + } + // filename = <basename> + SET_RESULT(basename, 0, filenameLen); + SET_RESULT(extension, 0, -1); + return NS_OK; +} + +//---------------------------------------------------------------------------- +// nsNoAuthURLParser implementation +//---------------------------------------------------------------------------- + +NS_IMETHODIMP +nsNoAuthURLParser::ParseAuthority(const char* auth, int32_t authLen, + uint32_t* usernamePos, int32_t* usernameLen, + uint32_t* passwordPos, int32_t* passwordLen, + uint32_t* hostnamePos, int32_t* hostnameLen, + int32_t* port) { + MOZ_ASSERT_UNREACHABLE("Shouldn't parse auth in a NoAuthURL!"); + return NS_ERROR_UNEXPECTED; +} + +void nsNoAuthURLParser::ParseAfterScheme(const char* spec, int32_t specLen, + uint32_t* authPos, int32_t* authLen, + uint32_t* pathPos, int32_t* pathLen) { + MOZ_ASSERT(specLen >= 0, "unexpected"); + + // everything is the path + uint32_t pos = 0; + switch (CountConsecutiveSlashes(spec, specLen)) { + case 0: + case 1: + break; + case 2: { + const char* p = nullptr; + if (specLen > 2) { + // looks like there is an authority section + + // if the authority looks like a drive number then we + // really want to treat it as part of the path + // [a-zA-Z][:|]{/\} + // i.e one of: c: c:\foo c:/foo c| c|\foo c|/foo + if ((specLen > 3) && (spec[3] == ':' || spec[3] == '|') && + IsAsciiAlpha(spec[2]) && + ((specLen == 4) || (spec[4] == '/') || (spec[4] == '\\'))) { + pos = 1; + break; + } + // Ignore apparent authority; path is everything after it + for (p = spec + 2; p < spec + specLen; ++p) { + if (*p == '/' || *p == '?' || *p == '#') break; + } + } + SET_RESULT(auth, 0, -1); + if (p && p != spec + specLen) { + SET_RESULT(path, p - spec, specLen - (p - spec)); + } else { + SET_RESULT(path, 0, -1); + } + return; + } + default: + pos = 2; + break; + } + SET_RESULT(auth, pos, 0); + SET_RESULT(path, pos, specLen - pos); +} + +#if defined(XP_WIN) +NS_IMETHODIMP +nsNoAuthURLParser::ParseFilePath(const char* filepath, int32_t filepathLen, + uint32_t* directoryPos, int32_t* directoryLen, + uint32_t* basenamePos, int32_t* basenameLen, + uint32_t* extensionPos, + int32_t* extensionLen) { + if (NS_WARN_IF(!filepath)) { + return NS_ERROR_INVALID_POINTER; + } + + if (filepathLen < 0) filepathLen = strlen(filepath); + + // look for a filepath consisting of only a drive number, which may or + // may not have a leading slash. + if (filepathLen > 1 && filepathLen < 4) { + const char* end = filepath + filepathLen; + const char* p = filepath; + if (*p == '/') p++; + if ((end - p == 2) && (p[1] == ':' || p[1] == '|') && IsAsciiAlpha(*p)) { + // filepath = <drive-number>: + SET_RESULT(directory, 0, filepathLen); + SET_RESULT(basename, 0, -1); + SET_RESULT(extension, 0, -1); + return NS_OK; + } + } + + // otherwise fallback on common implementation + return nsBaseURLParser::ParseFilePath(filepath, filepathLen, directoryPos, + directoryLen, basenamePos, basenameLen, + extensionPos, extensionLen); +} +#endif + +//---------------------------------------------------------------------------- +// nsAuthURLParser implementation +//---------------------------------------------------------------------------- + +NS_IMETHODIMP +nsAuthURLParser::ParseAuthority(const char* auth, int32_t authLen, + uint32_t* usernamePos, int32_t* usernameLen, + uint32_t* passwordPos, int32_t* passwordLen, + uint32_t* hostnamePos, int32_t* hostnameLen, + int32_t* port) { + nsresult rv; + + if (NS_WARN_IF(!auth)) { + return NS_ERROR_INVALID_POINTER; + } + + if (authLen < 0) authLen = strlen(auth); + + if (authLen == 0) { + SET_RESULT(username, 0, -1); + SET_RESULT(password, 0, -1); + SET_RESULT(hostname, 0, 0); + if (port) *port = -1; + return NS_OK; + } + + // search backwards for @ + const char* p = auth + authLen - 1; + for (; (*p != '@') && (p > auth); --p) { + } + if (*p == '@') { + // auth = <user-info@server-info> + rv = ParseUserInfo(auth, p - auth, usernamePos, usernameLen, passwordPos, + passwordLen); + if (NS_FAILED(rv)) return rv; + rv = ParseServerInfo(p + 1, authLen - (p - auth + 1), hostnamePos, + hostnameLen, port); + if (NS_FAILED(rv)) return rv; + OFFSET_RESULT(hostname, p + 1 - auth); + + // malformed if has a username or password + // but no host info, such as: http://u:p@/ + if ((usernamePos || passwordPos) && (!hostnamePos || !*hostnameLen)) { + return NS_ERROR_MALFORMED_URI; + } + } else { + // auth = <server-info> + SET_RESULT(username, 0, -1); + SET_RESULT(password, 0, -1); + rv = ParseServerInfo(auth, authLen, hostnamePos, hostnameLen, port); + if (NS_FAILED(rv)) return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +nsAuthURLParser::ParseUserInfo(const char* userinfo, int32_t userinfoLen, + uint32_t* usernamePos, int32_t* usernameLen, + uint32_t* passwordPos, int32_t* passwordLen) { + if (NS_WARN_IF(!userinfo)) { + return NS_ERROR_INVALID_POINTER; + } + + if (userinfoLen < 0) userinfoLen = strlen(userinfo); + + if (userinfoLen == 0) { + SET_RESULT(username, 0, -1); + SET_RESULT(password, 0, -1); + return NS_OK; + } + + const char* p = (const char*)memchr(userinfo, ':', userinfoLen); + if (p) { + // userinfo = <username:password> + SET_RESULT(username, 0, p - userinfo); + SET_RESULT(password, p - userinfo + 1, userinfoLen - (p - userinfo + 1)); + } else { + // userinfo = <username> + SET_RESULT(username, 0, userinfoLen); + SET_RESULT(password, 0, -1); + } + return NS_OK; +} + +NS_IMETHODIMP +nsAuthURLParser::ParseServerInfo(const char* serverinfo, int32_t serverinfoLen, + uint32_t* hostnamePos, int32_t* hostnameLen, + int32_t* port) { + if (NS_WARN_IF(!serverinfo)) { + return NS_ERROR_INVALID_POINTER; + } + + if (serverinfoLen < 0) serverinfoLen = strlen(serverinfo); + + if (serverinfoLen == 0) { + SET_RESULT(hostname, 0, 0); + if (port) *port = -1; + return NS_OK; + } + + // search backwards for a ':' but stop on ']' (IPv6 address literal + // delimiter). check for illegal characters in the hostname. + const char* p = serverinfo + serverinfoLen - 1; + const char *colon = nullptr, *bracket = nullptr; + for (; p > serverinfo; --p) { + switch (*p) { + case ']': + bracket = p; + break; + case ':': + if (bracket == nullptr) colon = p; + break; + case ' ': + // hostname must not contain a space + return NS_ERROR_MALFORMED_URI; + } + } + + if (colon) { + // serverinfo = <hostname:port> + SET_RESULT(hostname, 0, colon - serverinfo); + if (port) { + // XXX unfortunately ToInteger is not defined for substrings + nsAutoCString buf(colon + 1, serverinfoLen - (colon + 1 - serverinfo)); + if (buf.Length() == 0) { + *port = -1; + } else { + const char* nondigit = NS_strspnp("0123456789", buf.get()); + if (nondigit && *nondigit) return NS_ERROR_MALFORMED_URI; + + nsresult err; + *port = buf.ToInteger(&err); + if (NS_FAILED(err) || *port < 0 || + *port > std::numeric_limits<uint16_t>::max()) { + return NS_ERROR_MALFORMED_URI; + } + } + } + } else { + // serverinfo = <hostname> + SET_RESULT(hostname, 0, serverinfoLen); + if (port) *port = -1; + } + + // In case of IPv6 address check its validity + if (*hostnameLen > 1 && *(serverinfo + *hostnamePos) == '[' && + *(serverinfo + *hostnamePos + *hostnameLen - 1) == ']' && + !net_IsValidIPv6Addr( + Substring(serverinfo + *hostnamePos + 1, *hostnameLen - 2))) { + return NS_ERROR_MALFORMED_URI; + } + + return NS_OK; +} + +void nsAuthURLParser::ParseAfterScheme(const char* spec, int32_t specLen, + uint32_t* authPos, int32_t* authLen, + uint32_t* pathPos, int32_t* pathLen) { + MOZ_ASSERT(specLen >= 0, "unexpected"); + + uint32_t nslash = CountConsecutiveSlashes(spec, specLen); + + // search for the end of the authority section + const char* end = spec + specLen; + const char* p; + for (p = spec + nslash; p < end; ++p) { + if (*p == '/' || *p == '?' || *p == '#') break; + } + if (p < end) { + // spec = [/]<auth><path> + SET_RESULT(auth, nslash, p - (spec + nslash)); + SET_RESULT(path, p - spec, specLen - (p - spec)); + } else { + // spec = [/]<auth> + SET_RESULT(auth, nslash, specLen - nslash); + SET_RESULT(path, 0, -1); + } +} + +//---------------------------------------------------------------------------- +// nsStdURLParser implementation +//---------------------------------------------------------------------------- + +void nsStdURLParser::ParseAfterScheme(const char* spec, int32_t specLen, + uint32_t* authPos, int32_t* authLen, + uint32_t* pathPos, int32_t* pathLen) { + MOZ_ASSERT(specLen >= 0, "unexpected"); + + uint32_t nslash = CountConsecutiveSlashes(spec, specLen); + + // search for the end of the authority section + const char* end = spec + specLen; + const char* p; + for (p = spec + nslash; p < end; ++p) { + if (strchr("/?#;", *p)) break; + } + switch (nslash) { + case 0: + case 2: + if (p < end) { + // spec = (//)<auth><path> + SET_RESULT(auth, nslash, p - (spec + nslash)); + SET_RESULT(path, p - spec, specLen - (p - spec)); + } else { + // spec = (//)<auth> + SET_RESULT(auth, nslash, specLen - nslash); + SET_RESULT(path, 0, -1); + } + break; + case 1: + // spec = /<path> + SET_RESULT(auth, 0, -1); + SET_RESULT(path, 0, specLen); + break; + default: + // spec = ///[/]<path> + SET_RESULT(auth, 2, 0); + SET_RESULT(path, 2, specLen - 2); + } +} diff --git a/netwerk/base/nsURLParsers.h b/netwerk/base/nsURLParsers.h new file mode 100644 index 0000000000..3143022b90 --- /dev/null +++ b/netwerk/base/nsURLParsers.h @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsURLParsers_h__ +#define nsURLParsers_h__ + +#include "nsIURLParser.h" +#include "mozilla/Attributes.h" + +//---------------------------------------------------------------------------- +// base class for url parsers +//---------------------------------------------------------------------------- + +class nsBaseURLParser : public nsIURLParser { + public: + NS_DECL_NSIURLPARSER + + nsBaseURLParser() = default; + + protected: + // implemented by subclasses + virtual void ParseAfterScheme(const char* spec, int32_t specLen, + uint32_t* authPos, int32_t* authLen, + uint32_t* pathPos, int32_t* pathLen) = 0; +}; + +//---------------------------------------------------------------------------- +// an url parser for urls that do not have an authority section +// +// eg. file:foo/bar.txt +// file:/foo/bar.txt (treated equivalently) +// file:///foo/bar.txt +// +// eg. file:////foo/bar.txt (UNC-filepath = \\foo\bar.txt) +// +// XXX except in this case: +// file://foo/bar.txt (the authority "foo" is ignored) +//---------------------------------------------------------------------------- + +class nsNoAuthURLParser final : public nsBaseURLParser { + ~nsNoAuthURLParser() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + +#if defined(XP_WIN) + NS_IMETHOD ParseFilePath(const char*, int32_t, uint32_t*, int32_t*, uint32_t*, + int32_t*, uint32_t*, int32_t*) override; +#endif + + NS_IMETHOD ParseAuthority(const char* auth, int32_t authLen, + uint32_t* usernamePos, int32_t* usernameLen, + uint32_t* passwordPos, int32_t* passwordLen, + uint32_t* hostnamePos, int32_t* hostnameLen, + int32_t* port) override; + + void ParseAfterScheme(const char* spec, int32_t specLen, uint32_t* authPos, + int32_t* authLen, uint32_t* pathPos, + int32_t* pathLen) override; +}; + +//---------------------------------------------------------------------------- +// an url parser for urls that must have an authority section +// +// eg. http:www.foo.com/bar.html +// http:/www.foo.com/bar.html +// http://www.foo.com/bar.html (treated equivalently) +// http:///www.foo.com/bar.html +//---------------------------------------------------------------------------- + +class nsAuthURLParser : public nsBaseURLParser { + protected: + virtual ~nsAuthURLParser() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD ParseAuthority(const char* auth, int32_t authLen, + uint32_t* usernamePos, int32_t* usernameLen, + uint32_t* passwordPos, int32_t* passwordLen, + uint32_t* hostnamePos, int32_t* hostnameLen, + int32_t* port) override; + + NS_IMETHOD ParseUserInfo(const char* userinfo, int32_t userinfoLen, + uint32_t* usernamePos, int32_t* usernameLen, + uint32_t* passwordPos, + int32_t* passwordLen) override; + + NS_IMETHOD ParseServerInfo(const char* serverinfo, int32_t serverinfoLen, + uint32_t* hostnamePos, int32_t* hostnameLen, + int32_t* port) override; + + void ParseAfterScheme(const char* spec, int32_t specLen, uint32_t* authPos, + int32_t* authLen, uint32_t* pathPos, + int32_t* pathLen) override; +}; + +//---------------------------------------------------------------------------- +// an url parser for urls that may or may not have an authority section +// +// eg. http:www.foo.com (www.foo.com is authority) +// http:www.foo.com/bar.html (www.foo.com is authority) +// http:/www.foo.com/bar.html (www.foo.com is part of file path) +// http://www.foo.com/bar.html (www.foo.com is authority) +// http:///www.foo.com/bar.html (www.foo.com is part of file path) +//---------------------------------------------------------------------------- + +class nsStdURLParser : public nsAuthURLParser { + virtual ~nsStdURLParser() = default; + + public: + void ParseAfterScheme(const char* spec, int32_t specLen, uint32_t* authPos, + int32_t* authLen, uint32_t* pathPos, + int32_t* pathLen) override; +}; + +#endif // nsURLParsers_h__ diff --git a/netwerk/base/rust-helper/Cargo.toml b/netwerk/base/rust-helper/Cargo.toml new file mode 100644 index 0000000000..370d420182 --- /dev/null +++ b/netwerk/base/rust-helper/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "netwerk_helper" +version = "0.0.1" +authors = ["Jeff Hemphill <jthemphill@mozilla.com>"] +license = "MPL-2.0" + +[dependencies] +nserror = { path = "../../../xpcom/rust/nserror" } +nsstring = { path = "../../../xpcom/rust/nsstring" } +thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } diff --git a/netwerk/base/rust-helper/cbindgen.toml b/netwerk/base/rust-helper/cbindgen.toml new file mode 100644 index 0000000000..1e5e235576 --- /dev/null +++ b/netwerk/base/rust-helper/cbindgen.toml @@ -0,0 +1,18 @@ +header = """/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */""" +include_guard = "mozilla_net_rustHelper_h" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" +namespaces = ["mozilla", "net"] +includes = ["nsError.h", "nsString.h"] + +[export] +item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"] + +[export.rename] +"ThinVec" = "nsTArray" diff --git a/netwerk/base/rust-helper/moz.build b/netwerk/base/rust-helper/moz.build new file mode 100644 index 0000000000..1f7512ecf9 --- /dev/null +++ b/netwerk/base/rust-helper/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if CONFIG["COMPILE_ENVIRONMENT"]: + CbindgenHeader("rust_helper.h", inputs=["/netwerk/base/rust-helper"]) + + EXPORTS.mozilla.net += [ + "!rust_helper.h", + ] diff --git a/netwerk/base/rust-helper/src/lib.rs b/netwerk/base/rust-helper/src/lib.rs new file mode 100644 index 0000000000..7a97736e50 --- /dev/null +++ b/netwerk/base/rust-helper/src/lib.rs @@ -0,0 +1,360 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate nserror; +use self::nserror::*; + +extern crate nsstring; +use self::nsstring::{nsACString, nsCString}; + +extern crate thin_vec; +use self::thin_vec::ThinVec; + +use std::fs::File; +use std::io::{self, BufRead}; +use std::net::Ipv4Addr; + +/// HTTP leading whitespace, defined in netwerk/protocol/http/nsHttp.h +static HTTP_LWS: &'static [u8] = &[' ' as u8, '\t' as u8]; + +/// Trim leading whitespace, trailing whitespace, and quality-value +/// from a token. +fn trim_token(token: &[u8]) -> &[u8] { + // Trim left whitespace + let ltrim = token + .iter() + .take_while(|c| HTTP_LWS.iter().any(|ws| &ws == c)) + .count(); + + // Trim right whitespace + // remove "; q=..." if present + let rtrim = token[ltrim..] + .iter() + .take_while(|c| **c != (';' as u8) && HTTP_LWS.iter().all(|ws| ws != *c)) + .count(); + + &token[ltrim..ltrim + rtrim] +} + +#[no_mangle] +/// Allocates an nsACString that contains a ISO 639 language list +/// notated with HTTP "q" values for output with an HTTP Accept-Language +/// header. Previous q values will be stripped because the order of +/// the langs implies the q value. The q values are calculated by dividing +/// 1.0 amongst the number of languages present. +/// +/// Ex: passing: "en, ja" +/// returns: "en,ja;q=0.5" +/// +/// passing: "en, ja, fr_CA" +/// returns: "en,ja;q=0.7,fr_CA;q=0.3" +pub extern "C" fn rust_prepare_accept_languages<'a, 'b>( + i_accept_languages: &'a nsACString, + o_accept_languages: &'b mut nsACString, +) -> nsresult { + if i_accept_languages.is_empty() { + return NS_OK; + } + + let make_tokens = || { + i_accept_languages + .split(|c| *c == (',' as u8)) + .map(|token| trim_token(token)) + .filter(|token| token.len() != 0) + }; + + let n = make_tokens().count(); + + for (count_n, i_token) in make_tokens().enumerate() { + // delimiter if not first item + if count_n != 0 { + o_accept_languages.append(","); + } + + let token_pos = o_accept_languages.len(); + o_accept_languages.append(&i_token as &[u8]); + + { + let o_token = o_accept_languages.to_mut(); + canonicalize_language_tag(&mut o_token[token_pos..]); + } + + // Divide the quality-values evenly among the languages. + let q = 1.0 - count_n as f32 / n as f32; + + let u: u32 = ((q + 0.005) * 100.0) as u32; + // Only display q-value if less than 1.00. + if u < 100 { + // With a small number of languages, one decimal place is + // enough to prevent duplicate q-values. + // Also, trailing zeroes do not add any information, so + // they can be removed. + if n < 10 || u % 10 == 0 { + let u = (u + 5) / 10; + o_accept_languages.append(&format!(";q=0.{}", u)); + } else { + // Values below 10 require zero padding. + o_accept_languages.append(&format!(";q=0.{:02}", u)); + } + } + } + + NS_OK +} + +/// Defines a consistent capitalization for a given language string. +/// +/// # Arguments +/// * `token` - a narrow char slice describing a language. +/// +/// Valid language tags are of the form +/// "*", "fr", "en-US", "es-419", "az-Arab", "x-pig-latin", "man-Nkoo-GN" +/// +/// Language tags are defined in the +/// [rfc5646](https://tools.ietf.org/html/rfc5646) spec. According to +/// the spec: +/// +/// > At all times, language tags and their subtags, including private +/// > use and extensions, are to be treated as case insensitive: there +/// > exist conventions for the capitalization of some of the subtags, +/// > but these MUST NOT be taken to carry meaning. +/// +/// So why is this code even here? See bug 1108183, I guess. +fn canonicalize_language_tag(token: &mut [u8]) { + for c in token.iter_mut() { + *c = c.to_ascii_lowercase(); + } + + let sub_tags = token.split_mut(|c| *c == ('-' as u8)); + for (i, sub_tag) in sub_tags.enumerate() { + if i == 0 { + // ISO 639-1 language code, like the "en" in "en-US" + continue; + } + + match sub_tag.len() { + // Singleton tag, like "x" or "i". These signify a + // non-standard language, so we stop capitalizing after + // these. + 1 => break, + // ISO 3166-1 Country code, like "US" + 2 => { + sub_tag[0] = sub_tag[0].to_ascii_uppercase(); + sub_tag[1] = sub_tag[1].to_ascii_uppercase(); + } + // ISO 15924 script code, like "Nkoo" + 4 => { + sub_tag[0] = sub_tag[0].to_ascii_uppercase(); + } + _ => {} + }; + } +} + +#[no_mangle] +pub extern "C" fn rust_net_is_valid_ipv4_addr<'a>(addr: &'a nsACString) -> bool { + is_valid_ipv4_addr(addr) +} + +#[inline] +fn try_apply_digit(current_octet: u8, digit_to_apply: u8) -> Option<u8> { + current_octet.checked_mul(10)?.checked_add(digit_to_apply) +} + +pub fn is_valid_ipv4_addr<'a>(addr: &'a [u8]) -> bool { + let mut current_octet: Option<u8> = None; + let mut dots: u8 = 0; + for c in addr { + let c = *c as char; + match c { + '.' => { + match current_octet { + None => { + // starting an octet with a . is not allowed + return false; + } + Some(_) => { + dots = dots + 1; + current_octet = None; + } + } + } + // The character is not a digit + no_digit if no_digit.to_digit(10).is_none() => { + return false; + } + digit => { + match current_octet { + None => { + // Unwrap is sound because it has been checked in the previous arm + current_octet = Some(digit.to_digit(10).unwrap() as u8); + } + Some(octet) => { + if let Some(0) = current_octet { + // Leading 0 is not allowed + return false; + } + if let Some(applied) = + try_apply_digit(octet, digit.to_digit(10).unwrap() as u8) + { + current_octet = Some(applied); + } else { + // Multiplication or Addition overflowed + return false; + } + } + } + } + } + } + dots == 3 && current_octet.is_some() +} + +#[no_mangle] +pub extern "C" fn rust_net_is_valid_ipv6_addr<'a>(addr: &'a nsACString) -> bool { + is_valid_ipv6_addr(addr) +} + +#[inline(always)] +fn fast_is_hex_digit(c: u8) -> bool { + match c { + b'0'..=b'9' => true, + b'a'..=b'f' => true, + b'A'..=b'F' => true, + _ => false, + } +} + +pub fn is_valid_ipv6_addr<'a>(addr: &'a [u8]) -> bool { + let mut double_colon = false; + let mut colon_before = false; + let mut digits: u8 = 0; + let mut blocks: u8 = 0; + + // The smallest ipv6 is unspecified (::) + // The IP starts with a single colon + if addr.len() < 2 || addr[0] == b':' && addr[1] != b':' { + return false; + } + //Enumerate with an u8 for cache locality + for (i, c) in (0u8..).zip(addr) { + match c { + maybe_digit if fast_is_hex_digit(*maybe_digit) => { + // Too many digits in the block + if digits == 4 { + return false; + } + colon_before = false; + digits += 1; + } + b':' => { + // Too many columns + if double_colon && colon_before || blocks == 8 { + return false; + } + if !colon_before { + if digits != 0 { + blocks += 1; + } + digits = 0; + colon_before = true; + } else if !double_colon { + double_colon = true; + } + } + b'.' => { + // IPv4 from the last block + if is_valid_ipv4_addr(&addr[(i - digits) as usize..]) { + return double_colon && blocks < 6 || !double_colon && blocks == 6; + } + return false; + } + _ => { + // Invalid character + return false; + } + } + } + if colon_before && !double_colon { + // The IP ends with a single colon + return false; + } + if digits != 0 { + blocks += 1; + } + + double_colon && blocks < 8 || !double_colon && blocks == 8 +} + +#[no_mangle] +pub extern "C" fn rust_net_is_valid_scheme_char(a_char: u8) -> bool { + is_valid_scheme_char(a_char) +} + +#[no_mangle] +pub extern "C" fn rust_net_is_valid_scheme<'a>(scheme: &'a nsACString) -> bool { + if scheme.is_empty() { + return false; + } + + // first char must be alpha + if !scheme[0].is_ascii_alphabetic() { + return false; + } + + scheme[1..] + .iter() + .all(|a_char| is_valid_scheme_char(*a_char)) +} + +fn is_valid_scheme_char(a_char: u8) -> bool { + a_char.is_ascii_alphanumeric() || a_char == b'+' || a_char == b'.' || a_char == b'-' +} + +pub type ParsingCallback = extern "C" fn(&ThinVec<nsCString>) -> bool; + +#[no_mangle] +pub extern "C" fn rust_parse_etc_hosts<'a>(path: &'a nsACString, callback: ParsingCallback) { + let file = match File::open(&*path.to_utf8()) { + Ok(file) => io::BufReader::new(file), + Err(..) => return, + }; + + let mut array = ThinVec::new(); + for line in file.lines() { + let line = match line { + Ok(l) => l, + Err(..) => continue, + }; + + let mut iter = line.split('#').next().unwrap().split_whitespace(); + iter.next(); // skip the IP + + array.extend( + iter.filter(|host| { + // Make sure it's a valid domain + let invalid = [ + '\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']', + ]; + host.parse::<Ipv4Addr>().is_err() && !host.contains(&invalid[..]) + }) + .map(nsCString::from), + ); + + // /etc/hosts files can be huge. To make sure we don't block shutdown + // for every 100 domains that we parse we call the callback passing the + // domains and see if we should keep parsing. + if array.len() > 100 { + let keep_going = callback(&array); + array.clear(); + if !keep_going { + break; + } + } + } + + if !array.is_empty() { + callback(&array); + } +} |