diff options
Diffstat (limited to 'netwerk/base')
304 files changed, 67756 insertions, 0 deletions
diff --git a/netwerk/base/ADivertableParentChannel.h b/netwerk/base/ADivertableParentChannel.h new file mode 100644 index 000000000..2102d3d83 --- /dev/null +++ b/netwerk/base/ADivertableParentChannel.h @@ -0,0 +1,45 @@ +/* -*- 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 _adivertablechannelparent_h_ +#define _adivertablechannelparent_h_ + +#include "nsISupports.h" + +class nsIStreamListener; + +namespace mozilla { +namespace net { + +// To be implemented by a channel's parent actors, e.g. HttpChannelParent +// and FTPChannelParent. Used by ChannelDiverterParent to divert +// nsIStreamListener callbacks from the child process to a new +// listener in the parent process. +class ADivertableParentChannel : public nsISupports +{ +public: + // Called by ChannelDiverterParent::DivertTo(nsIStreamListener*). + // The listener should now be used to received nsIStreamListener callbacks, + // i.e. OnStartRequest, OnDataAvailable and OnStopRequest, as if it had been + // passed to AsyncOpen for the channel. A reference to the listener will be + // added and kept until OnStopRequest has completed. + virtual void DivertTo(nsIStreamListener *aListener) = 0; + + // Called to suspend parent channel in ChannelDiverterParent constructor. + virtual nsresult SuspendForDiversion() = 0; + + // While messages are diverted back from the child to the parent calls to + // suspend/resume the channel must also suspend/resume the message diversion. + // These two functions will be called by nsHttpChannel and nsFtpChannel + // Suspend()/Resume() functions. + virtual nsresult SuspendMessageDiversion() = 0; + virtual nsresult ResumeMessageDiversion() = 0; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/ARefBase.h b/netwerk/base/ARefBase.h new file mode 100644 index 000000000..0b2726c43 --- /dev/null +++ b/netwerk/base/ARefBase.h @@ -0,0 +1,32 @@ +/* -*- 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 "nscore.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() {} + virtual ~ARefBase() {} + + NS_IMETHOD_ (MozExternalRefCountType) AddRef() = 0; + NS_IMETHOD_ (MozExternalRefCountType) Release() = 0; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/ArrayBufferInputStream.cpp b/netwerk/base/ArrayBufferInputStream.cpp new file mode 100644 index 000000000..d70c45110 --- /dev/null +++ b/netwerk/base/ArrayBufferInputStream.cpp @@ -0,0 +1,120 @@ +/* -*- 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 "jsapi.h" +#include "jsfriendapi.h" + +NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream, nsIInputStream); + +ArrayBufferInputStream::ArrayBufferInputStream() +: mBufferLength(0) +, mPos(0) +, mClosed(false) +{ +} + +NS_IMETHODIMP +ArrayBufferInputStream::SetData(JS::Handle<JS::Value> aBuffer, + uint32_t aByteOffset, + uint32_t aLength, + JSContext* aCx) +{ + if (!aBuffer.isObject()) { + return NS_ERROR_FAILURE; + } + JS::RootedObject arrayBuffer(aCx, &aBuffer.toObject()); + if (!JS_IsArrayBufferObject(arrayBuffer)) { + return NS_ERROR_FAILURE; + } + + uint32_t buflen = JS_GetArrayBufferByteLength(arrayBuffer); + uint32_t offset = std::min(buflen, aByteOffset); + mBufferLength = std::min(buflen - offset, aLength); + + mArrayBuffer = mozilla::MakeUnique<char[]>(mBufferLength); + + JS::AutoCheckCannotGC nogc; + bool isShared; + char* src = (char*) JS_GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset; + memcpy(&mArrayBuffer[0], src, mBufferLength); + 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::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, &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 000000000..ddd3e7cef --- /dev/null +++ b/netwerk/base/ArrayBufferInputStream.h @@ -0,0 +1,38 @@ +/* -*- 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" + +#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(); + NS_DECL_ISUPPORTS + NS_DECL_NSIARRAYBUFFERINPUTSTREAM + NS_DECL_NSIINPUTSTREAM + +private: + virtual ~ArrayBufferInputStream() {} + mozilla::UniquePtr<char[]> mArrayBuffer; + uint32_t mBufferLength; + uint32_t mPos; + bool mClosed; +}; + +#endif // ArrayBufferInputStream_h diff --git a/netwerk/base/AutoClose.h b/netwerk/base/AutoClose.h new file mode 100644 index 000000000..43ab27133 --- /dev/null +++ b/netwerk/base/AutoClose.h @@ -0,0 +1,77 @@ +/* -*- 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 { + +// Like an nsAutoPtr 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) + { + already_AddRefed<T> other = rhs.forget(); + TakeOverInternal(&other); + } + + void CloseAndRelease() + { + TakeOverInternal(nullptr); + } + +private: + void TakeOverInternal(already_AddRefed<T> *aOther) + { + nsCOMPtr<T> ptr; + { + MutexAutoLock lock(mMutex); + ptr.swap(mPtr); + if (aOther) { + mPtr = *aOther; + } + } + + if (ptr) { + ptr->Close(); + } + } + + void operator=(const AutoClose<T> &) = delete; + AutoClose(const AutoClose<T> &) = delete; + + nsCOMPtr<T> mPtr; + Mutex mMutex; +}; + +} // 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 000000000..e4bc05826 --- /dev/null +++ b/netwerk/base/BackgroundFileSaver.cpp @@ -0,0 +1,1273 @@ +/* -*- 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/Casting.h" +#include "mozilla/Logging.h" +#include "mozilla/Telemetry.h" +#include "nsCOMArray.h" +#include "nsIAsyncInputStream.h" +#include "nsIFile.h" +#include "nsIMutableArray.h" +#include "nsIPipe.h" +#include "nsIX509Cert.h" +#include "nsIX509CertDB.h" +#include "nsIX509CertList.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) + : 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() +: mControlThread(nullptr) +, mWorkerThread(nullptr) +, mPipeOutputStream(nullptr) +, mPipeInputStream(nullptr) +, mObserver(nullptr) +, mLock("BackgroundFileSaver.mLock") +, mWorkerThreadAttentionRequested(false) +, mFinishRequested(false) +, mComplete(false) +, mStatus(NS_OK) +, mAppend(false) +, mInitialTarget(nullptr) +, mInitialTargetKeepPartial(false) +, mRenamedTarget(nullptr) +, mRenamedTargetKeepPartial(false) +, mAsyncCopyContext(nullptr) +, mSha256Enabled(false) +, mSignatureInfoEnabled(false) +, mActualTarget(nullptr) +, mActualTargetKeepPartial(false) +, mDigestContext(nullptr) +{ + LOG(("Created BackgroundFileSaver [this = %p]", this)); +} + +BackgroundFileSaver::~BackgroundFileSaver() +{ + LOG(("Destroying BackgroundFileSaver [this = %p]", this)); + nsNSSShutDownPreventionLock lock; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); +} + +void +BackgroundFileSaver::destructorSafeDestroyNSSReference() +{ + mDigestContext = nullptr; +} + +void +BackgroundFileSaver::virtualDestroyNSSReference() +{ + destructorSafeDestroyNSSReference(); +} + +// Called on the control thread. +nsresult +BackgroundFileSaver::Init() +{ + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); + + nsresult rv; + + rv = NS_NewPipe2(getter_AddRefs(mPipeInputStream), + getter_AddRefs(mPipeOutputStream), true, true, 0, + HasInfiniteBuffer() ? UINT32_MAX : 0); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetCurrentThread(getter_AddRefs(mControlThread)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewThread(getter_AddRefs(mWorkerThread)); + 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 = mObserver; + NS_IF_ADDREF(*aObserver); + 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); + mSha256Enabled = true; + 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); + mSignatureInfoEnabled = true; + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaver::GetSignatureInfo(nsIArray** 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; + } + nsCOMPtr<nsIMutableArray> sigArray = do_CreateInstance(NS_ARRAY_CONTRACTID); + for (int i = 0; i < mSignatureInfo.Count(); ++i) { + sigArray->AppendElement(mSignatureInfo[i], false); + } + *aSignatureInfo = sigArray; + NS_IF_ADDREF(*aSignatureInfo); + 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) { + // Copy is not in progress, post an event to handle the change manually. + rv = mWorkerThread->Dispatch(NewRunnableMethod(this, + &BackgroundFileSaver::ProcessAttention), + NS_DISPATCH_NORMAL); + 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) +{ + BackgroundFileSaver *self = (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(); + + // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object + // alive even if other references disappeared. At this point, we've finished + // using the object and can safely release our reference. + NS_RELEASE(self); +} + +// 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. + 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); + } + + // Now we can update the actual target file name. + 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 = mControlThread->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 context if requested and NSS hasn't been shut down. + if (sha256Enabled && !mDigestContext) { + nsNSSShutDownPreventionLock lock; + if (!isAlreadyShutDown()) { + mDigestContext = UniquePK11Context( + PK11_CreateDigestContext(SEC_OID_SHA256)); + NS_ENSURE_TRUE(mDigestContext, NS_ERROR_OUT_OF_MEMORY); + } + } + + // 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 (mDigestContext && 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); + + 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; + } + + nsNSSShutDownPreventionLock lock; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = MapSECStatus( + PK11_DigestOp(mDigestContext.get(), + BitwiseCast<unsigned char*, char*>(buffer), + count)); + NS_ENSURE_SUCCESS(rv, rv); + } + + 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); + + outputStream = NS_BufferOutputStream(outputStream, BUFFERED_IO_SIZE); + if (!outputStream) { + return NS_ERROR_FAILURE; + } + + // Wrap the output stream so that it feeds the digest context if needed. + if (mDigestContext) { + // No need to acquire the NSS lock here, DigestOutputStream must acquire it + // in any case before each asynchronous write. Constructing the + // DigestOutputStream cannot fail. Passing mDigestContext to + // DigestOutputStream is safe, because BackgroundFileSaver always outlives + // the outputStream. BackgroundFileSaver is reference-counted before the + // call to AsyncCopy, and mDigestContext is never destroyed before + // AsyncCopyCallback. + outputStream = new DigestOutputStream(outputStream, mDigestContext.get()); + } + + // 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, mWorkerThread, + 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; + + MOZ_ASSERT(!mAsyncCopyContext, + "Should not be copying when checking completion conditions."); + + bool failed = true; + { + MutexAutoLock lock(mLock); + + 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 && mDigestContext) { + nsNSSShutDownPreventionLock lock; + if (!isAlreadyShutDown()) { + Digest d; + rv = d.End(SEC_OID_SHA256, mDigestContext); + if (NS_SUCCEEDED(rv)) { + MutexAutoLock lock(mLock); + mSha256 = + nsDependentCSubstring(BitwiseCast<char*, unsigned char*>(d.get().data), + d.get().len); + } + } + } + + // 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(mControlThread->Dispatch(NewRunnableMethod(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); + } + + // 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. + mWorkerThread->Shutdown(); + + 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"); + + nsNSSShutDownPreventionLock nssLock; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + { + MutexAutoLock lock(mLock); + if (!mSignatureInfoEnabled) { + return NS_OK; + } + } + nsresult rv; + nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); +#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; + } + nsCOMPtr<nsIX509CertList> nssCertList = + do_CreateInstance(NS_X509CERTLIST_CONTRACTID); + if (!nssCertList) { + break; + } + 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; + } + nsCOMPtr<nsIX509Cert> nssCert = nullptr; + rv = certDB->ConstructX509( + reinterpret_cast<char *>( + certChainElement->pCertContext->pbCertEncoded), + certChainElement->pCertContext->cbCertEncoded, + getter_AddRefs(nssCert)); + if (!nssCert) { + extractionSuccess = false; + LOG(("Couldn't create NSS cert [this = %p]", this)); + break; + } + nssCertList->AddCert(nssCert); + nsString subjectName; + nssCert->GetSubjectName(subjectName); + LOG(("Adding cert %s [this = %p]", + NS_ConvertUTF16toUTF8(subjectName).get(), this)); + } + if (extractionSuccess) { + mSignatureInfo.AppendObject(nssCertList); + } + } + } + // 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() +: BackgroundFileSaver() +, mAsyncWaitCallback(nullptr) +{ +} + +BackgroundFileSaverOutputStream::~BackgroundFileSaverOutputStream() +{ +} + +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::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) + +BackgroundFileSaverStreamListener::BackgroundFileSaverStreamListener() +: BackgroundFileSaver() +, mSuspensionLock("BackgroundFileSaverStreamListener.mSuspensionLock") +, mReceivedTooMuchData(false) +, mRequest(nullptr) +, mRequestSuspended(false) +{ +} + +BackgroundFileSaverStreamListener::~BackgroundFileSaverStreamListener() +{ +} + +bool +BackgroundFileSaverStreamListener::HasInfiniteBuffer() +{ + return true; +} + +nsAsyncCopyProgressFun +BackgroundFileSaverStreamListener::GetProgressCallback() +{ + return AsyncCopyProgressCallback; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + NS_ENSURE_ARG(aRequest); + + return NS_OK; +} + +NS_IMETHODIMP +BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + 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, + nsISupports *aContext, + 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->mControlThread->Dispatch(NewRunnableMethod(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, + PK11Context* aContext) : + mOutputStream(aStream) + , mDigestContext(aContext) +{ + MOZ_ASSERT(mDigestContext, "Can't have null digest context"); + MOZ_ASSERT(mOutputStream, "Can't have null output stream"); +} + +DigestOutputStream::~DigestOutputStream() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + shutdown(ShutdownCalledFrom::Object); +} + +NS_IMETHODIMP +DigestOutputStream::Close() +{ + return mOutputStream->Close(); +} + +NS_IMETHODIMP +DigestOutputStream::Flush() +{ + return mOutputStream->Flush(); +} + +NS_IMETHODIMP +DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval) +{ + nsNSSShutDownPreventionLock lock; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = MapSECStatus( + PK11_DigestOp(mDigestContext, + 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 000000000..1fa9268f8 --- /dev/null +++ b/netwerk/base/BackgroundFileSaver.h @@ -0,0 +1,418 @@ +/* -*- 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 "nsNSSShutDown.h" +#include "nsStreamUtils.h" +#include "nsString.h" + +class nsIAsyncInputStream; +class nsIThread; +class nsIX509CertList; + +namespace mozilla { +namespace net { + +class DigestOutputStream; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaver + +class BackgroundFileSaver : public nsIBackgroundFileSaver, + public nsNSSShutDownObject +{ +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(); + + /** + * Used by nsNSSShutDownList to manage nsNSSShutDownObjects. + */ + void virtualDestroyNSSReference() override; + + /** + * 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(); + + /** + * Helper function for managing NSS objects (mDigestContext). + */ + void destructorSafeDestroyNSSReference(); + + /** + * Thread that constructed this object. + */ + nsCOMPtr<nsIThread> mControlThread; + + /** + * Thread to which the actual input/output is delegated. + */ + nsCOMPtr<nsIThread> mWorkerThread; + + /** + * 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; + + /** + * True if the worker thread is already waiting to process a change in state. + */ + bool mWorkerThreadAttentionRequested; + + /** + * True if the operation should finish as soon as possibile. + */ + bool mFinishRequested; + + /** + * True if the operation completed, with either success or failure. + */ + bool mComplete; + + /** + * 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; + + /** + * True if we should append data to the initial target file, instead of + * overwriting it. + */ + bool mAppend; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * Whether or not to compute the hash. Must be set on the main thread before + * setTarget is called. + */ + bool mSha256Enabled; + + /** + * Store the signature info. + */ + nsCOMArray<nsIX509CertList> mSignatureInfo; + + /** + * Whether or not to extract the signature. Must be set on the main thread + * before setTarget is called. + */ + bool mSignatureInfoEnabled; + + ////////////////////////////////////////////////////////////////////////////// + //// 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; + + /** + * Used to calculate the file hash. This keeps state across file renames and + * is lazily initialized in ProcessStateChange. + */ + UniquePK11Context mDigestContext; + + ////////////////////////////////////////////////////////////////////////////// + //// 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(); + + /** + * Original callback provided to our AsyncWait wrapper. + */ + nsCOMPtr<nsIOutputStreamCallback> mAsyncWaitCallback; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// BackgroundFileSaverStreamListener. This class is instantiated by +// nsExternalHelperAppService, DownloadCore.jsm, and possibly others. + +class BackgroundFileSaverStreamListener final : public BackgroundFileSaver + , public nsIStreamListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + BackgroundFileSaverStreamListener(); + +protected: + virtual bool HasInfiniteBuffer() override; + virtual nsAsyncCopyProgressFun GetProgressCallback() override; + +private: + ~BackgroundFileSaverStreamListener(); + + /** + * Protects the state related to whether the request should be suspended. + */ + mozilla::Mutex mSuspensionLock; + + /** + * Whether we should suspend the request because we received too much data. + */ + bool mReceivedTooMuchData; + + /** + * Request for which we received too much data. This is populated when + * mReceivedTooMuchData becomes true for the first time. + */ + nsCOMPtr<nsIRequest> mRequest; + + /** + * Whether mRequest is currently suspended. + */ + bool mRequestSuspended; + + /** + * 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 nsNSSShutDownObject, + public nsIOutputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + // Constructor. Neither parameter may be null. The caller owns both. + DigestOutputStream(nsIOutputStream* outputStream, PK11Context* aContext); + + // We don't own any NSS objects here, so no need to clean up + void virtualDestroyNSSReference() override { } + +private: + ~DigestOutputStream(); + + // Calls to write are passed to this stream. + nsCOMPtr<nsIOutputStream> mOutputStream; + // Digest context used to compute the hash, owned by the caller. + PK11Context* mDigestContext; + + // Don't accidentally copy construct. + DigestOutputStream(const DigestOutputStream& d); +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/CaptivePortalService.cpp b/netwerk/base/CaptivePortalService.cpp new file mode 100644 index 000000000..f97fb41d7 --- /dev/null +++ b/netwerk/base/CaptivePortalService.cpp @@ -0,0 +1,366 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/Services.h" +#include "mozilla/Preferences.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsXULAppAPI.h" + +static const char16_t kInterfaceName[] = u"captive-portal-inteface"; + +static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login"; +static const char kAbortCaptivePortalLoginEvent[] = "captive-portal-login-abort"; +static const char kCaptivePortalLoginSuccessEvent[] = "captive-portal-login-success"; + +static const uint32_t kDefaultInterval = 60*1000; // check every 60 seconds + +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) + +CaptivePortalService::CaptivePortalService() + : mState(UNKNOWN) + , mStarted(false) + , mInitialized(false) + , mRequestInProgress(false) + , mEverBeenCaptive(false) + , mDelay(kDefaultInterval) + , mSlackCount(0) + , mMinInterval(kDefaultInterval) + , mMaxInterval(25*kDefaultInterval) + , mBackoffFactor(5.0) +{ + 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; + } + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + nsresult rv; + if (!mCaptivePortalDetector) { + mCaptivePortalDetector = + do_GetService("@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 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 = do_CreateInstance(NS_TIMER_CONTRACTID); + } + + 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); + } + + LOG(("Initialized CaptivePortalService\n")); + return NS_OK; +} + +nsresult +CaptivePortalService::Start() +{ + if (!mInitialized) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // Doesn't do anything if called in the content process. + return NS_OK; + } + + if (mStarted) { + return NS_OK; + } + + 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; + 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::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. + mState = LOCKED_PORTAL; + mLastChecked = TimeStamp::Now(); + mEverBeenCaptive = true; + } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) { + // The user has successfully logged in. We have connectivity. + mState = UNLOCKED_PORTAL; + mLastChecked = TimeStamp::Now(); + mSlackCount = 0; + mDelay = mMinInterval; + + RearmTimer(); + } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) { + // The login has been aborted + mState = UNKNOWN; + mLastChecked = TimeStamp::Now(); + mSlackCount = 0; + } + + // 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; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsICaptivePortalCallback +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Prepare() +{ + LOG(("CaptivePortalService::Prepare\n")); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + // 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) { + mState = UNLOCKED_PORTAL; + } else { + mState = NOT_CAPTIVE; + } + } + + mRequestInProgress = false; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/CaptivePortalService.h b/netwerk/base/CaptivePortalService.h new file mode 100644 index 000000000..dd15450dc --- /dev/null +++ b/netwerk/base/CaptivePortalService.h @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "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: + NS_DECL_ISUPPORTS + NS_DECL_NSICAPTIVEPORTALSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSICAPTIVEPORTALCALLBACK + + CaptivePortalService(); + nsresult Initialize(); + nsresult Start(); + nsresult Stop(); + + // 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: + virtual ~CaptivePortalService(); + nsresult PerformCheck(); + nsresult RearmTimer(); + + nsCOMPtr<nsICaptivePortalDetector> mCaptivePortalDetector; + int32_t mState; + + nsCOMPtr<nsITimer> mTimer; + bool mStarted; + bool mInitialized; + bool mRequestInProgress; + bool mEverBeenCaptive; + + uint32_t mDelay; + int32_t mSlackCount; + + uint32_t mMinInterval; + uint32_t mMaxInterval; + float mBackoffFactor; + + // 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/ChannelDiverterChild.cpp b/netwerk/base/ChannelDiverterChild.cpp new file mode 100644 index 000000000..d275783f8 --- /dev/null +++ b/netwerk/base/ChannelDiverterChild.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 "mozilla/net/ChannelDiverterChild.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "mozilla/net/HttpChannelChild.h" +#include "mozilla/net/FTPChannelChild.h" +#include "mozilla/net/PHttpChannelChild.h" +#include "mozilla/net/PFTPChannelChild.h" +#include "nsIDivertableChannel.h" + +namespace mozilla { +namespace net { + +ChannelDiverterChild::ChannelDiverterChild() +{ +} + +ChannelDiverterChild::~ChannelDiverterChild() +{ +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/ChannelDiverterChild.h b/netwerk/base/ChannelDiverterChild.h new file mode 100644 index 000000000..a92de2f11 --- /dev/null +++ b/netwerk/base/ChannelDiverterChild.h @@ -0,0 +1,26 @@ +/* -*- 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 _channeldiverterchild_h_ +#define _channeldiverterchild_h_ + +#include "mozilla/net/PChannelDiverterChild.h" + +namespace mozilla { +namespace net { + +class ChannelDiverterChild : + public PChannelDiverterChild +{ +public: + ChannelDiverterChild(); + virtual ~ChannelDiverterChild(); +}; + +} // namespace net +} // namespace mozilla + +#endif /* _channeldiverterchild_h_ */ diff --git a/netwerk/base/ChannelDiverterParent.cpp b/netwerk/base/ChannelDiverterParent.cpp new file mode 100644 index 000000000..3b66644ab --- /dev/null +++ b/netwerk/base/ChannelDiverterParent.cpp @@ -0,0 +1,75 @@ +/* -*- 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/ChannelDiverterParent.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "mozilla/net/HttpChannelParent.h" +#include "mozilla/net/FTPChannelParent.h" +#include "mozilla/net/PHttpChannelParent.h" +#include "mozilla/net/PFTPChannelParent.h" +#include "ADivertableParentChannel.h" + +namespace mozilla { +namespace net { + +ChannelDiverterParent::ChannelDiverterParent() +{ +} + +ChannelDiverterParent::~ChannelDiverterParent() +{ +} + +bool +ChannelDiverterParent::Init(const ChannelDiverterArgs& aArgs) +{ + switch (aArgs.type()) { + case ChannelDiverterArgs::THttpChannelDiverterArgs: + { + auto httpParent = static_cast<HttpChannelParent*>( + aArgs.get_HttpChannelDiverterArgs().mChannelParent()); + httpParent->SetApplyConversion(aArgs.get_HttpChannelDiverterArgs().mApplyConversion()); + + mDivertableChannelParent = + static_cast<ADivertableParentChannel*>(httpParent); + break; + } + case ChannelDiverterArgs::TPFTPChannelParent: + { + mDivertableChannelParent = static_cast<ADivertableParentChannel*>( + static_cast<FTPChannelParent*>(aArgs.get_PFTPChannelParent())); + break; + } + default: + NS_NOTREACHED("unknown ChannelDiverterArgs type"); + return false; + } + MOZ_ASSERT(mDivertableChannelParent); + + nsresult rv = mDivertableChannelParent->SuspendForDiversion(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + return true; +} + +void +ChannelDiverterParent::DivertTo(nsIStreamListener* newListener) +{ + MOZ_ASSERT(newListener); + MOZ_ASSERT(mDivertableChannelParent); + + mDivertableChannelParent->DivertTo(newListener); +} + +void +ChannelDiverterParent::ActorDestroy(ActorDestroyReason aWhy) +{ + // Implement me! Bug 1005179 +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/ChannelDiverterParent.h b/netwerk/base/ChannelDiverterParent.h new file mode 100644 index 000000000..047e1c68a --- /dev/null +++ b/netwerk/base/ChannelDiverterParent.h @@ -0,0 +1,40 @@ +/* -*- 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 _channeldiverterparent_h_ +#define _channeldiverterparent_h_ + +#include "mozilla/net/PChannelDiverterParent.h" + +class nsIStreamListener; + +namespace mozilla { +namespace net { + +class ChannelDiverterArgs; +class ADivertableParentChannel; + +class ChannelDiverterParent : + public PChannelDiverterParent +{ +public: + ChannelDiverterParent(); + virtual ~ChannelDiverterParent(); + + bool Init(const ChannelDiverterArgs& aArgs); + + void DivertTo(nsIStreamListener* newListener); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + +private: + RefPtr<ADivertableParentChannel> mDivertableChannelParent; +}; + +} // namespace net +} // namespace mozilla + +#endif /* _channeldiverterparent_h_ */ diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp new file mode 100644 index 000000000..f5d0880ae --- /dev/null +++ b/netwerk/base/Dashboard.cpp @@ -0,0 +1,921 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsHttp.h" +#include "nsICancelable.h" +#include "nsIDNSService.h" +#include "nsIDNSRecord.h" +#include "nsIInputStream.h" +#include "nsISocketTransport.h" +#include "nsIThread.h" +#include "nsProxyRelease.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "nsURLHelper.h" +#include "mozilla/Logging.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() + { + mTotalSent = 0; + mTotalRecv = 0; + mThread = nullptr; + } + + uint64_t mTotalSent; + uint64_t mTotalRecv; + nsTArray<SocketInfo> mData; + nsMainThreadPtrHandle<NetDashboardCallback> mCallback; + nsIThread *mThread; + +private: + virtual ~SocketData() + { + } +}; + +static void GetErrorString(nsresult rv, nsAString& errorString); + +NS_IMPL_ISUPPORTS0(SocketData) + + +class HttpData + : public nsISupports +{ + virtual ~HttpData() + { + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + HttpData() + { + mThread = nullptr; + } + + nsTArray<HttpRetParams> mData; + nsMainThreadPtrHandle<NetDashboardCallback> mCallback; + nsIThread *mThread; +}; + +NS_IMPL_ISUPPORTS0(HttpData) + + +class WebSocketRequest + : public nsISupports +{ + virtual ~WebSocketRequest() + { + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + WebSocketRequest() + { + mThread = nullptr; + } + + nsMainThreadPtrHandle<NetDashboardCallback> mCallback; + nsIThread *mThread; +}; + +NS_IMPL_ISUPPORTS0(WebSocketRequest) + + +class DnsData + : public nsISupports +{ + virtual ~DnsData() + { + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + DnsData() + { + mThread = nullptr; + } + + nsTArray<DNSCacheEntries> mData; + nsMainThreadPtrHandle<NetDashboardCallback> mCallback; + nsIThread *mThread; +}; + +NS_IMPL_ISUPPORTS0(DnsData) + + +class ConnectionData + : public nsITransportEventSink + , public nsITimerCallback +{ + virtual ~ConnectionData() + { + if (mTimer) { + mTimer->Cancel(); + } + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORTEVENTSINK + NS_DECL_NSITIMERCALLBACK + + void StartTimer(uint32_t aTimeout); + void StopTimer(); + + explicit ConnectionData(Dashboard *target) + { + mThread = nullptr; + mDashboard = target; + } + + nsCOMPtr<nsISocketTransport> mSocket; + nsCOMPtr<nsIInputStream> mStreamIn; + nsCOMPtr<nsITimer> mTimer; + nsMainThreadPtrHandle<NetDashboardCallback> mCallback; + nsIThread *mThread; + Dashboard *mDashboard; + + nsCString mHost; + uint32_t mPort; + const char *mProtocol; + uint32_t mTimeout; + + nsString mStatus; +}; + +NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback) + +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); + mThread->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>> + (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"); + mThread->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>> + (mDashboard, &Dashboard::GetConnectionStatus, this), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +void +ConnectionData::StartTimer(uint32_t aTimeout) +{ + if (!mTimer) { + mTimer = do_CreateInstance("@mozilla.org/timer;1"); + } + + 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() + { + } + +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 + : public nsIDNSListener +{ + virtual ~LookupHelper() + { + if (mCancel) { + mCancel->Cancel(NS_ERROR_ABORT); + } + } + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + LookupHelper() { + } + + nsresult ConstructAnswer(LookupArgument *aArgument); +public: + nsCOMPtr<nsICancelable> mCancel; + nsMainThreadPtrHandle<NetDashboardCallback> mCallback; + nsIThread *mThread; + nsresult mStatus; +}; + +NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener) + +NS_IMETHODIMP +LookupHelper::OnLookupComplete(nsICancelable *aRequest, + nsIDNSRecord *aRecord, nsresult aStatus) +{ + MOZ_ASSERT(aRequest == mCancel); + mCancel = nullptr; + mStatus = aStatus; + + RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this); + mThread->Dispatch(NewRunnableMethod<RefPtr<LookupArgument>> + (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(); + + if (NS_SUCCEEDED(mStatus)) { + dict.mAnswer = true; + bool hasMore; + aRecord->HasMore(&hasMore); + while (hasMore) { + nsString* nextAddress = addresses.AppendElement(fallible); + if (!nextAddress) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCString nextAddressASCII; + aRecord->GetNextAddrAsString(nextAddressASCII); + CopyASCIItoUTF16(nextAddressASCII, *nextAddress); + aRecord->HasMore(&hasMore); + } + } else { + dict.mAnswer = false; + GetErrorString(mStatus, dict.mError); + } + + JS::RootedValue 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; +} + +Dashboard::~Dashboard() +{ +} + +NS_IMETHODIMP +Dashboard::RequestSockets(NetDashboardCallback *aCallback) +{ + RefPtr<SocketData> socketData = new SocketData(); + socketData->mCallback = + new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true); + socketData->mThread = NS_GetCurrentThread(); + gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<SocketData>> + (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->mThread->Dispatch(NewRunnableMethod<RefPtr<SocketData>> + (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; + mSocket.mTcp = socketData->mData[i].tcp; + 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::RootedValue val(cx); + if (!ToJSValue(cx, dict, &val)) + return NS_ERROR_FAILURE; + socketData->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestHttpConnections(NetDashboardCallback *aCallback) +{ + RefPtr<HttpData> httpData = new HttpData(); + httpData->mCallback = + new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true); + httpData->mThread = NS_GetCurrentThread(); + + gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<HttpData>> + (this, &Dashboard::GetHttpDispatch, httpData), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult +Dashboard::GetHttpDispatch(HttpData *aHttpData) +{ + RefPtr<HttpData> httpData = aHttpData; + HttpInfo::GetHttpConnectionData(&httpData->mData); + httpData->mThread->Dispatch(NewRunnableMethod<RefPtr<HttpData>> + (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::HalfOpenInfoDict; + 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; + connection.mSpdy = httpData->mData[i].spdy; + connection.mSsl = httpData->mData[i].ssl; + + connection.mActive.Construct(); + connection.mIdle.Construct(); + connection.mHalfOpens.Construct(); + + Sequence<HttpConnInfo> &active = connection.mActive.Value(); + Sequence<HttpConnInfo> &idle = connection.mIdle.Value(); + Sequence<HalfOpenInfoDict> &halfOpens = connection.mHalfOpens.Value(); + + if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) || + !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) || + !halfOpens.SetCapacity(httpData->mData[i].halfOpens.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].halfOpens.Length(); j++) { + HalfOpenInfoDict &info = *halfOpens.AppendElement(fallible); + info.mSpeculative = httpData->mData[i].halfOpens[j].speculative; + } + } + + JS::RootedValue 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; + } + if (!mWs.data.AppendElement(mData)) { + return NS_ERROR_OUT_OF_MEMORY; + } + 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(NetDashboardCallback *aCallback) +{ + RefPtr<WebSocketRequest> wsRequest = new WebSocketRequest(); + wsRequest->mCallback = + new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true); + wsRequest->mThread = NS_GetCurrentThread(); + + wsRequest->mThread->Dispatch(NewRunnableMethod<RefPtr<WebSocketRequest>> + (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::RootedValue val(cx); + if (!ToJSValue(cx, dict, &val)) { + return NS_ERROR_FAILURE; + } + wsRequest->mCallback->OnDashboardDataAvailable(val); + + return NS_OK; +} + +NS_IMETHODIMP +Dashboard::RequestDNSInfo(NetDashboardCallback *aCallback) +{ + RefPtr<DnsData> dnsData = new DnsData(); + dnsData->mCallback = + new nsMainThreadPtrHolder<NetDashboardCallback>(aCallback, true); + + nsresult rv; + dnsData->mData.Clear(); + dnsData->mThread = NS_GetCurrentThread(); + + if (!mDnsService) { + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<DnsData>> + (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->mThread->Dispatch(NewRunnableMethod<RefPtr<DnsData>> + (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; + + 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) { + CopyASCIItoUTF16("ipv6", entry.mFamily); + } else { + CopyASCIItoUTF16("ipv4", entry.mFamily); + } + } + + JS::RootedValue 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, + NetDashboardCallback *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<NetDashboardCallback>(aCallback, true); + helper->mThread = NS_GetCurrentThread(); + rv = mDnsService->AsyncResolve(aHost, 0, helper.get(), + NS_GetCurrentThread(), + getter_AddRefs(helper->mCancel)); + return rv; +} + +void +HttpConnInfo::SetHTTP1ProtocolVersion(uint8_t pv) +{ + switch (pv) { + case NS_HTTP_VERSION_0_9: + protocolVersion.AssignLiteral(u"http/0.9"); + break; + case NS_HTTP_VERSION_1_0: + protocolVersion.AssignLiteral(u"http/1.0"); + break; + case NS_HTTP_VERSION_1_1: + protocolVersion.AssignLiteral(u"http/1.1"); + break; + case NS_HTTP_VERSION_2_0: + protocolVersion.AssignLiteral(u"http/2.0"); + break; + default: + protocolVersion.AssignLiteral(u"unknown protocol version"); + } +} + +void +HttpConnInfo::SetHTTP2ProtocolVersion(uint8_t pv) +{ + MOZ_ASSERT (pv == HTTP_VERSION_2); + protocolVersion.Assign(u"h2"); +} + +NS_IMETHODIMP +Dashboard::GetLogPath(nsACString &aLogPath) +{ + aLogPath.SetCapacity(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, + NetDashboardCallback *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<NetDashboardCallback>(aCallback, true); + connectionData->mThread = NS_GetCurrentThread(); + + rv = TestNewConnection(connectionData); + if (NS_FAILED(rv)) { + mozilla::net::GetErrorString(rv, connectionData->mStatus); + connectionData->mThread->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>> + (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::RootedValue 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 && + NS_LITERAL_STRING("ssl").EqualsASCII(connectionData->mProtocol)) { + rv = gSocketTransportService->CreateTransport( + &connectionData->mProtocol, 1, connectionData->mHost, + connectionData->mPort, nullptr, + getter_AddRefs(connectionData->mSocket)); + } else { + rv = gSocketTransportService->CreateTransport( + nullptr, 0, connectionData->mHost, + connectionData->mPort, nullptr, + getter_AddRefs(connectionData->mSocket)); + } + if (NS_FAILED(rv)) { + return rv; + } + + rv = connectionData->mSocket->SetEventSink(connectionData, + NS_GetCurrentThread()); + 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; +} + +typedef struct +{ + nsresult key; + const char *error; +} ErrorEntry; + +#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_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 (size_t i = 0; i < ArrayLength(socketTransportStatuses); ++i) { + if (socketTransportStatuses[i].key == rv) { + errorString.AssignASCII(socketTransportStatuses[i].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 000000000..4e893c15d --- /dev/null +++ b/netwerk/base/Dashboard.h @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" +#include "nsIDNSListener.h" +#include "nsIServiceManager.h" +#include "nsITimer.h" +#include "nsITransport.h" + +class nsIDNSService; + +namespace mozilla { +namespace net { + +class SocketData; +class HttpData; +class DnsData; +class WebSocketRequest; +class ConnectionData; + +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(nsCString hostname, uint32_t mSerial) + { + LogData temp(hostname, mSerial, false); + return data.IndexOf(temp); + } + nsTArray<LogData> data; + mozilla::Mutex lock; + }; + + + bool mEnableLogging; + WebSocketData mWs; + +private: + virtual ~Dashboard(); + + 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 *); + + 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 000000000..3f1f30d16 --- /dev/null +++ b/netwerk/base/DashboardTypes.h @@ -0,0 +1,63 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace net { + +struct SocketInfo +{ + nsCString host; + uint64_t sent; + uint64_t received; + uint16_t port; + bool active; + bool tcp; +}; + +struct HalfOpenSockets +{ + bool speculative; +}; + +struct DNSCacheEntries +{ + nsCString hostname; + nsTArray<nsCString> hostaddr; + uint16_t family; + int64_t expiration; + nsCString netInterface; +}; + +struct HttpConnInfo +{ + uint32_t ttl; + uint32_t rtt; + nsString protocolVersion; + + void SetHTTP1ProtocolVersion(uint8_t pv); + void SetHTTP2ProtocolVersion(uint8_t pv); +}; + +struct HttpRetParams +{ + nsCString host; + nsTArray<HttpConnInfo> active; + nsTArray<HttpConnInfo> idle; + nsTArray<HalfOpenSockets> halfOpens; + uint32_t counter; + uint16_t port; + bool spdy; + bool ssl; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_DashboardTypes_h_ diff --git a/netwerk/base/EventTokenBucket.cpp b/netwerk/base/EventTokenBucket.cpp new file mode 100644 index 000000000..e12624ea2 --- /dev/null +++ b/netwerk/base/EventTokenBucket.cpp @@ -0,0 +1,462 @@ +/* -*- 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() {} + + friend class EventTokenBucket; + ATokenBucketEvent *mEvent; +}; + +NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable) + +TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent *event) + : mEvent(event) +{ +} + +NS_IMETHODIMP +TokenBucketCancelable::Cancel(nsresult reason) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + mEvent = nullptr; + return NS_OK; +} + +void +TokenBucketCancelable::Fire() +{ + if (!mEvent) + return; + + ATokenBucketEvent *event = mEvent; + mEvent = nullptr; + event->OnTokenBucketAdmitted(); +} + +//////////////////////////////////////////// +// EventTokenBucket +//////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback) + +// 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 = do_CreateInstance("@mozilla.org/timer;1"); + if (mTimer) + mTimer->SetTarget(sts); + SetRate(eventsPerSecond, burstSize); +} + +EventTokenBucket::~EventTokenBucket() +{ + SOCKET_LOG(("EventTokenBucket::dtor %p events=%d\n", + this, mEvents.GetSize())); + + CleanupTimers(); + + // Complete any queued events to prevent hangs + while (mEvents.GetSize()) { + RefPtr<TokenBucketCancelable> cancelable = + dont_AddRef(static_cast<TokenBucketCancelable *>(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(PR_GetCurrentThread() == gSocketThread); + SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this)); + mCredit = 0; +} + +uint32_t +EventTokenBucket::BurstEventsAvailable() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + return static_cast<uint32_t>(mCredit / mUnitCost); +} + +uint32_t +EventTokenBucket::QueuedEvents() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + return mEvents.GetSize(); +} + +void +EventTokenBucket::Pause() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + SOCKET_LOG(("EventTokenBucket::Pause %p\n", this)); + if (mPaused || mStopped) + return; + + mPaused = true; + if (mTimerArmed) { + mTimer->Cancel(); + mTimerArmed = false; + } +} + +void +EventTokenBucket::UnPause() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + SOCKET_LOG(("EventTokenBucket::UnPause %p\n", this)); + if (!mPaused || mStopped) + return; + + mPaused = false; + DispatchEvents(); + UpdateTimer(); +} + +void +EventTokenBucket::Stop() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + 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 = + dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront())); + cancelable->Fire(); + } +} + +nsresult +EventTokenBucket::SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + 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() + + NS_ADDREF(*cancelable = cancelEvent.get()); + + if (mPaused || !TryImmediateDispatch(cancelEvent.get())) { + // queue it + SOCKET_LOG((" queued\n")); + mEvents.Push(cancelEvent.forget().take()); + 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(PR_GetCurrentThread() == gSocketThread); + SOCKET_LOG(("EventTokenBucket::DispatchEvents %p %d\n", this, mPaused)); + if (mPaused || mStopped) + return; + + while (mEvents.GetSize() && mUnitCost <= mCredit) { + RefPtr<TokenBucketCancelable> cancelable = + dont_AddRef(static_cast<TokenBucketCancelable *>(mEvents.PopFront())); + if (cancelable->mEvent) { + SOCKET_LOG(("EventTokenBucket::DispachEvents [%p] " + "Dispatching queue token bucket event cost=%lu credit=%lu\n", + this, mUnitCost, mCredit)); + mCredit -= mUnitCost; + cancelable->Fire(); + } + } + +#ifdef XP_WIN + if (!mEvents.GetSize()) + WantNormalTimers(); +#endif +} + +void +EventTokenBucket::UpdateTimer() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + 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 %dms\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(PR_GetCurrentThread() == gSocketThread); + +#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; +} + +void +EventTokenBucket::UpdateCredits() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + 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 %lu (%lu 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 = do_CreateInstance("@mozilla.org/timer;1"); + + // 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() events = %d\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 000000000..b187ca7b0 --- /dev/null +++ b/netwerk/base/EventTokenBucket.h @@ -0,0 +1,149 @@ +/* -*- 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 "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 ARefBase +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + // 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 *event); + 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; + nsDeque 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/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp new file mode 100644 index 000000000..61b9394f9 --- /dev/null +++ b/netwerk/base/LoadContextInfo.cpp @@ -0,0 +1,181 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "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, NeckoOriginAttributes aOriginAttributes) + : mIsAnonymous(aIsAnonymous) + , mOriginAttributes(aOriginAttributes) +{ +} + +LoadContextInfo::~LoadContextInfo() +{ +} + +NS_IMETHODIMP LoadContextInfo::GetIsPrivate(bool *aIsPrivate) +{ + *aIsPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfo::GetIsAnonymous(bool *aIsAnonymous) +{ + *aIsAnonymous = mIsAnonymous; + return NS_OK; +} + +NeckoOriginAttributes 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, NeckoOriginAttributes()); + info.forget(aDefault); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::GetPrivate(nsILoadContextInfo * *aPrivate) +{ + NeckoOriginAttributes 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, NeckoOriginAttributes()); + info.forget(aAnonymous); + return NS_OK; +} + +NS_IMETHODIMP LoadContextInfoFactory::Custom(bool aAnonymous, + JS::HandleValue aOriginAttributes, JSContext *cx, + nsILoadContextInfo * *_retval) +{ + NeckoOriginAttributes 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); + } + + NeckoOriginAttributes oa; + NS_GetOriginAttributes(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, + NeckoOriginAttributes(nsILoadContextInfo::NO_APP_ID, false)); + } + + DebugOnly<bool> pb = aLoadContext->UsePrivateBrowsing(); + DocShellOriginAttributes doa; + aLoadContext->GetOriginAttributes(doa); + MOZ_ASSERT(pb == (doa.mPrivateBrowsingId > 0)); + + NeckoOriginAttributes noa; + noa.InheritFromDocShellToNecko(doa); + + return new LoadContextInfo(aIsAnonymous, noa); +} + +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, + NeckoOriginAttributes 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 000000000..8477dfd1c --- /dev/null +++ b/netwerk/base/LoadContextInfo.h @@ -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/. */ + +#ifndef nsLoadContextInfo_h__ +#define nsLoadContextInfo_h__ + +#include "nsILoadContextInfo.h" + +class nsIChannel; +class nsILoadContext; + +namespace mozilla { +namespace net { + +class LoadContextInfo : public nsILoadContextInfo +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSILOADCONTEXTINFO + + LoadContextInfo(bool aIsAnonymous, NeckoOriginAttributes aOriginAttributes); + +private: + virtual ~LoadContextInfo(); + +protected: + bool mIsAnonymous : 1; + NeckoOriginAttributes mOriginAttributes; +}; + +class LoadContextInfoFactory : public nsILoadContextInfoFactory +{ + virtual ~LoadContextInfoFactory() {} +public: + NS_DECL_ISUPPORTS // deliberately not thread-safe + NS_DECL_NSILOADCONTEXTINFOFACTORY +}; + +LoadContextInfo* +GetLoadContextInfo(nsIChannel *aChannel); + +LoadContextInfo* +GetLoadContextInfo(nsILoadContext *aLoadContext, + bool aIsAnonymous); + +LoadContextInfo* +GetLoadContextInfo(nsIDOMWindow *aLoadContext, + bool aIsAnonymous); + +LoadContextInfo* +GetLoadContextInfo(nsILoadContextInfo *aInfo); + +LoadContextInfo* +GetLoadContextInfo(bool const aIsAnonymous, + NeckoOriginAttributes const &aOriginAttributes); + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp new file mode 100644 index 000000000..216cf559c --- /dev/null +++ b/netwerk/base/LoadInfo.cpp @@ -0,0 +1,925 @@ +/* -*- 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 "mozilla/Assertions.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozIThirdPartyUtil.h" +#include "nsFrameLoader.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIDocShell.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIFrameLoader.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsISupportsImpl.h" +#include "nsISupportsUtils.h" +#include "nsContentUtils.h" +#include "nsDocShell.h" +#include "nsGlobalWindow.h" +#include "nsNullPrincipal.h" + +using namespace mozilla::dom; + +namespace mozilla { +namespace net { + +static void +InheritOriginAttributes(nsIPrincipal* aLoadingPrincipal, NeckoOriginAttributes& aAttrs) +{ + const PrincipalOriginAttributes attrs = + BasePrincipal::Cast(aLoadingPrincipal)->OriginAttributesRef(); + aAttrs.InheritFromDocToNecko(attrs); +} + +LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + nsINode* aLoadingContext, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType) + : mLoadingPrincipal(aLoadingContext ? + aLoadingContext->NodePrincipal() : aLoadingPrincipal) + , mTriggeringPrincipal(aTriggeringPrincipal ? + aTriggeringPrincipal : mLoadingPrincipal.get()) + , mPrincipalToInherit(nullptr) + , mLoadingContext(do_GetWeakReference(aLoadingContext)) + , mSecurityFlags(aSecurityFlags) + , mInternalContentPolicyType(aContentPolicyType) + , mTainting(LoadTainting::Basic) + , mUpgradeInsecureRequests(false) + , mVerifySignedContent(false) + , mEnforceSRI(false) + , mForceInheritPrincipalDropped(false) + , mInnerWindowID(0) + , mOuterWindowID(0) + , mParentOuterWindowID(0) + , mFrameOuterWindowID(0) + , mEnforceSecurity(false) + , mInitialSecurityCheckDone(false) + , mIsThirdPartyContext(false) + , mForcePreflight(false) + , mIsPreflight(false) + , mForceHSTSPriming(false) + , mMixedContentWouldBlock(false) +{ + 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 || + mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT); + + // 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 (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) { + mSecurityFlags ^= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + mForceInheritPrincipalDropped = true; + } + + if (aLoadingContext) { + nsCOMPtr<nsPIDOMWindowOuter> contextOuter = aLoadingContext->OwnerDoc()->GetWindow(); + if (contextOuter) { + ComputeIsThirdPartyContext(contextOuter); + mOuterWindowID = contextOuter->WindowID(); + nsCOMPtr<nsPIDOMWindowOuter> parent = contextOuter->GetScriptableParent(); + mParentOuterWindowID = parent ? parent->WindowID() : mOuterWindowID; + } + + mInnerWindowID = aLoadingContext->OwnerDoc()->InnerWindowID(); + + // 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. + nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = + do_QueryInterface(aLoadingContext); + nsCOMPtr<nsIFrameLoader> fl = frameLoaderOwner ? + frameLoaderOwner->GetFrameLoader() : nullptr; + if (fl) { + nsCOMPtr<nsIDocShell> docShell; + if (NS_SUCCEEDED(fl->GetDocShell(getter_AddRefs(docShell))) && docShell) { + nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_GetInterface(docShell); + if (outerWindow) { + mFrameOuterWindowID = outerWindow->WindowID(); + } + } + } + + // 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 owner doc has content signature, we enforce SRI + nsCOMPtr<nsIChannel> channel = aLoadingContext->OwnerDoc()->GetChannel(); + if (channel) { + nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo(); + if (loadInfo) { + mEnforceSRI = loadInfo->GetVerifySignedContent(); + } + } + } + + // If CSP requires SRI (require-sri-for), then store that information + // in the loadInfo so we can enforce SRI before loading the subresource. + if (!mEnforceSRI) { + // do not look into the CSP if already true: + // a CSP saying that SRI isn't needed should not + // overrule GetVerifySignedContent + if (aLoadingPrincipal) { + nsCOMPtr<nsIContentSecurityPolicy> csp; + aLoadingPrincipal->GetCsp(getter_AddRefs(csp)); + uint32_t externalType = + nsContentUtils::InternalContentPolicyTypeToExternal(aContentPolicyType); + // csp could be null if loading principal is system principal + if (csp) { + csp->RequireSRIForType(externalType, &mEnforceSRI); + } + // if CSP is delivered via a meta tag, it's speculatively available + // as 'preloadCSP'. If we are preloading a script or style, we have + // to apply that speculative 'preloadCSP' for such loads. + if (!mEnforceSRI && nsContentUtils::IsPreloadType(aContentPolicyType)) { + nsCOMPtr<nsIContentSecurityPolicy> preloadCSP; + aLoadingPrincipal->GetPreloadCsp(getter_AddRefs(preloadCSP)); + if (preloadCSP) { + preloadCSP->RequireSRIForType(externalType, &mEnforceSRI); + } + } + } + } + + InheritOriginAttributes(mLoadingPrincipal, mOriginAttributes); + + // 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->ItemType() == nsIDocShellTreeItem::typeContent) { + bool usePrivateBrowsing; + nsresult rv = loadContext->GetUsePrivateBrowsing(&usePrivateBrowsing); + if (NS_SUCCEEDED(rv)) { + mOriginAttributes.SyncAttributesWithPrivateBrowsing(usePrivateBrowsing); + } + } + } + + // 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. + if (aLoadingContext) { + nsCOMPtr<nsIDocShell> docShell = aLoadingContext->OwnerDoc()->GetDocShell(); + if (docShell) { + if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) { + MOZ_ASSERT(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, + nsIPrincipal* aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags) + : mLoadingPrincipal(nullptr) + , mTriggeringPrincipal(aTriggeringPrincipal) + , mPrincipalToInherit(nullptr) + , mSecurityFlags(aSecurityFlags) + , mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT) + , mTainting(LoadTainting::Basic) + , mUpgradeInsecureRequests(false) + , mVerifySignedContent(false) + , mEnforceSRI(false) + , mForceInheritPrincipalDropped(false) + , mInnerWindowID(0) + , mOuterWindowID(0) + , mParentOuterWindowID(0) + , mFrameOuterWindowID(0) + , mEnforceSecurity(false) + , mInitialSecurityCheckDone(false) + , mIsThirdPartyContext(false) // NB: TYPE_DOCUMENT implies not third-party. + , mForcePreflight(false) + , mIsPreflight(false) + , mForceHSTSPriming(false) + , mMixedContentWouldBlock(false) +{ + // 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 (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) { + mSecurityFlags ^= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + mForceInheritPrincipalDropped = true; + } + + // NB: Ignore the current inner window since we're navigating away from it. + mOuterWindowID = aOuterWindow->WindowID(); + + // TODO We can have a parent without a frame element in some cases dealing + // with the hidden window. + nsCOMPtr<nsPIDOMWindowOuter> parent = aOuterWindow->GetScriptableParent(); + mParentOuterWindowID = parent ? parent->WindowID() : 0; + + // get the docshell from the outerwindow, and then get the originattributes + nsCOMPtr<nsIDocShell> docShell = aOuterWindow->GetDocShell(); + MOZ_ASSERT(docShell); + const DocShellOriginAttributes attrs = + nsDocShell::Cast(docShell)->GetOriginAttributes(); + + if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) { + MOZ_ASSERT(attrs.mPrivateBrowsingId == 0, + "chrome docshell shouldn't have mPrivateBrowsingId set."); + } + + mOriginAttributes.InheritFromDocShellToNecko(attrs); +} + +LoadInfo::LoadInfo(const LoadInfo& rhs) + : mLoadingPrincipal(rhs.mLoadingPrincipal) + , mTriggeringPrincipal(rhs.mTriggeringPrincipal) + , mPrincipalToInherit(rhs.mPrincipalToInherit) + , mLoadingContext(rhs.mLoadingContext) + , mSecurityFlags(rhs.mSecurityFlags) + , mInternalContentPolicyType(rhs.mInternalContentPolicyType) + , mTainting(rhs.mTainting) + , mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests) + , mVerifySignedContent(rhs.mVerifySignedContent) + , mEnforceSRI(rhs.mEnforceSRI) + , mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped) + , mInnerWindowID(rhs.mInnerWindowID) + , mOuterWindowID(rhs.mOuterWindowID) + , mParentOuterWindowID(rhs.mParentOuterWindowID) + , mFrameOuterWindowID(rhs.mFrameOuterWindowID) + , mEnforceSecurity(rhs.mEnforceSecurity) + , mInitialSecurityCheckDone(rhs.mInitialSecurityCheckDone) + , mIsThirdPartyContext(rhs.mIsThirdPartyContext) + , mOriginAttributes(rhs.mOriginAttributes) + , mRedirectChainIncludingInternalRedirects( + rhs.mRedirectChainIncludingInternalRedirects) + , mRedirectChain(rhs.mRedirectChain) + , mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders) + , mForcePreflight(rhs.mForcePreflight) + , mIsPreflight(rhs.mIsPreflight) + , mForceHSTSPriming(rhs.mForceHSTSPriming) + , mMixedContentWouldBlock(rhs.mMixedContentWouldBlock) +{ +} + +LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + LoadTainting aTainting, + bool aUpgradeInsecureRequests, + bool aVerifySignedContent, + bool aEnforceSRI, + bool aForceInheritPrincipalDropped, + uint64_t aInnerWindowID, + uint64_t aOuterWindowID, + uint64_t aParentOuterWindowID, + uint64_t aFrameOuterWindowID, + bool aEnforceSecurity, + bool aInitialSecurityCheckDone, + bool aIsThirdPartyContext, + const NeckoOriginAttributes& aOriginAttributes, + nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChainIncludingInternalRedirects, + nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChain, + const nsTArray<nsCString>& aCorsUnsafeHeaders, + bool aForcePreflight, + bool aIsPreflight, + bool aForceHSTSPriming, + bool aMixedContentWouldBlock) + : mLoadingPrincipal(aLoadingPrincipal) + , mTriggeringPrincipal(aTriggeringPrincipal) + , mPrincipalToInherit(aPrincipalToInherit) + , mSecurityFlags(aSecurityFlags) + , mInternalContentPolicyType(aContentPolicyType) + , mTainting(aTainting) + , mUpgradeInsecureRequests(aUpgradeInsecureRequests) + , mVerifySignedContent(aVerifySignedContent) + , mEnforceSRI(aEnforceSRI) + , mForceInheritPrincipalDropped(aForceInheritPrincipalDropped) + , mInnerWindowID(aInnerWindowID) + , mOuterWindowID(aOuterWindowID) + , mParentOuterWindowID(aParentOuterWindowID) + , mFrameOuterWindowID(aFrameOuterWindowID) + , mEnforceSecurity(aEnforceSecurity) + , mInitialSecurityCheckDone(aInitialSecurityCheckDone) + , mIsThirdPartyContext(aIsThirdPartyContext) + , mOriginAttributes(aOriginAttributes) + , mCorsUnsafeHeaders(aCorsUnsafeHeaders) + , mForcePreflight(aForcePreflight) + , mIsPreflight(aIsPreflight) + , mForceHSTSPriming (aForceHSTSPriming) + , mMixedContentWouldBlock(aMixedContentWouldBlock) +{ + // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal + MOZ_ASSERT(mLoadingPrincipal || aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT); + MOZ_ASSERT(mTriggeringPrincipal); + + mRedirectChainIncludingInternalRedirects.SwapElements( + aRedirectChainIncludingInternalRedirects); + + mRedirectChain.SwapElements(aRedirectChain); +} + +LoadInfo::~LoadInfo() +{ +} + +void +LoadInfo::ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow) +{ + nsContentPolicyType type = + nsContentUtils::InternalContentPolicyTypeToExternal(mInternalContentPolicyType); + if (type == nsIContentPolicy::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); +} + +NS_IMPL_ISUPPORTS(LoadInfo, nsILoadInfo) + +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->mEnforceSecurity = false; + copy->mInitialSecurityCheckDone = false; + copy->mRedirectChainIncludingInternalRedirects.Clear(); + copy->mRedirectChain.Clear(); + return copy.forget(); +} + +NS_IMETHODIMP +LoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) +{ + NS_IF_ADDREF(*aLoadingPrincipal = mLoadingPrincipal); + return NS_OK; +} + +nsIPrincipal* +LoadInfo::LoadingPrincipal() +{ + return mLoadingPrincipal; +} + +NS_IMETHODIMP +LoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) +{ + NS_ADDREF(*aTriggeringPrincipal = mTriggeringPrincipal); + return NS_OK; +} + +nsIPrincipal* +LoadInfo::TriggeringPrincipal() +{ + return mTriggeringPrincipal; +} + +NS_IMETHODIMP +LoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) +{ + NS_IF_ADDREF(*aPrincipalToInherit = mPrincipalToInherit); + 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; +} + +NS_IMETHODIMP +LoadInfo::GetLoadingDocument(nsIDOMDocument** aResult) +{ + nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext); + if (node) { + nsCOMPtr<nsIDOMDocument> context = do_QueryInterface(node->OwnerDoc()); + context.forget(aResult); + } + return NS_OK; +} + +nsINode* +LoadInfo::LoadingNode() +{ + nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext); + return node; +} + +NS_IMETHODIMP +LoadInfo::GetSecurityFlags(nsSecurityFlags* aResult) +{ + *aResult = mSecurityFlags; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetSecurityMode(uint32_t* aFlags) +{ + *aFlags = (mSecurityFlags & + (nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS | + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED | + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS | + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL | + nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS)); + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) +{ + *aIsInThirdPartyContext = mIsThirdPartyContext; + 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_DATA_INHERITS) ? + nsILoadInfo::SEC_COOKIES_SAME_ORIGIN : nsILoadInfo::SEC_COOKIES_INCLUDE; + } + + *aResult = policy; + return NS_OK; +} + +void +LoadInfo::SetIncludeCookiesSecFlag() +{ + MOZ_ASSERT(!mEnforceSecurity, + "Request should not have been opened yet"); + 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 = (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED); + 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::GetExternalContentPolicyType(nsContentPolicyType* aResult) +{ + *aResult = nsContentUtils::InternalContentPolicyTypeToExternal(mInternalContentPolicyType); + return NS_OK; +} + +nsContentPolicyType +LoadInfo::InternalContentPolicyType() +{ + return mInternalContentPolicyType; +} + +NS_IMETHODIMP +LoadInfo::GetUpgradeInsecureRequests(bool* aResult) +{ + *aResult = mUpgradeInsecureRequests; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetVerifySignedContent(bool aVerifySignedContent) +{ + MOZ_ASSERT(mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "can only verify content for TYPE_DOCUMENT"); + mVerifySignedContent = aVerifySignedContent; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetVerifySignedContent(bool* aResult) +{ + *aResult = mVerifySignedContent; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetEnforceSRI(bool aEnforceSRI) +{ + mEnforceSRI = aEnforceSRI; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetEnforceSRI(bool* aResult) +{ + *aResult = mEnforceSRI; + 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::GetOuterWindowID(uint64_t* aResult) +{ + *aResult = mOuterWindowID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetParentOuterWindowID(uint64_t* aResult) +{ + *aResult = mParentOuterWindowID; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetFrameOuterWindowID(uint64_t* aResult) +{ + *aResult = mFrameOuterWindowID; + 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::ResetPrincipalsToNullPrincipal() +{ + // take the originAttributes from the LoadInfo and create + // a new NullPrincipal using those origin attributes. + PrincipalOriginAttributes pAttrs; + pAttrs.InheritFromNecko(mOriginAttributes); + nsCOMPtr<nsIPrincipal> newNullPrincipal = nsNullPrincipal::Create(pAttrs); + + MOZ_ASSERT(mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT || + !mLoadingPrincipal, + "LoadingPrincipal should be null for toplevel loads"); + + // the loadingPrincipal for toplevel loads is always a nullptr; + if (mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT) { + mLoadingPrincipal = newNullPrincipal; + } + mTriggeringPrincipal = newNullPrincipal; + 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) +{ + NeckoOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + mOriginAttributes = attrs; + return NS_OK; +} + +nsresult +LoadInfo::GetOriginAttributes(mozilla::NeckoOriginAttributes* aOriginAttributes) +{ + NS_ENSURE_ARG(aOriginAttributes); + *aOriginAttributes = mOriginAttributes; + return NS_OK; +} + +nsresult +LoadInfo::SetOriginAttributes(const mozilla::NeckoOriginAttributes& aOriginAttributes) +{ + mOriginAttributes = aOriginAttributes; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::SetEnforceSecurity(bool aEnforceSecurity) +{ + // Indicates whether the channel was openend using AsyncOpen2. Once set + // to true, it must remain true throughout the lifetime of the channel. + // Setting it to anything else than true will be discarded. + MOZ_ASSERT(aEnforceSecurity, "aEnforceSecurity must be true"); + mEnforceSecurity = mEnforceSecurity || aEnforceSecurity; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetEnforceSecurity(bool* aResult) +{ + *aResult = mEnforceSecurity; + 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; +} + +NS_IMETHODIMP +LoadInfo::AppendRedirectedPrincipal(nsIPrincipal* aPrincipal, bool aIsInternalRedirect) +{ + NS_ENSURE_ARG(aPrincipal); + MOZ_ASSERT(NS_IsMainThread()); + + mRedirectChainIncludingInternalRedirects.AppendElement(aPrincipal); + if (!aIsInternalRedirect) { + mRedirectChain.AppendElement(aPrincipal); + } + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetRedirectChainIncludingInternalRedirects(JSContext* aCx, JS::MutableHandle<JS::Value> aChain) +{ + if (!ToJSValue(aCx, mRedirectChainIncludingInternalRedirects, aChain)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +const nsTArray<nsCOMPtr<nsIPrincipal>>& +LoadInfo::RedirectChainIncludingInternalRedirects() +{ + return mRedirectChainIncludingInternalRedirects; +} + +NS_IMETHODIMP +LoadInfo::GetRedirectChain(JSContext* aCx, JS::MutableHandle<JS::Value> aChain) +{ + if (!ToJSValue(aCx, mRedirectChain, aChain)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +const nsTArray<nsCOMPtr<nsIPrincipal>>& +LoadInfo::RedirectChain() +{ + return mRedirectChain; +} + +void +LoadInfo::SetCorsPreflightInfo(const nsTArray<nsCString>& aHeaders, + bool aForcePreflight) +{ + MOZ_ASSERT(GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS); + MOZ_ASSERT(!mInitialSecurityCheckDone); + mCorsUnsafeHeaders = aHeaders; + 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_DATA_INHERITS); + MOZ_ASSERT(!mInitialSecurityCheckDone); + mIsPreflight = true; +} + +NS_IMETHODIMP +LoadInfo::GetIsPreflight(bool* aIsPreflight) +{ + *aIsPreflight = mIsPreflight; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetForceHSTSPriming(bool* aForceHSTSPriming) +{ + *aForceHSTSPriming = mForceHSTSPriming; + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetMixedContentWouldBlock(bool *aMixedContentWouldBlock) +{ + *aMixedContentWouldBlock = mMixedContentWouldBlock; + return NS_OK; +} + +void +LoadInfo::SetHSTSPriming(bool aMixedContentWouldBlock) +{ + mForceHSTSPriming = true; + mMixedContentWouldBlock = aMixedContentWouldBlock; +} + +void +LoadInfo::ClearHSTSPriming() +{ + mForceHSTSPriming = false; + mMixedContentWouldBlock = false; +} + +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); + LoadTainting tainting = static_cast<LoadTainting>(aTainting); + if (tainting > mTainting) { + mTainting = tainting; + } + return NS_OK; +} + +NS_IMETHODIMP +LoadInfo::GetIsTopLevelLoad(bool *aResult) +{ + *aResult = mFrameOuterWindowID ? mFrameOuterWindowID == mOuterWindowID + : mParentOuterWindowID == mOuterWindowID; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h new file mode 100644 index 000000000..261f85349 --- /dev/null +++ b/netwerk/base/LoadInfo.h @@ -0,0 +1,163 @@ +/* -*- 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 "nsIContentPolicy.h" +#include "nsILoadInfo.h" +#include "nsIPrincipal.h" +#include "nsIWeakReferenceUtils.h" // for nsWeakPtr +#include "nsIURI.h" +#include "nsTArray.h" + +#include "mozilla/BasePrincipal.h" + +class nsINode; +class nsPIDOMWindowOuter; + +namespace mozilla { + +namespace dom { +class XMLHttpRequestMainThread; +} + +namespace net { +class OptionalLoadInfoArgs; +} // namespace net + +namespace ipc { +// we have to forward declare that function so we can use it as a friend. +nsresult +LoadInfoArgsToLoadInfo(const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs, + nsILoadInfo** outLoadInfo); +} // namespace ipc + +namespace net { + +/** + * Class that provides an nsILoadInfo implementation. + * + * Note that there is no reason why this class should be MOZ_EXPORT, but + * Thunderbird relies on some insane hacks which require this, so we'll leave it + * as is for now, but hopefully we'll be able to remove the MOZ_EXPORT keyword + * from this class at some point. See bug 1149127 for the discussion. + */ +class MOZ_EXPORT LoadInfo final : public nsILoadInfo +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOADINFO + + // aLoadingPrincipal MUST NOT BE NULL. + LoadInfo(nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + nsINode* aLoadingContext, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType); + + // Constructor used for TYPE_DOCUMENT loads which have no reasonable + // loadingNode or loadingPrincipal + LoadInfo(nsPIDOMWindowOuter* aOuterWindow, + nsIPrincipal* aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags); + + // 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; + + void SetIsPreflight(); + +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, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + LoadTainting aTainting, + bool aUpgradeInsecureRequests, + bool aVerifySignedContent, + bool aEnforceSRI, + bool aForceInheritPrincipalDropped, + uint64_t aInnerWindowID, + uint64_t aOuterWindowID, + uint64_t aParentOuterWindowID, + uint64_t aFrameOuterWindowID, + bool aEnforceSecurity, + bool aInitialSecurityCheckDone, + bool aIsThirdPartyRequest, + const NeckoOriginAttributes& aOriginAttributes, + nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChainIncludingInternalRedirects, + nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChain, + const nsTArray<nsCString>& aUnsafeHeaders, + bool aForcePreflight, + bool aIsPreflight, + bool aForceHSTSPriming, + bool aMixedContentWouldBlock); + LoadInfo(const LoadInfo& rhs); + + friend nsresult + mozilla::ipc::LoadInfoArgsToLoadInfo( + const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs, + nsILoadInfo** outLoadInfo); + + ~LoadInfo(); + + void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow); + + // 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; + + // if you add a member, please also update the copy constructor + nsCOMPtr<nsIPrincipal> mLoadingPrincipal; + nsCOMPtr<nsIPrincipal> mTriggeringPrincipal; + nsCOMPtr<nsIPrincipal> mPrincipalToInherit; + nsWeakPtr mLoadingContext; + nsSecurityFlags mSecurityFlags; + nsContentPolicyType mInternalContentPolicyType; + LoadTainting mTainting; + bool mUpgradeInsecureRequests; + bool mVerifySignedContent; + bool mEnforceSRI; + bool mForceInheritPrincipalDropped; + uint64_t mInnerWindowID; + uint64_t mOuterWindowID; + uint64_t mParentOuterWindowID; + uint64_t mFrameOuterWindowID; + bool mEnforceSecurity; + bool mInitialSecurityCheckDone; + bool mIsThirdPartyContext; + NeckoOriginAttributes mOriginAttributes; + nsTArray<nsCOMPtr<nsIPrincipal>> mRedirectChainIncludingInternalRedirects; + nsTArray<nsCOMPtr<nsIPrincipal>> mRedirectChain; + nsTArray<nsCString> mCorsUnsafeHeaders; + bool mForcePreflight; + bool mIsPreflight; + + bool mForceHSTSPriming : 1; + bool mMixedContentWouldBlock : 1; +}; + +} // 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 000000000..e2633969f --- /dev/null +++ b/netwerk/base/LoadTainting.h @@ -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/. */ + +#ifndef mozilla_LoadTainting_h +#define mozilla_LoadTainting_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. +// +// NOTE: Checking the tainting is not currently adequate. You *must* still +// check the final URL and CORS mode on the channel. +// +// These values are currently only set on the channel LoadInfo when the request +// was initiated through fetch() or when a service worker interception occurs. +// In the future we should set the tainting value within necko so that it is +// consistently applied. Once that is done consumers can replace checks against +// the final URL and CORS mode with checks against tainting. +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 000000000..f8add31aa --- /dev/null +++ b/netwerk/base/MemoryDownloader.cpp @@ -0,0 +1,92 @@ +/* -*- 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) +{ +} + +MemoryDownloader::~MemoryDownloader() +{ +} + +NS_IMETHODIMP +MemoryDownloader::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt) +{ + MOZ_ASSERT(!mData); + mData.reset(new FallibleTArray<uint8_t>()); + mStatus = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP +MemoryDownloader::OnStopRequest(nsIRequest* aRequest, + nsISupports* aCtxt, + 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, aCtxt, aStatus, + mozilla::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, + nsISupports* aCtxt, + 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 000000000..32fcff66c --- /dev/null +++ b/netwerk/base/MemoryDownloader.h @@ -0,0 +1,65 @@ +/* -*- 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 + + typedef mozilla::UniquePtr<FallibleTArray<uint8_t>> Data; + + class IObserver : public nsISupports { + public: + // Note: aData may be null if (and only if) aStatus indicates failure. + virtual void OnDownloadComplete(MemoryDownloader* aDownloader, + nsIRequest* aRequest, + nsISupports* aCtxt, + nsresult aStatus, + Data aData) = 0; + }; + + explicit MemoryDownloader(IObserver* aObserver); + +private: + virtual ~MemoryDownloader(); + + 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/NetStatistics.h b/netwerk/base/NetStatistics.h new file mode 100644 index 000000000..261355083 --- /dev/null +++ b/netwerk/base/NetStatistics.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 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 NetStatistics_h__ +#define NetStatistics_h__ + +#include "mozilla/Assertions.h" + +#include "nsCOMPtr.h" +#include "nsError.h" +#include "nsINetworkInterface.h" +#include "nsINetworkManager.h" +#include "nsINetworkStatsServiceProxy.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" + +namespace mozilla { +namespace net { + +// The following members are used for network per-app metering. +const static uint64_t NETWORK_STATS_THRESHOLD = 65536; +const static char NETWORK_STATS_NO_SERVICE_TYPE[] = ""; + +inline nsresult +GetActiveNetworkInfo(nsCOMPtr<nsINetworkInfo> &aNetworkInfo) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + nsCOMPtr<nsINetworkManager> networkManager = + do_GetService("@mozilla.org/network/manager;1", &rv); + + if (NS_FAILED(rv) || !networkManager) { + aNetworkInfo = nullptr; + return rv; + } + + networkManager->GetActiveNetworkInfo(getter_AddRefs(aNetworkInfo)); + + return NS_OK; +} + +class SaveNetworkStatsEvent : public Runnable { +public: + SaveNetworkStatsEvent(uint32_t aAppId, + bool aIsInIsolatedMozBrowser, + nsMainThreadPtrHandle<nsINetworkInfo> &aActiveNetworkInfo, + uint64_t aCountRecv, + uint64_t aCountSent, + bool aIsAccumulative) + : mAppId(aAppId), + mIsInIsolatedMozBrowser(aIsInIsolatedMozBrowser), + mActiveNetworkInfo(aActiveNetworkInfo), + mCountRecv(aCountRecv), + mCountSent(aCountSent), + mIsAccumulative(aIsAccumulative) + { + MOZ_ASSERT(mAppId != NECKO_NO_APP_ID); + MOZ_ASSERT(mActiveNetworkInfo); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + nsCOMPtr<nsINetworkStatsServiceProxy> mNetworkStatsServiceProxy = + do_GetService("@mozilla.org/networkstatsServiceProxy;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + + // save the network stats through NetworkStatsServiceProxy + mNetworkStatsServiceProxy->SaveAppStats(mAppId, + mIsInIsolatedMozBrowser, + mActiveNetworkInfo, + PR_Now() / 1000, + mCountRecv, + mCountSent, + mIsAccumulative, + nullptr); + + return NS_OK; + } +private: + uint32_t mAppId; + bool mIsInIsolatedMozBrowser; + nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo; + uint64_t mCountRecv; + uint64_t mCountSent; + bool mIsAccumulative; +}; + +} // namespace mozilla:net +} // namespace mozilla + +#endif // !NetStatistics_h__ diff --git a/netwerk/base/NetUtil.jsm b/netwerk/base/NetUtil.jsm new file mode 100644 index 000000000..e970c8ad8 --- /dev/null +++ b/netwerk/base/NetUtil.jsm @@ -0,0 +1,461 @@ +/* -*- 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/. */ + +this.EXPORTED_SYMBOLS = [ + "NetUtil", +]; + +/** + * Necko utilities + */ + +//////////////////////////////////////////////////////////////////////////////// +//// Constants + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cr = Components.results; +const Cu = Components.utils; + +const PR_UINT32_MAX = 0xffffffff; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +//////////////////////////////////////////////////////////////////////////////// +//// NetUtil Object + +this.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: function(aRequest, aContext) {}, + onStopRequest: function(aRequest, aContext, 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 "OS.File" 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: function(aRequest, aContext) {}, + onStopRequest: function(aRequest, aContext, 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, -1, -1, 0, 0, true); + pump.asyncRead(listener, null); + return; + } + + let channel = aSource; + if (!(channel instanceof Ci.nsIChannel)) { + channel = this.newChannel(aSource); + } + + try { + // Open the channel using asyncOpen2() if the loadinfo contains one + // of the security mode flags, otherwise fall back to use asyncOpen(). + if (channel.loadInfo && + channel.loadInfo.securityMode != 0) { + channel.asyncOpen2(listener); + } + else { + // Log deprecation warning to console to make sure all channels + // are created providing the correct security flags in the loadinfo. + // See nsILoadInfo for all available security flags and also the API + // of NetUtil.newChannel() for details above. + Cu.reportError("NetUtil.jsm: asyncFetch() requires the channel to have " + + "one of the security flags set in the loadinfo (see nsILoadInfo). " + + "Please create channel using NetUtil.newChannel()"); + channel.asyncOpen(listener, null); + } + } + 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 this.ioService.newFileURI(aTarget); + } + + return this.ioService.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. + * } + * @param aOriginCharset [deprecated] + * The character set for the URI. Only used if aWhatToLoad is a + * string, which is a deprecated API. Must be undefined otherwise. + * Use NetUtil.newURI if you need to use this option. + * @param aBaseURI [deprecated] + * The base URI for the spec. Only used if aWhatToLoad is a string, + * which is a deprecated API. Must be undefined otherwise. Use + * NetUtil.newURI if you need to use this option. + * @return an nsIChannel object. + */ + newChannel: function NetUtil_newChannel(aWhatToLoad, aOriginCharset, aBaseURI) + { + // Check for the deprecated API first. + if (typeof aWhatToLoad == "string" || + (aWhatToLoad instanceof Ci.nsIFile) || + (aWhatToLoad instanceof Ci.nsIURI)) { + + let uri = (aWhatToLoad instanceof Ci.nsIURI) + ? aWhatToLoad + : this.newURI(aWhatToLoad, aOriginCharset, aBaseURI); + + // log deprecation warning for developers. + Services.console.logStringMessage( + "Warning: NetUtil.newChannel(uri) deprecated, please provide argument 'aWhatToLoad'"); + + // Provide default loadinfo arguments and call the new API. + let systemPrincipal = + Services.scriptSecurityManager.getSystemPrincipal(); + + return this.ioService.newChannelFromURI2( + uri, + null, // loadingNode + systemPrincipal, // loadingPrincipal + null, // triggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); + } + + // We are using the updated API, that requires only the options object. + if (typeof aWhatToLoad != "object" || + aOriginCharset !== undefined || + aBaseURI !== undefined) { + 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) { + securityFlags = loadUsingSystemPrincipal + ? Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL + : Ci.nsILoadInfo.SEC_NORMAL; + } + + 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; + } + + return this.ioService.newChannelFromURI2(uri, + loadingNode || null, + loadingPrincipal || null, + triggeringPrincipal || null, + securityFlags, + contentPolicyType); + }, + + /** + * 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); + } + }, + + /** + * Returns a reference to nsIIOService. + * + * @return a reference to nsIIOService. + */ + get ioService() + { + delete this.ioService; + return this.ioService = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + }, +}; diff --git a/netwerk/base/NetworkActivityMonitor.cpp b/netwerk/base/NetworkActivityMonitor.cpp new file mode 100644 index 000000000..887878977 --- /dev/null +++ b/netwerk/base/NetworkActivityMonitor.cpp @@ -0,0 +1,300 @@ +/* -*- 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 "NetworkActivityMonitor.h" +#include "prmem.h" +#include "nsIObserverService.h" +#include "nsPISocketTransportService.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "mozilla/Services.h" +#include "prerror.h" + +using namespace mozilla::net; + +static PRStatus +nsNetMon_Connect(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout) +{ + PRStatus ret; + PRErrorCode code; + ret = fd->lower->methods->connect(fd->lower, addr, timeout); + if (ret == PR_SUCCESS || (code = PR_GetError()) == PR_WOULD_BLOCK_ERROR || + code == PR_IN_PROGRESS_ERROR) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload); + return ret; +} + +static int32_t +nsNetMon_Read(PRFileDesc *fd, void *buf, int32_t len) +{ + int32_t ret; + ret = fd->lower->methods->read(fd->lower, buf, len); + if (ret >= 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload); + return ret; +} + +static int32_t +nsNetMon_Write(PRFileDesc *fd, const void *buf, int32_t len) +{ + int32_t ret; + ret = fd->lower->methods->write(fd->lower, buf, len); + if (ret > 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload); + return ret; +} + +static int32_t +nsNetMon_Writev(PRFileDesc *fd, + const PRIOVec *iov, + int32_t size, + PRIntervalTime timeout) +{ + int32_t ret; + ret = fd->lower->methods->writev(fd->lower, iov, size, timeout); + if (ret > 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload); + return ret; +} + +static int32_t +nsNetMon_Recv(PRFileDesc *fd, + void *buf, + int32_t amount, + int flags, + PRIntervalTime timeout) +{ + int32_t ret; + ret = fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout); + if (ret >= 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload); + return ret; +} + +static int32_t +nsNetMon_Send(PRFileDesc *fd, + const void *buf, + int32_t amount, + int flags, + PRIntervalTime timeout) +{ + int32_t ret; + ret = fd->lower->methods->send(fd->lower, buf, amount, flags, timeout); + if (ret > 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload); + return ret; +} + +static int32_t +nsNetMon_RecvFrom(PRFileDesc *fd, + void *buf, + int32_t amount, + int flags, + PRNetAddr *addr, + PRIntervalTime timeout) +{ + int32_t ret; + ret = fd->lower->methods->recvfrom(fd->lower, + buf, + amount, + flags, + addr, + timeout); + if (ret >= 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload); + 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; + ret = fd->lower->methods->sendto(fd->lower, + buf, + amount, + flags, + addr, + timeout); + if (ret > 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload); + return ret; +} + +static int32_t +nsNetMon_AcceptRead(PRFileDesc *listenSock, + PRFileDesc **acceptedSock, + PRNetAddr **peerAddr, + void *buf, + int32_t amount, + PRIntervalTime timeout) +{ + int32_t ret; + ret = listenSock->lower->methods->acceptread(listenSock->lower, + acceptedSock, + peerAddr, + buf, + amount, + timeout); + if (ret > 0) + NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload); + return ret; +} + + +class NotifyNetworkActivity : public mozilla::Runnable { +public: + explicit NotifyNetworkActivity(NetworkActivityMonitor::Direction aDirection) + : mDirection(aDirection) + {} + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (!obs) + return NS_ERROR_FAILURE; + + obs->NotifyObservers(nullptr, + mDirection == NetworkActivityMonitor::kUpload + ? NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC + : NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC, + nullptr); + return NS_OK; + } +private: + NetworkActivityMonitor::Direction mDirection; +}; + +NetworkActivityMonitor * NetworkActivityMonitor::gInstance = nullptr; +static PRDescIdentity sNetActivityMonitorLayerIdentity; +static PRIOMethods sNetActivityMonitorLayerMethods; +static PRIOMethods *sNetActivityMonitorLayerMethodsPtr = nullptr; + +NetworkActivityMonitor::NetworkActivityMonitor() + : mBlipInterval(PR_INTERVAL_NO_TIMEOUT) +{ + MOZ_COUNT_CTOR(NetworkActivityMonitor); + + NS_ASSERTION(gInstance==nullptr, + "multiple NetworkActivityMonitor instances!"); +} + +NetworkActivityMonitor::~NetworkActivityMonitor() +{ + MOZ_COUNT_DTOR(NetworkActivityMonitor); + gInstance = nullptr; +} + +nsresult +NetworkActivityMonitor::Init(int32_t blipInterval) +{ + nsresult rv; + + if (gInstance) + return NS_ERROR_ALREADY_INITIALIZED; + + NetworkActivityMonitor * mon = new NetworkActivityMonitor(); + rv = mon->Init_Internal(blipInterval); + if (NS_FAILED(rv)) { + delete mon; + return rv; + } + + gInstance = mon; + return NS_OK; +} + +nsresult +NetworkActivityMonitor::Shutdown() +{ + if (!gInstance) + return NS_ERROR_NOT_INITIALIZED; + + delete gInstance; + return NS_OK; +} + +nsresult +NetworkActivityMonitor::Init_Internal(int32_t blipInterval) +{ + 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; + sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods; + } + + mBlipInterval = PR_MillisecondsToInterval(blipInterval); + // Set the last notification times to time that has just expired, so any + // activity even right now will trigger notification. + mLastNotificationTime[kUpload] = PR_IntervalNow() - mBlipInterval; + mLastNotificationTime[kDownload] = mLastNotificationTime[kUpload]; + + return NS_OK; +} + +nsresult +NetworkActivityMonitor::AttachIOLayer(PRFileDesc *fd) +{ + if (!gInstance) + return NS_OK; + + PRFileDesc * layer; + PRStatus status; + + layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity, + sNetActivityMonitorLayerMethodsPtr); + if (!layer) { + return NS_ERROR_FAILURE; + } + + status = PR_PushIOLayer(fd, PR_NSPR_IO_LAYER, layer); + + if (status == PR_FAILURE) { + PR_DELETE(layer); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +NetworkActivityMonitor::DataInOut(Direction direction) +{ + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + if (gInstance) { + PRIntervalTime now = PR_IntervalNow(); + if ((now - gInstance->mLastNotificationTime[direction]) > + gInstance->mBlipInterval) { + gInstance->mLastNotificationTime[direction] = now; + gInstance->PostNotification(direction); + } + } + + return NS_OK; +} + +void +NetworkActivityMonitor::PostNotification(Direction direction) +{ + nsCOMPtr<nsIRunnable> ev = new NotifyNetworkActivity(direction); + NS_DispatchToMainThread(ev); +} diff --git a/netwerk/base/NetworkActivityMonitor.h b/netwerk/base/NetworkActivityMonitor.h new file mode 100644 index 000000000..28c9a911e --- /dev/null +++ b/netwerk/base/NetworkActivityMonitor.h @@ -0,0 +1,46 @@ +/* -*- 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/. */ + +#ifndef NetworkActivityMonitor_h___ +#define NetworkActivityMonitor_h___ + +#include <stdint.h> +#include "nscore.h" +#include "prio.h" +#include "prinrval.h" + +namespace mozilla { namespace net { + +class NetworkActivityMonitor +{ +public: + enum Direction { + kUpload = 0, + kDownload = 1 + }; + + NetworkActivityMonitor(); + ~NetworkActivityMonitor(); + + static nsresult Init(int32_t blipInterval); + static nsresult Shutdown(); + + static nsresult AttachIOLayer(PRFileDesc *fd); + static nsresult DataInOut(Direction direction); + +private: + nsresult Init_Internal(int32_t blipInterval); + void PostNotification(Direction direction); + + static NetworkActivityMonitor * gInstance; + PRIntervalTime mBlipInterval; + PRIntervalTime mLastNotificationTime[2]; +}; + +} // namespace net +} // namespace mozilla + +#endif /* NetworkActivityMonitor_h___ */ diff --git a/netwerk/base/NetworkInfoServiceCocoa.cpp b/netwerk/base/NetworkInfoServiceCocoa.cpp new file mode 100644 index 000000000..937c72658 --- /dev/null +++ b/netwerk/base/NetworkInfoServiceCocoa.cpp @@ -0,0 +1,104 @@ +/* -*- 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 <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <net/if.h> +#include <netdb.h> + +#include "mozilla/DebugOnly.h" +#include "mozilla/ScopeExit.h" + +#include "NetworkInfoServiceImpl.h" + +namespace mozilla { +namespace net { + +static nsresult +ListInterfaceAddresses(int aFd, const char* aIface, AddrMapType& aAddrMap); + +nsresult +DoListAddresses(AddrMapType& aAddrMap) +{ + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + return NS_ERROR_FAILURE; + } + + auto autoCloseSocket = MakeScopeExit([&] { + close(fd); + }); + + struct ifconf ifconf; + /* 16k of space should be enough to list all interfaces. Worst case, if it's + * not then we will error out and fail to list addresses. This should only + * happen on pathological machines with way too many interfaces. + */ + char buf[16384]; + + ifconf.ifc_len = sizeof(buf); + ifconf.ifc_buf = buf; + if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) { + return NS_ERROR_FAILURE; + } + + struct ifreq* ifreq = ifconf.ifc_req; + int i = 0; + while (i < ifconf.ifc_len) { + size_t len = IFNAMSIZ + ifreq->ifr_addr.sa_len; + + DebugOnly<nsresult> rv = + ListInterfaceAddresses(fd, ifreq->ifr_name, aAddrMap); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ListInterfaceAddresses failed"); + + ifreq = (struct ifreq*) ((char*)ifreq + len); + i += len; + } + + autoCloseSocket.release(); + return NS_OK; +} + +static nsresult +ListInterfaceAddresses(int aFd, const char* aInterface, AddrMapType& aAddrMap) +{ + struct ifreq ifreq; + memset(&ifreq, 0, sizeof(struct ifreq)); + strncpy(ifreq.ifr_name, aInterface, IFNAMSIZ - 1); + if (ioctl(aFd, SIOCGIFADDR, &ifreq) != 0) { + return NS_ERROR_FAILURE; + } + + char host[128]; + int family; + switch(family=ifreq.ifr_addr.sa_family) { + case AF_INET: + case AF_INET6: + getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host), 0, 0, NI_NUMERICHOST); + break; + case AF_UNSPEC: + return NS_OK; + default: + // Unknown family. + return NS_OK; + } + + nsCString ifaceStr; + ifaceStr.AssignASCII(aInterface); + + nsCString addrStr; + addrStr.AssignASCII(host); + + aAddrMap.Put(ifaceStr, addrStr); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/NetworkInfoServiceImpl.h b/netwerk/base/NetworkInfoServiceImpl.h new file mode 100644 index 000000000..6f92c335f --- /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 "nsDataHashtable.h" + +namespace mozilla { +namespace net { + +typedef nsDataHashtable<nsCStringHashKey, nsCString> AddrMapType; + +nsresult DoListAddresses(AddrMapType& aAddrMap); + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/NetworkInfoServiceLinux.cpp b/netwerk/base/NetworkInfoServiceLinux.cpp new file mode 100644 index 000000000..96627cfec --- /dev/null +++ b/netwerk/base/NetworkInfoServiceLinux.cpp @@ -0,0 +1,104 @@ +/* -*- 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 <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <net/if.h> +#include <netdb.h> + +#include "mozilla/DebugOnly.h" +#include "mozilla/ScopeExit.h" + +#include "NetworkInfoServiceImpl.h" + +namespace mozilla { +namespace net { + +static nsresult +ListInterfaceAddresses(int aFd, const char* aIface, AddrMapType& aAddrMap); + +nsresult +DoListAddresses(AddrMapType& aAddrMap) +{ + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + return NS_ERROR_FAILURE; + } + + auto autoCloseSocket = MakeScopeExit([&] { + close(fd); + }); + + struct ifconf ifconf; + /* 16k of space should be enough to list all interfaces. Worst case, if it's + * not then we will error out and fail to list addresses. This should only + * happen on pathological machines with way too many interfaces. + */ + char buf[16384]; + + ifconf.ifc_len = sizeof(buf); + ifconf.ifc_buf = buf; + if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) { + return NS_ERROR_FAILURE; + } + + struct ifreq* ifreq = ifconf.ifc_req; + int i = 0; + while (i < ifconf.ifc_len) { + size_t len = sizeof(struct ifreq); + + DebugOnly<nsresult> rv = + ListInterfaceAddresses(fd, ifreq->ifr_name, aAddrMap); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ListInterfaceAddresses failed"); + + ifreq = (struct ifreq*) ((char*)ifreq + len); + i += len; + } + + autoCloseSocket.release(); + return NS_OK; +} + +static nsresult +ListInterfaceAddresses(int aFd, const char* aInterface, AddrMapType& aAddrMap) +{ + struct ifreq ifreq; + memset(&ifreq, 0, sizeof(struct ifreq)); + strncpy(ifreq.ifr_name, aInterface, IFNAMSIZ - 1); + if (ioctl(aFd, SIOCGIFADDR, &ifreq) != 0) { + return NS_ERROR_FAILURE; + } + + char host[128]; + int family; + switch(family=ifreq.ifr_addr.sa_family) { + case AF_INET: + case AF_INET6: + getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host), 0, 0, NI_NUMERICHOST); + break; + case AF_UNSPEC: + return NS_OK; + default: + // Unknown family. + return NS_OK; + } + + nsCString ifaceStr; + ifaceStr.AssignASCII(aInterface); + + nsCString addrStr; + addrStr.AssignASCII(host); + + aAddrMap.Put(ifaceStr, addrStr); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/NetworkInfoServiceWindows.cpp b/netwerk/base/NetworkInfoServiceWindows.cpp new file mode 100644 index 000000000..2a9448e35 --- /dev/null +++ b/netwerk/base/NetworkInfoServiceWindows.cpp @@ -0,0 +1,60 @@ +/* -*- 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 <winsock2.h> +#include <ws2ipdef.h> +#include <iphlpapi.h> + +#include "mozilla/UniquePtr.h" + +#include "NetworkInfoServiceImpl.h" + +namespace mozilla { +namespace net { + +nsresult +DoListAddresses(AddrMapType& aAddrMap) +{ + UniquePtr<MIB_IPADDRTABLE> ipAddrTable; + DWORD size = sizeof(MIB_IPADDRTABLE); + + ipAddrTable.reset((MIB_IPADDRTABLE*) malloc(size)); + if (!ipAddrTable) { + return NS_ERROR_FAILURE; + } + + DWORD retVal = GetIpAddrTable(ipAddrTable.get(), &size, 0); + if (retVal == ERROR_INSUFFICIENT_BUFFER) { + ipAddrTable.reset((MIB_IPADDRTABLE*) malloc(size)); + if (!ipAddrTable) { + return NS_ERROR_FAILURE; + } + retVal = GetIpAddrTable(ipAddrTable.get(), &size, 0); + } + if (retVal != NO_ERROR) { + return NS_ERROR_FAILURE; + } + + for (DWORD i = 0; i < ipAddrTable->dwNumEntries; i++) { + int index = ipAddrTable->table[i].dwIndex; + uint32_t addrVal = (uint32_t) ipAddrTable->table[i].dwAddr; + + nsCString indexString; + indexString.AppendInt(index, 10); + + nsCString addrString; + addrString.AppendPrintf("%d.%d.%d.%d", + (addrVal >> 0) & 0xff, (addrVal >> 8) & 0xff, + (addrVal >> 16) & 0xff, (addrVal >> 24) & 0xff); + + aAddrMap.Put(indexString, addrString); + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/PollableEvent.cpp b/netwerk/base/PollableEvent.cpp new file mode 100644 index 000000000..9cb45efde --- /dev/null +++ b/netwerk/base/PollableEvent.cpp @@ -0,0 +1,347 @@ +/* -*- 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 "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(PR_GetCurrentThread() == gSocketThread); + 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; + } + } + + reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200)); + if (!reader) { + goto failed; + } + 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() + : mWriteFD(nullptr) + , mReadFD(nullptr) + , mSignaled(false) +{ + MOZ_COUNT_CTOR(PollableEvent); + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + // 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; + 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 (PR_GetCurrentThread() == gSocketThread) { + 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 + + mSignaled = true; + 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; + } + return (status == 1); +} + +bool +PollableEvent::Clear() +{ + // necessary because of the "dont signal on socket thread" optimization + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + SOCKET_LOG(("PollableEvent::Clear\n")); + 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::Signal 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::Signal 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 + +} +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/PollableEvent.h b/netwerk/base/PollableEvent.h new file mode 100644 index 000000000..800079932 --- /dev/null +++ b/netwerk/base/PollableEvent.h @@ -0,0 +1,38 @@ +/* -*- 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" + +namespace mozilla { +namespace net { + +// class must be called locked +class PollableEvent +{ +public: + PollableEvent(); + ~PollableEvent(); + + // Signal/Clear return false only if they fail + bool Signal(); + bool Clear(); + bool Valid() { return mWriteFD && mReadFD; } + + PRFileDesc *PollableFD() { return mReadFD; } + +private: + PRFileDesc *mWriteFD; + PRFileDesc *mReadFD; + bool mSignaled; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp new file mode 100644 index 000000000..e97b11d16 --- /dev/null +++ b/netwerk/base/Predictor.cpp @@ -0,0 +1,2550 @@ +/* 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 "nsICacheStorageService.h" +#include "nsICachingChannel.h" +#include "nsICancelable.h" +#include "nsIChannel.h" +#include "nsContentUtils.h" +#include "nsIDNSService.h" +#include "nsIDocument.h" +#include "nsIFile.h" +#include "nsIHttpChannel.h" +#include "nsIInputStream.h" +#include "nsIIOService.h" +#include "nsILoadContext.h" +#include "nsILoadContextInfo.h" +#include "nsILoadGroup.h" +#include "nsINetworkPredictorVerifier.h" +#include "nsIObserverService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.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/Preferences.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" + +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 RETURN_IF_FAILED(_rv) \ + do { \ + if (NS_FAILED(_rv)) { \ + return; \ + } \ + } while (0) + +#define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC) + + +static const char PREDICTOR_ENABLED_PREF[] = "network.predictor.enabled"; +static const char PREDICTOR_SSL_HOVER_PREF[] = "network.predictor.enable-hover-on-ssl"; +static const char PREDICTOR_PREFETCH_PREF[] = "network.predictor.enable-prefetch"; + +static const char PREDICTOR_PAGE_DELTA_DAY_PREF[] = + "network.predictor.page-degradation.day"; +static const int32_t PREDICTOR_PAGE_DELTA_DAY_DEFAULT = 0; +static const char PREDICTOR_PAGE_DELTA_WEEK_PREF[] = + "network.predictor.page-degradation.week"; +static const int32_t PREDICTOR_PAGE_DELTA_WEEK_DEFAULT = 5; +static const char PREDICTOR_PAGE_DELTA_MONTH_PREF[] = + "network.predictor.page-degradation.month"; +static const int32_t PREDICTOR_PAGE_DELTA_MONTH_DEFAULT = 10; +static const char PREDICTOR_PAGE_DELTA_YEAR_PREF[] = + "network.predictor.page-degradation.year"; +static const int32_t PREDICTOR_PAGE_DELTA_YEAR_DEFAULT = 25; +static const char PREDICTOR_PAGE_DELTA_MAX_PREF[] = + "network.predictor.page-degradation.max"; +static const int32_t PREDICTOR_PAGE_DELTA_MAX_DEFAULT = 50; +static const char PREDICTOR_SUB_DELTA_DAY_PREF[] = + "network.predictor.subresource-degradation.day"; +static const int32_t PREDICTOR_SUB_DELTA_DAY_DEFAULT = 1; +static const char PREDICTOR_SUB_DELTA_WEEK_PREF[] = + "network.predictor.subresource-degradation.week"; +static const int32_t PREDICTOR_SUB_DELTA_WEEK_DEFAULT = 10; +static const char PREDICTOR_SUB_DELTA_MONTH_PREF[] = + "network.predictor.subresource-degradation.month"; +static const int32_t PREDICTOR_SUB_DELTA_MONTH_DEFAULT = 25; +static const char PREDICTOR_SUB_DELTA_YEAR_PREF[] = + "network.predictor.subresource-degradation.year"; +static const int32_t PREDICTOR_SUB_DELTA_YEAR_DEFAULT = 50; +static const char PREDICTOR_SUB_DELTA_MAX_PREF[] = + "network.predictor.subresource-degradation.max"; +static const int32_t PREDICTOR_SUB_DELTA_MAX_DEFAULT = 100; + +static const char PREDICTOR_PREFETCH_ROLLING_LOAD_PREF[] = + "network.predictor.prefetch-rolling-load-count"; +static const int32_t PREFETCH_ROLLING_LOAD_DEFAULT = 10; +static const char PREDICTOR_PREFETCH_MIN_PREF[] = + "network.predictor.prefetch-min-confidence"; +static const int32_t PREFETCH_MIN_DEFAULT = 100; +static const char PREDICTOR_PRECONNECT_MIN_PREF[] = + "network.predictor.preconnect-min-confidence"; +static const int32_t PRECONNECT_MIN_DEFAULT = 90; +static const char PREDICTOR_PRERESOLVE_MIN_PREF[] = + "network.predictor.preresolve-min-confidence"; +static const int32_t PRERESOLVE_MIN_DEFAULT = 60; +static const char PREDICTOR_REDIRECT_LIKELY_PREF[] = + "network.predictor.redirect-likely-confidence"; +static const int32_t REDIRECT_LIKELY_DEFAULT = 75; + +static const char PREDICTOR_PREFETCH_FORCE_VALID_PREF[] = + "network.predictor.prefetch-force-valid-for"; +static const int32_t PREFETCH_FORCE_VALID_DEFAULT = 10; + +static const char PREDICTOR_MAX_RESOURCES_PREF[] = + "network.predictor.max-resources-per-entry"; +static const uint32_t PREDICTOR_MAX_RESOURCES_DEFAULT = 100; + +// This is selected in concert with max-resources-per-entry to keep memory usage +// low-ish. The default of the combo of the two is ~50k +static const char PREDICTOR_MAX_URI_LENGTH_PREF[] = + "network.predictor.max-uri-length"; +static const uint32_t PREDICTOR_MAX_URI_LENGTH_DEFAULT = 500; + +static const char PREDICTOR_DOING_TESTS_PREF[] = "network.predictor.doing-tests"; + +static const char PREDICTOR_CLEANED_UP_PREF[] = "network.predictor.cleaned-up"; + +// 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; + +static const uint32_t STARTUP_WINDOW = 5U * 60U; // 5min + +// 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, nsIIOService *ioService) +{ + nsAutoCString s; + s.Truncate(); + nsresult rv = nsContentUtils::GetASCIIOrigin(uri, s); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewURI(originUri, s, nullptr, nullptr, ioService); +} + +// 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; + } + + bool isHTTP = false; + uri->SchemeIs("http", &isHTTP); + if (!isHTTP) { + uri->SchemeIs("https", &isHTTP); + } + + return isHTTP; +} + +// 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; + } +} + +Predictor::Action::~Action() +{ } + +NS_IMETHODIMP +Predictor::Action::OnCacheEntryCheck(nsICacheEntry *entry, + nsIApplicationCache *appCache, + uint32_t *result) +{ + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Action::OnCacheEntryAvailable(nsICacheEntry *entry, bool isNew, + nsIApplicationCache *appCache, + 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%08x", + this, entry, mFullUri, mPredict, mPredictReason, mLearnReason, + targetURI.get(), sourceURI.get(), mStackCount, + isNew, result)); + if (NS_FAILED(result)) { + PREDICTOR_LOG(("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08X). " + "Aborting.", this, 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() + :mInitialized(false) + ,mEnabled(true) + ,mEnableHoverOnSSL(false) + ,mEnablePrefetch(true) + ,mPageDegradationDay(PREDICTOR_PAGE_DELTA_DAY_DEFAULT) + ,mPageDegradationWeek(PREDICTOR_PAGE_DELTA_WEEK_DEFAULT) + ,mPageDegradationMonth(PREDICTOR_PAGE_DELTA_MONTH_DEFAULT) + ,mPageDegradationYear(PREDICTOR_PAGE_DELTA_YEAR_DEFAULT) + ,mPageDegradationMax(PREDICTOR_PAGE_DELTA_MAX_DEFAULT) + ,mSubresourceDegradationDay(PREDICTOR_SUB_DELTA_DAY_DEFAULT) + ,mSubresourceDegradationWeek(PREDICTOR_SUB_DELTA_WEEK_DEFAULT) + ,mSubresourceDegradationMonth(PREDICTOR_SUB_DELTA_MONTH_DEFAULT) + ,mSubresourceDegradationYear(PREDICTOR_SUB_DELTA_YEAR_DEFAULT) + ,mSubresourceDegradationMax(PREDICTOR_SUB_DELTA_MAX_DEFAULT) + ,mPrefetchRollingLoadCount(PREFETCH_ROLLING_LOAD_DEFAULT) + ,mPrefetchMinConfidence(PREFETCH_MIN_DEFAULT) + ,mPreconnectMinConfidence(PRECONNECT_MIN_DEFAULT) + ,mPreresolveMinConfidence(PRERESOLVE_MIN_DEFAULT) + ,mRedirectLikelyConfidence(REDIRECT_LIKELY_DEFAULT) + ,mPrefetchForceValidFor(PREFETCH_FORCE_VALID_DEFAULT) + ,mMaxResourcesPerEntry(PREDICTOR_MAX_RESOURCES_DEFAULT) + ,mStartupCount(1) + ,mMaxURILength(PREDICTOR_MAX_URI_LENGTH_DEFAULT) + ,mDoingTests(false) +{ + 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); + + Preferences::AddBoolVarCache(&mEnabled, PREDICTOR_ENABLED_PREF, true); + Preferences::AddBoolVarCache(&mEnableHoverOnSSL, + PREDICTOR_SSL_HOVER_PREF, false); + Preferences::AddBoolVarCache(&mEnablePrefetch, PREDICTOR_PREFETCH_PREF, true); + Preferences::AddIntVarCache(&mPageDegradationDay, + PREDICTOR_PAGE_DELTA_DAY_PREF, + PREDICTOR_PAGE_DELTA_DAY_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationWeek, + PREDICTOR_PAGE_DELTA_WEEK_PREF, + PREDICTOR_PAGE_DELTA_WEEK_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationMonth, + PREDICTOR_PAGE_DELTA_MONTH_PREF, + PREDICTOR_PAGE_DELTA_MONTH_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationYear, + PREDICTOR_PAGE_DELTA_YEAR_PREF, + PREDICTOR_PAGE_DELTA_YEAR_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationMax, + PREDICTOR_PAGE_DELTA_MAX_PREF, + PREDICTOR_PAGE_DELTA_MAX_DEFAULT); + + Preferences::AddIntVarCache(&mSubresourceDegradationDay, + PREDICTOR_SUB_DELTA_DAY_PREF, + PREDICTOR_SUB_DELTA_DAY_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationWeek, + PREDICTOR_SUB_DELTA_WEEK_PREF, + PREDICTOR_SUB_DELTA_WEEK_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationMonth, + PREDICTOR_SUB_DELTA_MONTH_PREF, + PREDICTOR_SUB_DELTA_MONTH_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationYear, + PREDICTOR_SUB_DELTA_YEAR_PREF, + PREDICTOR_SUB_DELTA_YEAR_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationMax, + PREDICTOR_SUB_DELTA_MAX_PREF, + PREDICTOR_SUB_DELTA_MAX_DEFAULT); + + Preferences::AddIntVarCache(&mPrefetchRollingLoadCount, + PREDICTOR_PREFETCH_ROLLING_LOAD_PREF, + PREFETCH_ROLLING_LOAD_DEFAULT); + Preferences::AddIntVarCache(&mPrefetchMinConfidence, + PREDICTOR_PREFETCH_MIN_PREF, + PREFETCH_MIN_DEFAULT); + Preferences::AddIntVarCache(&mPreconnectMinConfidence, + PREDICTOR_PRECONNECT_MIN_PREF, + PRECONNECT_MIN_DEFAULT); + Preferences::AddIntVarCache(&mPreresolveMinConfidence, + PREDICTOR_PRERESOLVE_MIN_PREF, + PRERESOLVE_MIN_DEFAULT); + Preferences::AddIntVarCache(&mRedirectLikelyConfidence, + PREDICTOR_REDIRECT_LIKELY_PREF, + REDIRECT_LIKELY_DEFAULT); + + Preferences::AddIntVarCache(&mPrefetchForceValidFor, + PREDICTOR_PREFETCH_FORCE_VALID_PREF, + PREFETCH_FORCE_VALID_DEFAULT); + + Preferences::AddIntVarCache(&mMaxResourcesPerEntry, + PREDICTOR_MAX_RESOURCES_PREF, + PREDICTOR_MAX_RESOURCES_DEFAULT); + + Preferences::AddBoolVarCache(&mCleanedUp, PREDICTOR_CLEANED_UP_PREF, false); + + Preferences::AddUintVarCache(&mMaxURILength, PREDICTOR_MAX_URI_LENGTH_PREF, + PREDICTOR_MAX_URI_LENGTH_DEFAULT); + + Preferences::AddBoolVarCache(&mDoingTests, PREDICTOR_DOING_TESTS_PREF, false); + + if (!mCleanedUp) { + mCleanupTimer = do_CreateInstance("@mozilla.org/timer;1"); + mCleanupTimer->Init(this, 60 * 1000, nsITimer::TYPE_ONE_SHOT); + } + + 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); + } + + if (mCleanupTimer) { + mCleanupTimer->Cancel(); + mCleanupTimer = nullptr; + } +} + +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(); + } else if (!strcmp("timer-callback", topic)) { + MaybeCleanupOldDBFiles(); + mCleanupTimer = nullptr; + } + + 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), + NS_LITERAL_CSTRING(META_DATA_PREFIX)) && + !NS_LITERAL_CSTRING(SEEN_META_DATA).Equals(key) && + !NS_LITERAL_CSTRING(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(); + } + + nsCOMPtr<nsICacheStorageService> cacheStorageService = + do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<LoadContextInfo> lci = + new LoadContextInfo(false, NeckoOriginAttributes()); + + rv = cacheStorageService->DiskCacheStorage(lci, false, + getter_AddRefs(mCacheDiskStorage)); + NS_ENSURE_SUCCESS(rv, rv); + + mIOService = do_GetService("@mozilla.org/network/io-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewURI(getter_AddRefs(mStartupURI), + "predictor://startup", nullptr, mIOService); + NS_ENSURE_SUCCESS(rv, rv); + + mSpeculativeService = do_QueryInterface(mIOService, &rv); + 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 PredictorThreadShutdownRunner : public Runnable +{ +public: + PredictorThreadShutdownRunner(nsIThread *ioThread, bool success) + :mIOThread(ioThread) + ,mSuccess(success) + { } + ~PredictorThreadShutdownRunner() { } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread(), "Shutting down io thread off main thread!"); + if (mSuccess) { + // This means the cleanup happened. Mark so we don't try in the + // future. + Preferences::SetBool(PREDICTOR_CLEANED_UP_PREF, true); + } + return mIOThread->AsyncShutdown(); + } + +private: + nsCOMPtr<nsIThread> mIOThread; + bool mSuccess; +}; + +class PredictorOldCleanupRunner : public Runnable +{ +public: + PredictorOldCleanupRunner(nsIThread *ioThread, nsIFile *dbFile) + :mIOThread(ioThread) + ,mDBFile(dbFile) + { } + + ~PredictorOldCleanupRunner() { } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(!NS_IsMainThread(), "Cleaning up old files on main thread!"); + nsresult rv = CheckForAndDeleteOldDBFiles(); + RefPtr<PredictorThreadShutdownRunner> runner = + new PredictorThreadShutdownRunner(mIOThread, NS_SUCCEEDED(rv)); + NS_DispatchToMainThread(runner); + return NS_OK; + } + +private: + nsresult CheckForAndDeleteOldDBFiles() + { + nsCOMPtr<nsIFile> oldDBFile; + nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite")); + NS_ENSURE_SUCCESS(rv, rv); + + bool fileExists = false; + rv = oldDBFile->Exists(&fileExists); + NS_ENSURE_SUCCESS(rv, rv); + + if (fileExists) { + rv = oldDBFile->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + } + + fileExists = false; + rv = mDBFile->Exists(&fileExists); + NS_ENSURE_SUCCESS(rv, rv); + + if (fileExists) { + rv = mDBFile->Remove(false); + } + + return rv; + } + + nsCOMPtr<nsIThread> mIOThread; + nsCOMPtr<nsIFile> mDBFile; +}; + +} // namespace + +void +Predictor::MaybeCleanupOldDBFiles() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mEnabled || mCleanedUp) { + return; + } + + mCleanedUp = true; + + // This is used for cleaning up junk left over from the old backend + // built on top of sqlite, if necessary. + nsCOMPtr<nsIFile> dbFile; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(dbFile)); + RETURN_IF_FAILED(rv); + rv = dbFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite")); + RETURN_IF_FAILED(rv); + + nsCOMPtr<nsIThread> ioThread; + rv = NS_NewNamedThread("NetPredictClean", getter_AddRefs(ioThread)); + RETURN_IF_FAILED(rv); + + RefPtr<PredictorOldCleanupRunner> runner = + new PredictorOldCleanupRunner(ioThread, dbFile); + ioThread->Dispatch(runner, NS_DISPATCH_NORMAL); +} + +void +Predictor::Shutdown() +{ + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!"); + return; + } + + RemoveObserver(); + + mInitialized = false; +} + +nsresult +Predictor::Create(nsISupports *aOuter, const nsIID& aIID, + void **aResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + + RefPtr<Predictor> svc = new Predictor(); + if (IsNeckoChild()) { + // 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; +} + +// Called from the main thread to initiate predictive actions +NS_IMETHODIMP +Predictor::Predict(nsIURI *targetURI, nsIURI *sourceURI, + PredictorPredictReason reason, nsILoadContext *loadContext, + 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")); + + ipc::OptionalURIParams serTargetURI, serSourceURI; + SerializeURI(targetURI, serTargetURI); + SerializeURI(sourceURI, serSourceURI); + + IPC::SerializedLoadContext serLoadContext; + serLoadContext.Init(loadContext); + + // 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(serTargetURI, serSourceURI, + reason, serLoadContext, verifier); + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!mEnabled) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + if (loadContext && loadContext->UsePrivateBrowsing()) { + // 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, 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())); + uint32_t openFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::OPEN_PRIORITY | + nsICacheStorage::CHECK_MULTITHREADED; + mCacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), openFlags, uriAction); + + // Now we do the origin-only (and therefore predictor-only) entry + nsCOMPtr<nsIURI> targetOrigin; + nsresult rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin), mIOService); + 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; + mCacheDiskStorage->AsyncOpenURI(originKey, + NS_LITERAL_CSTRING(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; + + if (reason == nsINetworkPredictor::PREDICT_LOAD) { + MaybeLearnForStartup(targetURI, fullUri); + } + + 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, + nsINetworkPredictorVerifier *verifier) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForLink")); + if (!mSpeculativeService) { + PREDICTOR_LOG((" missing speculative service")); + return; + } + + if (!mEnableHoverOnSSL) { + bool isSSL = false; + sourceURI->SchemeIs("https", &isSSL); + if (isSSL) { + // 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; + } + } + + mSpeculativeService->SpeculativeConnect2(targetURI, nullptr, nullptr); + 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)); + + int32_t loadCount; + rv = entry->GetFetchCount(&loadCount); + 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); + 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; + mCacheDiskStorage->AsyncOpenURI(redirectURI, EmptyCString(), openFlags, + redirectAction); + return RunPredictions(nullptr, verifier); + } + + CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation, fullUri); + + return RunPredictions(targetURI, 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")); + int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime); + CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount, + globalDegradation, fullUri); + return RunPredictions(nullptr, 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 = mPageDegradationDay; + } else if (delta < ONE_WEEK) { + globalDegradation = mPageDegradationWeek; + } else if (delta < ONE_MONTH) { + globalDegradation = mPageDegradationMonth; + } else if (delta < ONE_YEAR) { + globalDegradation = mPageDegradationYear; + } else { + globalDegradation = mPageDegradationMax; + } + + 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 = mPreconnectMinConfidence - 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 = mSubresourceDegradationDay; + } else if (delta < ONE_WEEK) { + confidenceDegradation = mSubresourceDegradationWeek; + } else if (delta < ONE_MONTH) { + confidenceDegradation = mSubresourceDegradationMonth; + } else if (delta < ONE_YEAR) { + confidenceDegradation = mSubresourceDegradationYear; + } else { + confidenceDegradation = mSubresourceDegradationMax; + 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()); +} + +void +Predictor::SanitizePrefs() +{ + if (mPrefetchRollingLoadCount < 0) { + mPrefetchRollingLoadCount = 0; + } else if (mPrefetchRollingLoadCount > kMaxPrefetchRollingLoadCount) { + mPrefetchRollingLoadCount = kMaxPrefetchRollingLoadCount; + } +} + +void +Predictor::CalculatePredictions(nsICacheEntry *entry, nsIURI *referrer, + uint32_t lastLoad, uint32_t loadCount, + int32_t globalDegradation, bool fullUri) +{ + MOZ_ASSERT(NS_IsMainThread()); + + SanitizePrefs(); + + // 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, valuesToOperateOn; + keysToOperateOn.SwapElements(mKeysToOperateOn); + valuesToOperateOn.SwapElements(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(); + + nsCOMPtr<nsIURI> uri; + uint32_t hitCount, lastHit, flags; + if (!ParseMetaDataEntry(key, value, getter_AddRefs(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)); + if (!fullUri) { + // Not full URI - don't prefetch! No sense in it! + PREDICTOR_LOG((" forcing non-cacheability - 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")); + flags &= ~FLAG_PREFETCHABLE; + } else { + uint32_t expectedRollingLoadCount = (1 << mPrefetchRollingLoadCount) - 1; + expectedRollingLoadCount <<= kRollingLoadOffset; + if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) { + PREDICTOR_LOG((" forcing non-cacheability - missed a load")); + flags &= ~FLAG_PREFETCHABLE; + } + } + + PREDICTOR_LOG((" setting up prediction")); + SetupPrediction(confidence, flags, uri); + } +} + +// (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, nsIURI *uri) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsAutoCString uriStr; + uri->GetAsciiSpec(uriStr); + PREDICTOR_LOG(("SetupPrediction mEnablePrefetch=%d mPrefetchMinConfidence=%d " + "mPreconnectMinConfidence=%d mPreresolveMinConfidence=%d " + "flags=%d confidence=%d uri=%s", mEnablePrefetch, + mPrefetchMinConfidence, mPreconnectMinConfidence, + mPreresolveMinConfidence, flags, confidence, uriStr.get())); + if (mEnablePrefetch && (flags & FLAG_PREFETCHABLE) && + (mPrefetchRollingLoadCount || (confidence >= mPrefetchMinConfidence))) { + mPrefetches.AppendElement(uri); + } else if (confidence >= mPreconnectMinConfidence) { + mPreconnects.AppendElement(uri); + } else if (confidence >= mPreresolveMinConfidence) { + mPreresolves.AppendElement(uri); + } +} + +nsresult +Predictor::Prefetch(nsIURI *uri, nsIURI *referrer, + 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_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, /* aLoadGroup */ + nullptr, /* aCallbacks */ + nsIRequest::LOAD_BACKGROUND); + + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" NS_NewChannel failed rv=0x%X", 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; + } + + httpChannel->SetReferrer(referrer); + // XXX - set a header here to indicate this is a prefetch? + + nsCOMPtr<nsIStreamListener> listener = new PrefetchListener(verifier, uri, + this); + PREDICTOR_LOG((" calling AsyncOpen2 listener=%p channel=%p", listener.get(), + channel.get())); + rv = channel->AsyncOpen2(listener); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" AsyncOpen2 failed rv=0x%X", rv)); + } + + return rv; +} + +// Runs predictions that have been set up. +bool +Predictor::RunPredictions(nsIURI *referrer, 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, preconnects, preresolves; + prefetches.SwapElements(mPrefetches); + preconnects.SwapElements(mPreconnects); + preresolves.SwapElements(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, 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; + mSpeculativeService->SpeculativeConnect2(uri, nullptr, this); + predicted = true; + if (verifier) { + PREDICTOR_LOG((" sending preconnect verification")); + verifier->OnPredictPreconnect(uri); + } + } + + len = preresolves.Length(); + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + 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->AsyncResolve(hostname, + (nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + nsIDNSService::RESOLVE_SPECULATE), + mDNSListener, nullptr, + 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; +} + +// Called from the main thread to update the database +NS_IMETHODIMP +Predictor::Learn(nsIURI *targetURI, nsIURI *sourceURI, + PredictorLearnReason reason, + nsILoadContext *loadContext) +{ + 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")); + + ipc::URIParams serTargetURI; + SerializeURI(targetURI, serTargetURI); + + ipc::OptionalURIParams serSourceURI; + SerializeURI(sourceURI, serSourceURI); + + IPC::SerializedLoadContext serLoadContext; + serLoadContext.Init(loadContext); + + PREDICTOR_LOG((" forwarding to parent")); + gNeckoChild->SendPredLearn(serTargetURI, serSourceURI, reason, + serLoadContext); + return NS_OK; + } + + PREDICTOR_LOG((" called on parent process")); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return NS_OK; + } + + if (!mEnabled) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + if (loadContext && loadContext->UsePrivateBrowsing()) { + // 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), mIOService); + 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), mIOService); + 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), mIOService); + NS_ENSURE_SUCCESS(rv, rv); + rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin), mIOService); + 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())); + // 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; + } + mCacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), 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; + } + mCacheDiskStorage->AsyncOpenURI(originKey, + NS_LITERAL_CSTRING(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 && mDoingTests) { + PREDICTOR_LOG((" WARNING - updating rolling load count. " + "If you see this outside tests, you did it wrong")); + SanitizePrefs(); + + // 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, valuesToOperateOn; + keysToOperateOn.SwapElements(mKeysToOperateOn); + valuesToOperateOn.SwapElements(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(); + + nsCOMPtr<nsIURI> uri; + uint32_t hitCount, lastHit, flags; + if (!ParseMetaDataEntry(nullptr, value, nullptr, 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; + } + + uint32_t hitCount, lastHit, flags; + bool ok = mPredictor->ParseMetaDataEntry(nullptr, value, nullptr, + 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; + } + + nsCString uri(key + (sizeof(META_DATA_PREFIX) - 1)); + uint32_t uriLength = uri.Length(); + if (uriLength > mPredictor->mMaxURILength) { + // 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); + RETURN_IF_FAILED(rv); + + int32_t loadCount; + rv = entry->GetFetchCount(&loadCount); + RETURN_IF_FAILED(rv); + + nsCString key; + key.AssignLiteral(META_DATA_PREFIX); + nsCString uri; + targetURI->GetAsciiSpec(uri); + key.Append(uri); + if (uri.Length() > mMaxURILength) { + // 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(nullptr, value.BeginReading(), + nullptr, 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 >= mMaxResourcesPerEntry) { + 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, static_cast<uint32_t>(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%08X", 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) +{ + 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, nsIURI **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); + nsresult rv = NS_NewURI(uri, uriStart, nullptr, mIOService); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" NS_NewURI returned 0x%X", rv)); + return false; + } + PREDICTOR_LOG((" uri -> %s", uriStart)); + } + + 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 (!mEnabled) { + PREDICTOR_LOG((" not enabled")); + return NS_OK; + } + + RefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this); + PREDICTOR_LOG((" created a resetter")); + mCacheDiskStorage->AsyncVisitStorage(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, + nsIApplicationCache *appCache, + uint32_t *result) +{ + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry *entry, bool isNew, + nsIApplicationCache *appCache, + 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; + keysToDelete.SwapElements(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), + NS_LITERAL_CSTRING(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, int32_t fetchCount, + uint32_t lastModifiedTime, uint32_t expirationTime, + bool aPinned) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // 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 + mPredictor->mCacheDiskStorage->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); + } + + return NS_OK; +} + +NS_IMETHODIMP +Predictor::Resetter::OnCacheEntryVisitCompleted() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsTArray<nsCOMPtr<nsIURI>> urisToVisit; + urisToVisit.SwapElements(mURIsToVisit); + + MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length()); + if (!mEntriesToVisit) { + Complete(); + return NS_OK; + } + + uint32_t entriesToVisit = urisToVisit.Length(); + for (uint32_t i = 0; i < entriesToVisit; ++i) { + nsCString u; + urisToVisit[i]->GetAsciiSpec(u); + mPredictor->mCacheDiskStorage->AsyncOpenURI( + urisToVisit[i], EmptyCString(), + nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | nsICacheStorage::CHECK_MULTITHREADED, + this); + } + + 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 nsresult +EnsureGlobalPredictor(nsINetworkPredictor **aPredictor) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + nsCOMPtr<nsINetworkPredictor> predictor = + do_GetService("@mozilla.org/network/predictor;1", + &rv); + NS_ENSURE_SUCCESS(rv, rv); + + predictor.forget(aPredictor); + return NS_OK; +} + +nsresult +PredictorPredict(nsIURI *targetURI, nsIURI *sourceURI, + PredictorPredictReason reason, nsILoadContext *loadContext, + 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->Predict(targetURI, sourceURI, reason, + loadContext, verifier); +} + +nsresult +PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI, + PredictorLearnReason reason, + nsILoadContext *loadContext) +{ + 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->Learn(targetURI, sourceURI, reason, loadContext); +} + +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; + + if (loadGroup) { + nsCOMPtr<nsIInterfaceRequestor> callbacks; + loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) { + loadContext = do_GetInterface(callbacks); + } + } + + return predictor->Learn(targetURI, sourceURI, reason, loadContext); +} + +nsresult +PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI, + PredictorLearnReason reason, + nsIDocument *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); + + nsCOMPtr<nsILoadContext> loadContext; + + if (document) { + loadContext = document->GetLoadContext(); + } + + return predictor->Learn(targetURI, sourceURI, reason, loadContext); +} + +nsresult +PredictorLearnRedirect(nsIURI *targetURI, nsIChannel *channel, + nsILoadContext *loadContext) +{ + 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->Learn(targetURI, sourceURI, + nsINetworkPredictor::LEARN_LOAD_REDIRECT, + loadContext); +} + +// 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; + } + + ipc::URIParams serURI; + SerializeURI(aURI, serURI); + + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictPrefetch(serURI, 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; + } + + ipc::URIParams serURI; + SerializeURI(aURI, serURI); + + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictPreconnect(serURI)) { + 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; + } + + ipc::URIParams serURI; + SerializeURI(aURI, serURI); + + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent()); + if (!neckoParent) { + continue; + } + if (!neckoParent->SendPredOnPredictDNS(serURI)) { + 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, + nsISupports *aContext) +{ + mStartTime = TimeStamp::Now(); + return NS_OK; +} + +NS_IMETHODIMP +Predictor::PrefetchListener::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatusCode) +{ + PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%X", this, 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(mPredictor->mPrefetchForceValidFor); + PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%X", + mPredictor->mPrefetchForceValidFor, rv)); + } else { + rv = cachingChannel->ForceCacheEntryValidFor(0); + PREDICTOR_LOG((" removing any forced validity rv=%X", 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, + nsISupports *aContext, + 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) +{ + 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); + self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, + method); + } +} + +void +Predictor::UpdateCacheabilityInternal(nsIURI *sourceURI, nsIURI *targetURI, + uint32_t httpStatus, + const nsCString &method) +{ + PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus)); + + if (!mInitialized) { + PREDICTOR_LOG((" not initialized")); + return; + } + + if (!mEnabled) { + PREDICTOR_LOG((" not enabled")); + return; + } + + if (!mEnablePrefetch) { + PREDICTOR_LOG((" prefetch not enabled")); + return; + } + + uint32_t openFlags = nsICacheStorage::OPEN_READONLY | + nsICacheStorage::OPEN_SECRETLY | + nsICacheStorage::CHECK_MULTITHREADED; + RefPtr<Predictor::CacheabilityAction> action = + new Predictor::CacheabilityAction(targetURI, httpStatus, method, this); + nsAutoCString uri; + targetURI->GetAsciiSpec(uri); + PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get())); + mCacheDiskStorage->AsyncOpenURI(sourceURI, EmptyCString(), openFlags, action); +} + +NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction, + nsICacheEntryOpenCallback, + nsICacheEntryMetaDataVisitor); + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry *entry, + nsIApplicationCache *appCache, + uint32_t *result) +{ + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry *entry, + bool isNew, + nsIApplicationCache *appCache, + 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=%X isNew=%d", result, isNew)); + return NS_OK; + } + + nsresult rv = entry->VisitMetaData(this); + if (NS_FAILED(rv)) { + PREDICTOR_LOG((" VisitMetaData returned %x", rv)); + return NS_OK; + } + + nsTArray<nsCString> keysToCheck, valuesToCheck; + keysToCheck.SwapElements(mKeysToCheck); + valuesToCheck.SwapElements(mValuesToCheck); + + 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(); + nsCOMPtr<nsIURI> uri; + uint32_t hitCount, lastHit, flags; + + if (!mPredictor->ParseMetaDataEntry(key, value, getter_AddRefs(uri), + hitCount, lastHit, flags)) { + PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value)); + continue; + } + + bool eq = false; + if (NS_SUCCEEDED(uri->Equals(mTargetURI, &eq)) && eq) { + if (mHttpStatus == 200 && mMethod.EqualsLiteral("GET")) { + 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 000000000..69c597598 --- /dev/null +++ b/netwerk/base/Predictor.h @@ -0,0 +1,480 @@ +/* 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 "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 : 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(nsISupports *outer, 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 *reqponseHead, + nsILoadContextInfo *lci); + +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() + { } + + private: + virtual ~DNSListener() + { } + }; + + 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(); + + 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, Predictor *predictor) + :mTargetURI(targetURI) + ,mHttpStatus(httpStatus) + ,mMethod(method) + ,mPredictor(predictor) + { } + + private: + virtual ~CacheabilityAction() { } + + nsCOMPtr<nsIURI> mTargetURI; + uint32_t mHttpStatus; + nsCString mMethod; + 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() { } + + void Complete(); + + uint32_t mEntriesToVisit; + nsTArray<nsCString> mKeysToDelete; + RefPtr<Predictor> mPredictor; + nsTArray<nsCOMPtr<nsIURI>> mURIsToVisit; + }; + + 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() { } + 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() { } + + 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 + // * verifier - used for testing to verify the expected predictions happen + void PredictForLink(nsIURI *targetURI, + nsIURI *sourceURI, + 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); + + // 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 URI of the resource + void SetupPrediction(int32_t confidence, uint32_t flags, nsIURI *uri); + + // Used to kick off a prefetch from RunPredictions if necessary + // * uri - the URI to prefetch + // * referrer - the URI of the referring page + // * verifier - used for testing to ensure the expected prefetch happens + nsresult Prefetch(nsIURI *uri, nsIURI *referrer, 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 + // * verifier - used for testing to ensure the expected predictions happen + bool RunPredictions(nsIURI *referrer, 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 + void MaybeLearnForStartup(nsIURI *uri, bool fullUri); + + // 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 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, nsIURI **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); + + // Make sure our prefs are in their expected range of values + void SanitizePrefs(); + + // Our state + bool mInitialized; + + bool mEnabled; + bool mEnableHoverOnSSL; + bool mEnablePrefetch; + + int32_t mPageDegradationDay; + int32_t mPageDegradationWeek; + int32_t mPageDegradationMonth; + int32_t mPageDegradationYear; + int32_t mPageDegradationMax; + + int32_t mSubresourceDegradationDay; + int32_t mSubresourceDegradationWeek; + int32_t mSubresourceDegradationMonth; + int32_t mSubresourceDegradationYear; + int32_t mSubresourceDegradationMax; + + int32_t mPrefetchRollingLoadCount; + int32_t mPrefetchMinConfidence; + int32_t mPreconnectMinConfidence; + int32_t mPreresolveMinConfidence; + int32_t mRedirectLikelyConfidence; + + int32_t mPrefetchForceValidFor; + + int32_t mMaxResourcesPerEntry; + + bool mCleanedUp; + nsCOMPtr<nsITimer> mCleanupTimer; + + nsTArray<nsCString> mKeysToOperateOn; + nsTArray<nsCString> mValuesToOperateOn; + + nsCOMPtr<nsICacheStorage> mCacheDiskStorage; + + nsCOMPtr<nsIIOService> mIOService; + nsCOMPtr<nsISpeculativeConnect> mSpeculativeService; + + nsCOMPtr<nsIURI> mStartupURI; + uint32_t mStartupTime; + uint32_t mLastStartupTime; + int32_t mStartupCount; + + uint32_t mMaxURILength; + + nsCOMPtr<nsIDNSService> mDnsService; + + RefPtr<DNSListener> mDNSListener; + + nsTArray<nsCOMPtr<nsIURI>> mPrefetches; + nsTArray<nsCOMPtr<nsIURI>> mPreconnects; + nsTArray<nsCOMPtr<nsIURI>> mPreresolves; + + bool mDoingTests; + + 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 000000000..10c664502 --- /dev/null +++ b/netwerk/base/PrivateBrowsingChannel.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sts=4 sw=4 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) + { + // 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) + { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPrivateBrowsing; + return NS_OK; + } + + NS_IMETHOD IsPrivateModeOverriden(bool* aValue, bool *aResult) + { + 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; + Unused << channel->GetLoadInfo(getter_AddRefs(loadInfo)); + if (loadInfo) { + NeckoOriginAttributes 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/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp new file mode 100644 index 000000000..4d7a6c1fd --- /dev/null +++ b/netwerk/base/ProxyAutoConfig.cpp @@ -0,0 +1,1015 @@ +/* -*- 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 "nsThreadUtils.h" +#include "nsIConsoleService.h" +#include "nsIURLParser.h" +#include "nsJSUtils.h" +#include "jsfriendapi.h" +#include "prnetdb.h" +#include "nsITimer.h" +#include "mozilla/net/DNS.h" +#include "nsServiceManagerUtils.h" +#include "nsNetCID.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. + +static const char *sPacUtils = + "function dnsDomainIs(host, domain) {\n" + " return (host.length >= domain.length &&\n" + " host.substring(host.length - domain.length) == domain);\n" + "}\n" + "" + "function dnsDomainLevels(host) {\n" + " return host.split('.').length - 1;\n" + "}\n" + "" + "function convert_addr(ipchars) {\n" + " var bytes = ipchars.split('.');\n" + " var result = ((bytes[0] & 0xff) << 24) |\n" + " ((bytes[1] & 0xff) << 16) |\n" + " ((bytes[2] & 0xff) << 8) |\n" + " (bytes[3] & 0xff);\n" + " return result;\n" + "}\n" + "" + "function isInNet(ipaddr, pattern, maskstr) {\n" + " var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n" + " if (test == null) {\n" + " ipaddr = dnsResolve(ipaddr);\n" + " if (ipaddr == null)\n" + " return false;\n" + " } else if (test[1] > 255 || test[2] > 255 || \n" + " test[3] > 255 || test[4] > 255) {\n" + " return false; // not an IP address\n" + " }\n" + " var host = convert_addr(ipaddr);\n" + " var pat = convert_addr(pattern);\n" + " var mask = convert_addr(maskstr);\n" + " return ((host & mask) == (pat & mask));\n" + " \n" + "}\n" + "" + "function isPlainHostName(host) {\n" + " return (host.search('\\\\.') == -1);\n" + "}\n" + "" + "function isResolvable(host) {\n" + " var ip = dnsResolve(host);\n" + " return (ip != null);\n" + "}\n" + "" + "function localHostOrDomainIs(host, hostdom) {\n" + " return (host == hostdom) ||\n" + " (hostdom.lastIndexOf(host + '.', 0) == 0);\n" + "}\n" + "" + "function shExpMatch(url, pattern) {\n" + " pattern = pattern.replace(/\\./g, '\\\\.');\n" + " pattern = pattern.replace(/\\*/g, '.*');\n" + " pattern = pattern.replace(/\\?/g, '.');\n" + " var newRe = new RegExp('^'+pattern+'$');\n" + " return newRe.test(url);\n" + "}\n" + "" + "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n" + "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};\n" + "" + "function weekdayRange() {\n" + " function getDay(weekday) {\n" + " if (weekday in wdays) {\n" + " return wdays[weekday];\n" + " }\n" + " return -1;\n" + " }\n" + " var date = new Date();\n" + " var argc = arguments.length;\n" + " var wday;\n" + " if (argc < 1)\n" + " return false;\n" + " if (arguments[argc - 1] == 'GMT') {\n" + " argc--;\n" + " wday = date.getUTCDay();\n" + " } else {\n" + " wday = date.getDay();\n" + " }\n" + " var wd1 = getDay(arguments[0]);\n" + " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" + " return (wd1 == -1 || wd2 == -1) ? false\n" + " : (wd1 <= wd2) ? (wd1 <= wday && wday <= wd2)\n" + " : (wd2 >= wday || wday >= wd1);\n" + "}\n" + "" + "function dateRange() {\n" + " function getMonth(name) {\n" + " if (name in months) {\n" + " return months[name];\n" + " }\n" + " return -1;\n" + " }\n" + " var date = new Date();\n" + " var argc = arguments.length;\n" + " if (argc < 1) {\n" + " return false;\n" + " }\n" + " var isGMT = (arguments[argc - 1] == 'GMT');\n" + "\n" + " if (isGMT) {\n" + " argc--;\n" + " }\n" + " // function will work even without explict handling of this case\n" + " if (argc == 1) {\n" + " var tmp = parseInt(arguments[0]);\n" + " if (isNaN(tmp)) {\n" + " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n" + " getMonth(arguments[0]));\n" + " } else if (tmp < 32) {\n" + " return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n" + " } else { \n" + " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==\n" + " tmp);\n" + " }\n" + " }\n" + " var year = date.getFullYear();\n" + " var date1, date2;\n" + " date1 = new Date(year, 0, 1, 0, 0, 0);\n" + " date2 = new Date(year, 11, 31, 23, 59, 59);\n" + " var adjustMonth = false;\n" + " for (var i = 0; i < (argc >> 1); i++) {\n" + " var tmp = parseInt(arguments[i]);\n" + " if (isNaN(tmp)) {\n" + " var mon = getMonth(arguments[i]);\n" + " date1.setMonth(mon);\n" + " } else if (tmp < 32) {\n" + " adjustMonth = (argc <= 2);\n" + " date1.setDate(tmp);\n" + " } else {\n" + " date1.setFullYear(tmp);\n" + " }\n" + " }\n" + " for (var i = (argc >> 1); i < argc; i++) {\n" + " var tmp = parseInt(arguments[i]);\n" + " if (isNaN(tmp)) {\n" + " var mon = getMonth(arguments[i]);\n" + " date2.setMonth(mon);\n" + " } else if (tmp < 32) {\n" + " date2.setDate(tmp);\n" + " } else {\n" + " date2.setFullYear(tmp);\n" + " }\n" + " }\n" + " if (adjustMonth) {\n" + " date1.setMonth(date.getMonth());\n" + " date2.setMonth(date.getMonth());\n" + " }\n" + " if (isGMT) {\n" + " var tmp = date;\n" + " tmp.setFullYear(date.getUTCFullYear());\n" + " tmp.setMonth(date.getUTCMonth());\n" + " tmp.setDate(date.getUTCDate());\n" + " tmp.setHours(date.getUTCHours());\n" + " tmp.setMinutes(date.getUTCMinutes());\n" + " tmp.setSeconds(date.getUTCSeconds());\n" + " date = tmp;\n" + " }\n" + " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" + " : (date2 >= date) || (date >= date1);\n" + "}\n" + "" + "function timeRange() {\n" + " var argc = arguments.length;\n" + " var date = new Date();\n" + " var isGMT= false;\n" + "" + " if (argc < 1) {\n" + " return false;\n" + " }\n" + " if (arguments[argc - 1] == 'GMT') {\n" + " isGMT = true;\n" + " argc--;\n" + " }\n" + "\n" + " var hour = isGMT ? date.getUTCHours() : date.getHours();\n" + " var date1, date2;\n" + " date1 = new Date();\n" + " date2 = new Date();\n" + "\n" + " if (argc == 1) {\n" + " return (hour == arguments[0]);\n" + " } else if (argc == 2) {\n" + " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n" + " } else {\n" + " switch (argc) {\n" + " case 6:\n" + " date1.setSeconds(arguments[2]);\n" + " date2.setSeconds(arguments[5]);\n" + " case 4:\n" + " var middle = argc >> 1;\n" + " date1.setHours(arguments[0]);\n" + " date1.setMinutes(arguments[1]);\n" + " date2.setHours(arguments[middle]);\n" + " date2.setMinutes(arguments[middle + 1]);\n" + " if (middle == 2) {\n" + " date2.setSeconds(59);\n" + " }\n" + " break;\n" + " default:\n" + " throw 'timeRange: bad number of arguments'\n" + " }\n" + " }\n" + "\n" + " if (isGMT) {\n" + " date.setFullYear(date.getUTCFullYear());\n" + " date.setMonth(date.getUTCMonth());\n" + " date.setDate(date.getUTCDate());\n" + " date.setHours(date.getUTCHours());\n" + " date.setMinutes(date.getUTCMinutes());\n" + " date.setSeconds(date.getUTCSeconds());\n" + " }\n" + " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" + " : (date2 >= date) || (date >= date1);\n" + "\n" + "}\n" + ""; + +// sRunning is defined for the helper functions only while the +// Javascript engine is running and the PAC object cannot be deleted +// or reset. +static uint32_t sRunningIndex = 0xdeadbeef; +static ProxyAutoConfig *GetRunning() +{ + MOZ_ASSERT(sRunningIndex != 0xdeadbeef); + return static_cast<ProxyAutoConfig *>(PR_GetThreadPrivate(sRunningIndex)); +} + +static void SetRunning(ProxyAutoConfig *arg) +{ + MOZ_ASSERT(sRunningIndex != 0xdeadbeef); + PR_SetThreadPrivate(sRunningIndex, arg); +} + +// The PACResolver is used for dnsResolve() +class PACResolver final : public nsIDNSListener + , public nsITimerCallback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + PACResolver() + : mStatus(NS_ERROR_FAILURE) + { + } + + // nsIDNSListener + NS_IMETHOD OnLookupComplete(nsICancelable *request, + nsIDNSRecord *record, + nsresult status) override + { + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + mRequest = nullptr; + mStatus = status; + mResponse = record; + return NS_OK; + } + + // nsITimerCallback + NS_IMETHOD Notify(nsITimer *timer) override + { + if (mRequest) + mRequest->Cancel(NS_ERROR_NET_TIMEOUT); + mTimer = nullptr; + return NS_OK; + } + + nsresult mStatus; + nsCOMPtr<nsICancelable> mRequest; + nsCOMPtr<nsIDNSRecord> mResponse; + nsCOMPtr<nsITimer> mTimer; + +private: + ~PACResolver() {} +}; +NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback) + +static +void PACLogToConsole(nsString &aMessage) +{ + 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(NS_LITERAL_STRING("PAC Execution ")); + formattedMessage += aKind; + formattedMessage += NS_LITERAL_STRING(": "); + if (aReport->message()) + formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str())); + formattedMessage += NS_LITERAL_STRING(" ["); + formattedMessage.Append(aReport->linebuf(), aReport->linebufLength()); + formattedMessage += NS_LITERAL_STRING("]"); + PACLogToConsole(formattedMessage); +} + +static void +PACWarningReporter(JSContext* aCx, JSErrorReport* aReport) +{ + MOZ_ASSERT(aReport); + MOZ_ASSERT(JSREPORT_IS_WARNING(aReport->flags)); + + PACLogErrorOrWarning(NS_LITERAL_STRING("Warning"), aReport); +} + +class MOZ_STACK_CLASS AutoPACErrorReporter +{ + JSContext* mCx; + +public: + explicit AutoPACErrorReporter(JSContext* aCx) + : mCx(aCx) + {} + ~AutoPACErrorReporter() { + if (!JS_IsExceptionPending(mCx)) { + return; + } + JS::RootedValue exn(mCx); + if (!JS_GetPendingException(mCx, &exn)) { + return; + } + JS_ClearPendingException(mCx); + + js::ErrorReport report(mCx); + if (!report.init(mCx, exn, js::ErrorReport::WithSideEffects)) { + JS_ClearPendingException(mCx); + return; + } + + PACLogErrorOrWarning(NS_LITERAL_STRING("Error"), 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 nsCString &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() + : mJSContext(nullptr) + , mJSNeedsSetup(false) + , mShutdown(false) + , mIncludePath(false) +{ + MOZ_COUNT_CTOR(ProxyAutoConfig); +} + +bool +ProxyAutoConfig::ResolveAddress(const nsCString &aHostName, + NetAddr *aNetAddr, + unsigned int aTimeout) +{ + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + if (!dns) + return false; + + RefPtr<PACResolver> helper = new PACResolver(); + + if (NS_FAILED(dns->AsyncResolve(aHostName, + nsIDNSService::RESOLVE_PRIORITY_MEDIUM, + helper, + NS_GetCurrentThread(), + getter_AddRefs(helper->mRequest)))) + return false; + + if (aTimeout && helper->mRequest) { + if (!mTimer) + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (mTimer) { + 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. + while (helper->mRequest) + NS_ProcessNextEvent(NS_GetCurrentThread()); + + if (NS_FAILED(helper->mStatus) || + NS_FAILED(helper->mResponse->GetNextAddr(0, aNetAddr))) + return false; + return true; +} + +static +bool PACResolveToString(const nsCString &aHostName, + nsCString &aDottedDecimal, + unsigned int aTimeout) +{ + NetAddr netAddr; + if (!PACResolve(aHostName, &netAddr, aTimeout)) + return false; + + char dottedDecimal[128]; + if (!NetAddrToString(&netAddr, 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; + + JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0])); + if (!arg1) + return false; + + 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.SetCapacity(32 + message.Length()); + alertMessage += NS_LITERAL_STRING("PAC-alert: "); + alertMessage += message; + PACLogToConsole(alertMessage); + + args.rval().setUndefined(); /* return undefined */ + return true; +} + +static const JSFunctionSpec PACGlobalFunctions[] = { + JS_FS("dnsResolve", PACDnsResolve, 1, 0), + + // a global "var pacUseMultihomedDNS = true;" will change behavior + // of myIpAddress to actively use DNS + JS_FS("myIpAddress", PACMyIpAddress, 0, 0), + JS_FS("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() + { + JSContext* cx = JS_NewContext(sContextHeapSize); + if (NS_WARN_IF(!cx)) + return nullptr; + + 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: + static const unsigned sContextHeapSize = 4 << 20; // 4 MB + + 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); + + if (!JS::InitSelfHostedCode(mContext)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + JSAutoRequest ar(mContext); + + JS::CompartmentOptions options; + options.creationOptions().setZone(JS::SystemZone); + options.behaviors().setVersion(JSVERSION_LATEST); + 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); + + JSAutoCompartment ac(mContext, global); + AutoPACErrorReporter aper(mContext); + if (!JS_InitStandardClasses(mContext, global)) { + return NS_ERROR_FAILURE; + } + if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) { + return NS_ERROR_FAILURE; + } + + JS_FireOnNewGlobalObject(mContext, global); + + return NS_OK; + } +}; + +static const JSClassOps sJSContextWrapperGlobalClassOps = { + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, + JS_GlobalObjectTraceHook +}; + +const JSClass JSContextWrapper::sGlobalClass = { + "PACResolutionThreadGlobal", + JSCLASS_GLOBAL_FLAGS, + &sJSContextWrapperGlobalClassOps +}; + +void +ProxyAutoConfig::SetThreadLocalIndex(uint32_t index) +{ + sRunningIndex = index; +} + +nsresult +ProxyAutoConfig::Init(const nsCString &aPACURI, + const nsCString &aPACScript, + bool aIncludePath) +{ + mPACURI = aPACURI; + mPACScript = sPacUtils; + mPACScript.Append(aPACScript); + mIncludePath = aIncludePath; + + if (!GetRunning()) + return SetupJS(); + + mJSNeedsSetup = true; + return NS_OK; +} + +nsresult +ProxyAutoConfig::SetupJS() +{ + mJSNeedsSetup = false; + MOZ_ASSERT(!GetRunning(), "JIT is running"); + + delete mJSContext; + mJSContext = nullptr; + + if (mPACScript.IsEmpty()) + return NS_ERROR_FAILURE; + + NS_GetCurrentThread()->SetCanInvokeJS(true); + + mJSContext = JSContextWrapper::Create(); + if (!mJSContext) + return NS_ERROR_FAILURE; + + JSContext* cx = mJSContext->Context(); + JSAutoRequest ar(cx); + JSAutoCompartment ac(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()); + JS::CompileOptions options(cx); + options.setFileAndLine(mPACURI.get(), 1); + JS::Rooted<JSScript*> script(cx); + if (!JS_CompileScript(cx, mPACScript.get(), mPACScript.Length(), options, + &script) || + !JS_ExecuteScript(cx, script)) + { + nsString alertMessage(NS_LITERAL_STRING("PAC file failed to install from ")); + if (isDataURI) { + alertMessage += NS_LITERAL_STRING("data: URI"); + } + else { + alertMessage += NS_ConvertUTF8toUTF16(mPACURI); + } + PACLogToConsole(alertMessage); + SetRunning(nullptr); + return NS_ERROR_FAILURE; + } + SetRunning(nullptr); + + mJSContext->SetOK(); + nsString alertMessage(NS_LITERAL_STRING("PAC file installed from ")); + if (isDataURI) { + alertMessage += NS_LITERAL_STRING("data: URI"); + } + else { + alertMessage += NS_ConvertUTF8toUTF16(mPACURI); + } + PACLogToConsole(alertMessage); + + // we don't need these now + mPACScript.Truncate(); + mPACURI.Truncate(); + + return NS_OK; +} + +nsresult +ProxyAutoConfig::GetProxyForURI(const nsCString &aTestURI, + const nsCString &aTestHost, + nsACString &result) +{ + if (mJSNeedsSetup) + SetupJS(); + + if (!mJSContext || !mJSContext->IsOK()) + return NS_ERROR_NOT_AVAILABLE; + + JSContext *cx = mJSContext->Context(); + JSAutoRequest ar(cx); + JSAutoCompartment ac(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.get(), 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::RootedString uriString(cx, JS_NewStringCopyZ(cx, clensedURI.get())); + JS::RootedString hostString(cx, JS_NewStringCopyZ(cx, aTestHost.get())); + + if (uriString && hostString) { + JS::AutoValueArray<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; + + JSAutoCompartment ac(mJSContext->Context(), mJSContext->Global()); + JS_MaybeGC(mJSContext->Context()); +} + +ProxyAutoConfig::~ProxyAutoConfig() +{ + MOZ_COUNT_DTOR(ProxyAutoConfig); + 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 (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 nsCString &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::RootedValue 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 + PRNetAddr tempAddr; + memset(&tempAddr, 0, sizeof(PRNetAddr)); + if ((PR_StringToNetAddr(mRunningHost.get(), &tempAddr) == PR_SUCCESS) && + (!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; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/ProxyAutoConfig.h b/netwerk/base/ProxyAutoConfig.h new file mode 100644 index 000000000..1f7b88ac6 --- /dev/null +++ b/netwerk/base/ProxyAutoConfig.h @@ -0,0 +1,104 @@ +/* -*- 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 "nsString.h" +#include "nsCOMPtr.h" + +class nsITimer; +namespace JS { +class CallArgs; +} // namespace JS + +namespace mozilla { namespace net { + +class JSContextWrapper; +union NetAddr; + +// 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: + ProxyAutoConfig(); + ~ProxyAutoConfig(); + + nsresult Init(const nsCString &aPACURI, + const nsCString &aPACScript, + bool aIncludePath); + void SetThreadLocalIndex(uint32_t index); + void Shutdown(); + void GC(); + bool MyIPAddress(const JS::CallArgs &aArgs); + bool ResolveAddress(const nsCString &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 nsCString &aTestURI, + const nsCString &aTestHost, + nsACString &result); + +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 nsCString &hostName, unsigned int timeout, + const JS::CallArgs &aArgs, bool* aResult); + + JSContextWrapper *mJSContext; + bool mJSNeedsSetup; + bool mShutdown; + nsCString mPACScript; + nsCString mPACURI; + bool mIncludePath; + nsCString mRunningHost; + nsCOMPtr<nsITimer> mTimer; +}; + +} // namespace net +} // namespace mozilla + +#endif // ProxyAutoConfig_h__ diff --git a/netwerk/base/RedirectChannelRegistrar.cpp b/netwerk/base/RedirectChannelRegistrar.cpp new file mode 100644 index 000000000..17e26603a --- /dev/null +++ b/netwerk/base/RedirectChannelRegistrar.cpp @@ -0,0 +1,87 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar) + +RedirectChannelRegistrar::RedirectChannelRegistrar() + : mRealChannels(32) + , mParentChannels(32) + , mId(1) + , mLock("RedirectChannelRegistrar") +{ +} + +NS_IMETHODIMP +RedirectChannelRegistrar::RegisterChannel(nsIChannel *channel, + uint32_t *_retval) +{ + MutexAutoLock lock(mLock); + + mRealChannels.Put(mId, channel); + *_retval = mId; + + ++mId; + + // Ensure we always provide positive ids + if (!mId) + mId = 1; + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::GetRegisteredChannel(uint32_t id, + nsIChannel **_retval) +{ + MutexAutoLock lock(mLock); + + if (!mRealChannels.Get(id, _retval)) + return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::LinkChannels(uint32_t id, + nsIParentChannel *channel, + nsIChannel** _retval) +{ + MutexAutoLock lock(mLock); + + if (!mRealChannels.Get(id, _retval)) + return NS_ERROR_NOT_AVAILABLE; + + mParentChannels.Put(id, channel); + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::GetParentChannel(uint32_t id, + nsIParentChannel **_retval) +{ + MutexAutoLock lock(mLock); + + if (!mParentChannels.Get(id, _retval)) + return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +NS_IMETHODIMP +RedirectChannelRegistrar::DeregisterChannels(uint32_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 000000000..46e46c264 --- /dev/null +++ b/netwerk/base/RedirectChannelRegistrar.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 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() {} + +protected: + typedef nsInterfaceHashtable<nsUint32HashKey, nsIChannel> + ChannelHashtable; + typedef nsInterfaceHashtable<nsUint32HashKey, nsIParentChannel> + ParentChannelHashtable; + + ChannelHashtable mRealChannels; + ParentChannelHashtable mParentChannels; + uint32_t mId; + Mutex mLock; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/ReferrerPolicy.h b/netwerk/base/ReferrerPolicy.h new file mode 100644 index 000000000..591b9daf0 --- /dev/null +++ b/netwerk/base/ReferrerPolicy.h @@ -0,0 +1,186 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 ReferrerPolicy_h__ +#define ReferrerPolicy_h__ + +#include "nsStringGlue.h" +#include "nsIHttpChannel.h" +#include "nsUnicharUtils.h" + +namespace mozilla { namespace net { + +enum ReferrerPolicy { + /* spec tokens: never no-referrer */ + RP_No_Referrer = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER, + + /* spec tokens: origin */ + RP_Origin = nsIHttpChannel::REFERRER_POLICY_ORIGIN, + + /* spec tokens: default no-referrer-when-downgrade */ + RP_No_Referrer_When_Downgrade = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE, + RP_Default = nsIHttpChannel::REFERRER_POLICY_DEFAULT, + + /* spec tokens: origin-when-cross-origin */ + RP_Origin_When_Crossorigin = nsIHttpChannel::REFERRER_POLICY_ORIGIN_WHEN_XORIGIN, + + /* spec tokens: always unsafe-url */ + RP_Unsafe_URL = nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL, + + /* spec tokens: same-origin */ + RP_Same_Origin = nsIHttpChannel::REFERRER_POLICY_SAME_ORIGIN, + + /* spec tokens: strict-origin */ + RP_Strict_Origin = nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN, + + /* spec tokens: strict-origin-when-cross-origin */ + RP_Strict_Origin_When_Cross_Origin = nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN, + + /* spec tokens: empty string */ + /* The empty string "" corresponds to no referrer policy, or unset policy */ + RP_Unset = nsIHttpChannel::REFERRER_POLICY_UNSET, +}; + +/* spec tokens: never no-referrer */ +const char kRPS_Never[] = "never"; +const char kRPS_No_Referrer[] = "no-referrer"; + +/* spec tokens: origin */ +const char kRPS_Origin[] = "origin"; + +/* spec tokens: default no-referrer-when-downgrade */ +const char kRPS_Default[] = "default"; +const char kRPS_No_Referrer_When_Downgrade[] = "no-referrer-when-downgrade"; + +/* spec tokens: origin-when-cross-origin */ +const char kRPS_Origin_When_Cross_Origin[] = "origin-when-cross-origin"; +const char kRPS_Origin_When_Crossorigin[] = "origin-when-crossorigin"; + +/* spec tokens: same-origin */ +const char kRPS_Same_Origin[] = "same-origin"; + +/* spec tokens: strict-origin */ +const char kRPS_Strict_Origin[] = "strict-origin"; + +/* spec tokens: strict-origin-when-cross-origin */ +const char kRPS_Strict_Origin_When_Cross_Origin[] = "strict-origin-when-cross-origin"; + +/* spec tokens: always unsafe-url */ +const char kRPS_Always[] = "always"; +const char kRPS_Unsafe_URL[] = "unsafe-url"; + +inline ReferrerPolicy +ReferrerPolicyFromString(const nsAString& content) +{ + if (content.IsEmpty()) { + return RP_No_Referrer; + } + + nsString lowerContent(content); + ToLowerCase(lowerContent); + // This is implemented step by step as described in the Referrer Policy + // specification, section "Determine token's Policy". + if (lowerContent.EqualsLiteral(kRPS_Never) || + lowerContent.EqualsLiteral(kRPS_No_Referrer)) { + return RP_No_Referrer; + } + if (lowerContent.EqualsLiteral(kRPS_Origin)) { + return RP_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Default) || + lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade)) { + return RP_No_Referrer_When_Downgrade; + } + if (lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin) || + lowerContent.EqualsLiteral(kRPS_Origin_When_Crossorigin)) { + return RP_Origin_When_Crossorigin; + } + if (lowerContent.EqualsLiteral(kRPS_Same_Origin)) { + return RP_Same_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Strict_Origin)) { + return RP_Strict_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin)) { + return RP_Strict_Origin_When_Cross_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Always) || + lowerContent.EqualsLiteral(kRPS_Unsafe_URL)) { + return RP_Unsafe_URL; + } + // Spec says if none of the previous match, use empty string. + return RP_Unset; + +} + +inline bool +IsValidReferrerPolicy(const nsAString& content) +{ + if (content.IsEmpty()) { + return true; + } + + nsString lowerContent(content); + ToLowerCase(lowerContent); + + return lowerContent.EqualsLiteral(kRPS_Never) + || lowerContent.EqualsLiteral(kRPS_No_Referrer) + || lowerContent.EqualsLiteral(kRPS_Origin) + || lowerContent.EqualsLiteral(kRPS_Default) + || lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade) + || lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin) + || lowerContent.EqualsLiteral(kRPS_Origin_When_Crossorigin) + || lowerContent.EqualsLiteral(kRPS_Same_Origin) + || lowerContent.EqualsLiteral(kRPS_Strict_Origin) + || lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin) + || lowerContent.EqualsLiteral(kRPS_Always) + || lowerContent.EqualsLiteral(kRPS_Unsafe_URL); +} + +inline ReferrerPolicy +AttributeReferrerPolicyFromString(const nsAString& content) +{ + // Specs : https://html.spec.whatwg.org/multipage/infrastructure.html#referrer-policy-attribute + // Spec says the empty string "" corresponds to no referrer policy, or RP_Unset + if (content.IsEmpty()) { + return RP_Unset; + } + + nsString lowerContent(content); + ToLowerCase(lowerContent); + + if (lowerContent.EqualsLiteral(kRPS_No_Referrer)) { + return RP_No_Referrer; + } + if (lowerContent.EqualsLiteral(kRPS_Origin)) { + return RP_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_No_Referrer_When_Downgrade)) { + return RP_No_Referrer_When_Downgrade; + } + if (lowerContent.EqualsLiteral(kRPS_Origin_When_Cross_Origin)) { + return RP_Origin_When_Crossorigin; + } + if (lowerContent.EqualsLiteral(kRPS_Unsafe_URL)) { + return RP_Unsafe_URL; + } + if (lowerContent.EqualsLiteral(kRPS_Strict_Origin)) { + return RP_Strict_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Same_Origin)) { + return RP_Same_Origin; + } + if (lowerContent.EqualsLiteral(kRPS_Strict_Origin_When_Cross_Origin)) { + return RP_Strict_Origin_When_Cross_Origin; + } + + // Spec says invalid value default is empty string state + // So, return RP_Unset if none of the previous match, return RP_Unset + return RP_Unset; +} + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/RequestContextService.cpp b/netwerk/base/RequestContextService.cpp new file mode 100644 index 000000000..b72e42b13 --- /dev/null +++ b/netwerk/base/RequestContextService.cpp @@ -0,0 +1,217 @@ +/* -*- 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 "nsAutoPtr.h" +#include "nsIObserverService.h" +#include "nsIUUIDGenerator.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "RequestContextService.h" + +#include "mozilla/Atomics.h" +#include "mozilla/Services.h" + +#include "mozilla/net/PSpdyPush.h" + +namespace mozilla { +namespace net { + +// nsIRequestContext +class RequestContext final : public nsIRequestContext +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTCONTEXT + + explicit RequestContext(const nsID& id); +private: + virtual ~RequestContext(); + + nsID mID; + char mCID[NSID_LENGTH]; + Atomic<uint32_t> mBlockingTransactionCount; + nsAutoPtr<SpdyPushCache> mSpdyCache; + nsCString mUserAgentOverride; +}; + +NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext) + +RequestContext::RequestContext(const nsID& aID) + : mBlockingTransactionCount(0) +{ + mID = aID; + mID.ToProvidedString(mCID); +} + +RequestContext::~RequestContext() +{ +} + +NS_IMETHODIMP +RequestContext::GetBlockingTransactionCount(uint32_t *aBlockingTransactionCount) +{ + NS_ENSURE_ARG_POINTER(aBlockingTransactionCount); + *aBlockingTransactionCount = mBlockingTransactionCount; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::AddBlockingTransaction() +{ + mBlockingTransactionCount++; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::RemoveBlockingTransaction(uint32_t *outval) +{ + NS_ENSURE_ARG_POINTER(outval); + mBlockingTransactionCount--; + *outval = mBlockingTransactionCount; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::GetSpdyPushCache(mozilla::net::SpdyPushCache **aSpdyPushCache) +{ + *aSpdyPushCache = mSpdyCache.get(); + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::SetSpdyPushCache(mozilla::net::SpdyPushCache *aSpdyPushCache) +{ + mSpdyCache = aSpdyPushCache; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::GetID(nsID *outval) +{ + NS_ENSURE_ARG_POINTER(outval); + *outval = mID; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::GetUserAgentOverride(nsACString& aUserAgentOverride) +{ + aUserAgentOverride = mUserAgentOverride; + return NS_OK; +} + +NS_IMETHODIMP +RequestContext::SetUserAgentOverride(const nsACString& aUserAgentOverride) +{ + mUserAgentOverride = aUserAgentOverride; + 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; +} + +RequestContextService::~RequestContextService() +{ + MOZ_ASSERT(NS_IsMainThread()); + Shutdown(); + sSelf = nullptr; +} + +nsresult +RequestContextService::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_NOT_AVAILABLE; + } + + return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); +} + +void +RequestContextService::Shutdown() +{ + MOZ_ASSERT(NS_IsMainThread()); + mTable.Clear(); +} + +/* static */ nsresult +RequestContextService::Create(nsISupports *aOuter, const nsIID& aIID, void **aResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + + RefPtr<RequestContextService> svc = new RequestContextService(); + nsresult rv = svc->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + return svc->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +RequestContextService::GetRequestContext(const nsID& rcID, nsIRequestContext **rc) +{ + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(rc); + *rc = nullptr; + + if (!mTable.Get(rcID, rc)) { + nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID); + mTable.Put(rcID, newSC); + newSC.swap(*rc); + } + + return NS_OK; +} + +NS_IMETHODIMP +RequestContextService::NewRequestContextID(nsID *rcID) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!mUUIDGen) { + nsresult rv; + mUUIDGen = do_GetService("@mozilla.org/uuid-generator;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mUUIDGen->GenerateUUIDInPlace(rcID); +} + +NS_IMETHODIMP +RequestContextService::RemoveRequestContext(const nsID& 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; +} + +} // ::mozilla::net +} // ::mozilla diff --git a/netwerk/base/RequestContextService.h b/netwerk/base/RequestContextService.h new file mode 100644 index 000000000..d7b8e971a --- /dev/null +++ b/netwerk/base/RequestContextService.h @@ -0,0 +1,47 @@ +/* -*- 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" + +class nsIUUIDGenerator; + +namespace mozilla { +namespace net { + +class RequestContextService final + : public nsIRequestContextService + , public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTCONTEXTSERVICE + NS_DECL_NSIOBSERVER + + RequestContextService(); + + nsresult Init(); + void Shutdown(); + static nsresult Create(nsISupports *outer, const nsIID& iid, void **result); + +private: + virtual ~RequestContextService(); + + static RequestContextService *sSelf; + + nsInterfaceHashtable<nsIDHashKey, nsIRequestContext> mTable; + nsCOMPtr<nsIUUIDGenerator> mUUIDGen; +}; + +} // ::mozilla::net +} // ::mozilla + +#endif // mozilla__net__RequestContextService_h diff --git a/netwerk/base/ShutdownLayer.cpp b/netwerk/base/ShutdownLayer.cpp new file mode 100644 index 000000000..65d792459 --- /dev/null +++ b/netwerk/base/ShutdownLayer.cpp @@ -0,0 +1,80 @@ +/* -*- 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_DELETE(layer); + return NS_ERROR_FAILURE; + } + return NS_OK; +} diff --git a/netwerk/base/ShutdownLayer.h b/netwerk/base/ShutdownLayer.h new file mode 100644 index 000000000..59843b7d0 --- /dev/null +++ b/netwerk/base/ShutdownLayer.h @@ -0,0 +1,22 @@ +/* -*- 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 000000000..d0e311f77 --- /dev/null +++ b/netwerk/base/SimpleBuffer.cpp @@ -0,0 +1,95 @@ +/* -*- 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 { + +SimpleBuffer::SimpleBuffer() + : mStatus(NS_OK) + , mAvailable(0) +{ + mOwningThread = PR_GetCurrentThread(); +} + +nsresult SimpleBuffer::Write(char *src, size_t len) +{ + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); + 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) +{ + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); + 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() +{ + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); + return NS_SUCCEEDED(mStatus) ? mAvailable : 0; +} + +void SimpleBuffer::Clear() +{ + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); + 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 000000000..765239001 --- /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 "mozilla/LinkedList.h" +#include "nsIThreadInternal.h" + +namespace mozilla { +namespace net { + +class SimpleBufferPage : public LinkedListElement<SimpleBufferPage> +{ +public: + SimpleBufferPage() : mReadOffset(0), mWriteOffset(0) {} + static const size_t kSimpleBufferPageSize = 32000; + +private: + friend class SimpleBuffer; + char mBuffer[kSimpleBufferPageSize]; + size_t mReadOffset; + size_t mWriteOffset; +}; + +class SimpleBuffer +{ +public: + SimpleBuffer(); + ~SimpleBuffer() {} + + nsresult Write(char *stc, 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: + PRThread *mOwningThread; + nsresult mStatus; + AutoCleanLinkedList<SimpleBufferPage> mBufferList; + size_t mAvailable; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/StreamingProtocolService.cpp b/netwerk/base/StreamingProtocolService.cpp new file mode 100644 index 000000000..3df3326bc --- /dev/null +++ b/netwerk/base/StreamingProtocolService.cpp @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/ClearOnShutdown.h" +#include "StreamingProtocolService.h" +#include "mozilla/net/NeckoChild.h" +#include "nsIURI.h" +#include "necko-config.h" + +#ifdef NECKO_PROTOCOL_rtsp +#include "RtspControllerChild.h" +#include "RtspController.h" +#endif + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(StreamingProtocolControllerService, + nsIStreamingProtocolControllerService) + +/* static */ +StaticRefPtr<StreamingProtocolControllerService> sSingleton; + +/* static */ +already_AddRefed<StreamingProtocolControllerService> +StreamingProtocolControllerService::GetInstance() +{ + if (!sSingleton) { + sSingleton = new StreamingProtocolControllerService(); + ClearOnShutdown(&sSingleton); + } + RefPtr<StreamingProtocolControllerService> service = sSingleton.get(); + return service.forget(); +} + +NS_IMETHODIMP +StreamingProtocolControllerService::Create(nsIChannel *aChannel, nsIStreamingProtocolController **aResult) +{ + RefPtr<nsIStreamingProtocolController> mediacontroller; + nsCOMPtr<nsIURI> uri; + nsAutoCString scheme; + + NS_ENSURE_ARG_POINTER(aChannel); + aChannel->GetURI(getter_AddRefs(uri)); + + nsresult rv = uri->GetScheme(scheme); + if (NS_FAILED(rv)) return rv; + +#ifdef NECKO_PROTOCOL_rtsp + if (scheme.EqualsLiteral("rtsp")) { + if (IsNeckoChild()) { + mediacontroller = new RtspControllerChild(aChannel); + } else { + mediacontroller = new RtspController(aChannel); + } + } +#endif + + if (!mediacontroller) { + return NS_ERROR_NO_INTERFACE; + } + + mediacontroller->Init(uri); + + mediacontroller.forget(aResult); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/StreamingProtocolService.h b/netwerk/base/StreamingProtocolService.h new file mode 100644 index 000000000..96d51fe5c --- /dev/null +++ b/netwerk/base/StreamingProtocolService.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 mozilla_net_StreamingProtocolControllerService_h +#define mozilla_net_StreamingProtocolControllerService_h + +#include "mozilla/StaticPtr.h" +#include "nsIStreamingProtocolService.h" +#include "nsIStreamingProtocolController.h" +#include "nsCOMPtr.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +/** + * This class implements a service to help to create streaming protocol controller. + */ +class StreamingProtocolControllerService : public nsIStreamingProtocolControllerService +{ +private: + virtual ~StreamingProtocolControllerService() {}; + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMINGPROTOCOLCONTROLLERSERVICE + + StreamingProtocolControllerService() {}; + static already_AddRefed<StreamingProtocolControllerService> GetInstance(); +}; + +} // namespace net +} // namespace mozilla + +#endif //mozilla_net_StreamingProtocolControllerService_h diff --git a/netwerk/base/TLSServerSocket.cpp b/netwerk/base/TLSServerSocket.cpp new file mode 100644 index 000000000..b32a9a188 --- /dev/null +++ b/netwerk/base/TLSServerSocket.cpp @@ -0,0 +1,515 @@ +/* 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 "nsAutoPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIServerSocket.h" +#include "nsITimer.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 +//----------------------------------------------------------------------------- + +TLSServerSocket::TLSServerSocket() + : mServerCert(nullptr) +{ +} + +TLSServerSocket::~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); + + // 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); + + SetSessionCache(true); + SetSessionTickets(true); + SetRequestClientCertificate(REQUEST_NEVER); + + return NS_OK; +} + +void +TLSServerSocket::CreateClientTransport(PRFileDesc* aClientFD, + const NetAddr& aClientAddr) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + 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<nsISupports> infoSupports = + NS_ISUPPORTS_CAST(nsITLSServerConnectionInfo*, info); + rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr, infoSupports); + 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 = mServerCert; + NS_IF_ADDREF(*aCert); + 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::SetSessionCache(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_NO_CACHE, !aEnabled); + 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::SetCipherSuites(uint16_t* aCipherSuites, uint32_t aLength) +{ + // 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; + } + + for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i) { + uint16_t cipher_id = SSL_ImplementedCiphers[i]; + if (SSL_CipherPrefSet(mFD, cipher_id, false) != SECSuccess) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + } + + for (uint32_t i = 0; i < aLength; ++i) { + if (SSL_CipherPrefSet(mFD, aCipherSuites[i], true) != SECSuccess) { + return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); + } + } + + 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() {} + +public: + explicit TLSServerSecurityObserverProxy(nsITLSServerSecurityObserver* aListener) + : mListener(new nsMainThreadPtrHolder<nsITLSServerSecurityObserver>(aListener)) + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITLSSERVERSECURITYOBSERVER + + class OnHandshakeDoneRunnable : public Runnable + { + public: + OnHandshakeDoneRunnable(const nsMainThreadPtrHandle<nsITLSServerSecurityObserver>& aListener, + nsITLSServerSocket* aServer, + nsITLSClientStatus* aStatus) + : 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) + +TLSServerConnectionInfo::TLSServerConnectionInfo() + : mServerSocket(nullptr) + , mTransport(nullptr) + , mPeerCert(nullptr) + , mTlsVersionUsed(TLS_VERSION_UNKNOWN) + , mKeyLength(0) + , mMacLength(0) + , mLock("TLSServerConnectionInfo.mLock") + , mSecurityObserver(nullptr) +{ +} + +TLSServerConnectionInfo::~TLSServerConnectionInfo() +{ + if (!mSecurityObserver) { + return; + } + + RefPtr<nsITLSServerSecurityObserver> observer; + { + MutexAutoLock lock(mLock); + observer = mSecurityObserver.forget(); + } + + if (observer) { + NS_ReleaseOnMainThread(observer.forget()); + } +} + +NS_IMETHODIMP +TLSServerConnectionInfo::SetSecurityObserver(nsITLSServerSecurityObserver* aObserver) +{ + { + MutexAutoLock lock(mLock); + mSecurityObserver = new TLSServerSecurityObserverProxy(aObserver); + } + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetServerSocket(nsITLSServerSocket** aSocket) +{ + if (NS_WARN_IF(!aSocket)) { + return NS_ERROR_INVALID_POINTER; + } + *aSocket = mServerSocket; + NS_IF_ADDREF(*aSocket); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetStatus(nsITLSClientStatus** aStatus) +{ + if (NS_WARN_IF(!aStatus)) { + return NS_ERROR_INVALID_POINTER; + } + *aStatus = this; + NS_IF_ADDREF(*aStatus); + return NS_OK; +} + +NS_IMETHODIMP +TLSServerConnectionInfo::GetPeerCert(nsIX509Cert** aCert) +{ + if (NS_WARN_IF(!aCert)) { + return NS_ERROR_INVALID_POINTER; + } + *aCert = mPeerCert; + NS_IF_ADDREF(*aCert); + 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; +} + +// 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; + rv = certDB->ConstructX509(reinterpret_cast<char*>(clientCert->derCert.data), + clientCert->derCert.len, + 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; + } + mTlsVersionUsed = channelInfo.protocolVersion; + + 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; + + if (!mSecurityObserver) { + return NS_OK; + } + + // Notify consumer code that handshake is complete + nsCOMPtr<nsITLSServerSecurityObserver> observer; + { + MutexAutoLock lock(mLock); + 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 000000000..9fb57e0cc --- /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 "nsAutoPtr.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(); + +private: + virtual ~TLSServerSocket(); + + static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, + PRBool checksig, PRBool isServer); + + nsCOMPtr<nsIX509Cert> mServerCert; +}; + +class TLSServerConnectionInfo : public nsITLSServerConnectionInfo + , public nsITLSClientStatus +{ + friend class TLSServerSocket; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITLSSERVERCONNECTIONINFO + NS_DECL_NSITLSCLIENTSTATUS + + TLSServerConnectionInfo(); + +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; + nsCOMPtr<nsIX509Cert> mPeerCert; + int16_t mTlsVersionUsed; + nsCString mCipherName; + uint32_t mKeyLength; + uint32_t mMacLength; + // lock protects access to mSecurityObserver + mozilla::Mutex mLock; + nsCOMPtr<nsITLSServerSecurityObserver> mSecurityObserver; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_TLSServerSocket_h diff --git a/netwerk/base/ThrottleQueue.cpp b/netwerk/base/ThrottleQueue.cpp new file mode 100644 index 000000000..d5b8a41df --- /dev/null +++ b/netwerk/base/ThrottleQueue.cpp @@ -0,0 +1,392 @@ +/* -*- 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 "nsISeekableStream.h" +#include "nsIAsyncInputStream.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_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, 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::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; + } + + 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<nsISeekableStream> 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(mCallback, mEventTarget); + mCallback = nullptr; + mEventTarget = nullptr; + callbackEvent->OnInputStreamReady(this); +} + +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(ThrottleQueue, nsIInputChannelThrottleQueue, nsITimerCallback) + +ThrottleQueue::ThrottleQueue() + : mMeanBytesPerSecond(0) + , mMaxBytesPerSecond(0) + , mBytesProcessed(0) + , mTimerArmed(false) +{ + 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 = do_CreateInstance("@mozilla.org/timer;1"); + if (mTimer) + mTimer->SetTarget(sts); +} + +ThrottleQueue::~ThrottleQueue() +{ + if (mTimer && mTimerArmed) { + mTimer->Cancel(); + } + mTimer = nullptr; +} + +NS_IMETHODIMP +ThrottleQueue::RecordRead(uint32_t aBytesRead) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + 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(PR_GetCurrentThread() == gSocketThread); + 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 = thisSliceBytes; + } + 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(PR_GetCurrentThread() == gSocketThread); + // 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; + events.SwapElements(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; +} + +void +ThrottleQueue::QueueStream(ThrottleInputStream* aStream) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + if (mAsyncEvents.IndexOf(aStream) == mAsyncEvents.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(PR_GetCurrentThread() == gSocketThread); + mAsyncEvents.RemoveElement(aStream); +} + +} +} diff --git a/netwerk/base/ThrottleQueue.h b/netwerk/base/ThrottleQueue.h new file mode 100644 index 000000000..5e16c8ef6 --- /dev/null +++ b/netwerk/base/ThrottleQueue.h @@ -0,0 +1,65 @@ +/* -*- 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 "nsIThrottledInputChannel.h" +#include "nsITimer.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 final + : public nsIInputChannelThrottleQueue + , public nsITimerCallback +{ +public: + + ThrottleQueue(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE + NS_DECL_NSITIMERCALLBACK + + void QueueStream(ThrottleInputStream* aStream); + void DequeueStream(ThrottleInputStream* aStream); + +private: + + ~ThrottleQueue(); + + struct ThrottleEntry { + TimeStamp mTime; + uint32_t mBytesRead; + }; + + nsTArray<ThrottleEntry> mReadEvents; + uint32_t mMeanBytesPerSecond; + uint32_t mMaxBytesPerSecond; + uint64_t mBytesProcessed; + + nsTArray<RefPtr<ThrottleInputStream>> mAsyncEvents; + nsCOMPtr<nsITimer> mTimer; + bool mTimerArmed; +}; + +} +} + +#endif // mozilla_net_ThrottleQueue_h diff --git a/netwerk/base/Tickler.cpp b/netwerk/base/Tickler.cpp new file mode 100644 index 000000000..555fdbbe5 --- /dev/null +++ b/netwerk/base/Tickler.cpp @@ -0,0 +1,277 @@ +/* -*- 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 "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "prnetdb.h" + +#include "mozilla/jni/Utils.h" +#include "GeneratedJNIWrappers.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() +{ + 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(do_CreateInstance(NS_TIMER_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + rv = tmpTimer->SetTarget(mThread); + if (NS_FAILED(rv)) + return rv; + + 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(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(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 +{ + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + TicklerTimer(Tickler *aTickler) + { + mTickler = do_GetWeakReference(aTickler); + } + +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) + +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 mozilla::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 000000000..573fe6e76 --- /dev/null +++ b/netwerk/base/Tickler.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 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_B2G) +#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 "nsAutoPtr.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; + 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() { } +public: + NS_DECL_THREADSAFE_ISUPPORTS + + Tickler() { } + 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/moz.build b/netwerk/base/moz.build new file mode 100644 index 000000000..3b731db10 --- /dev/null +++ b/netwerk/base/moz.build @@ -0,0 +1,316 @@ +# -*- 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', + 'nsIApplicationCache.idl', + 'nsIApplicationCacheChannel.idl', + 'nsIApplicationCacheContainer.idl', + 'nsIApplicationCacheService.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', + 'nsIChannelWithDivertableParentListener.idl', + 'nsIChildChannel.idl', + 'nsIClassOfService.idl', + 'nsIContentSniffer.idl', + 'nsICryptoFIPSInfo.idl', + 'nsICryptoHash.idl', + 'nsICryptoHMAC.idl', + 'nsIDashboard.idl', + 'nsIDashboardEventNotifier.idl', + 'nsIDeprecationWarner.idl', + 'nsIDivertableChannel.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', + 'nsIIOService.idl', + 'nsIIOService2.idl', + 'nsILoadContextInfo.idl', + 'nsILoadGroup.idl', + 'nsILoadGroupChild.idl', + 'nsILoadInfo.idl', + 'nsIMIMEInputStream.idl', + 'nsIMultiPartChannel.idl', + 'nsINestedURI.idl', + 'nsINetAddr.idl', + 'nsINetUtil.idl', + 'nsINetworkInfoService.idl', + 'nsINetworkInterceptController.idl', + 'nsINetworkLinkService.idl', + 'nsINetworkPredictor.idl', + 'nsINetworkPredictorVerifier.idl', + 'nsINetworkProperties.idl', + 'nsINSSErrorsService.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', + 'nsIRedirectResultListener.idl', + 'nsIRequest.idl', + 'nsIRequestContext.idl', + 'nsIRequestObserver.idl', + 'nsIRequestObserverProxy.idl', + 'nsIResumableChannel.idl', + 'nsISecCheckWrapChannel.idl', + 'nsISecureBrowserUI.idl', + 'nsISecurityEventSink.idl', + 'nsISecurityInfoProvider.idl', + 'nsISensitiveInfoHiddenURI.idl', + 'nsISerializationHelper.idl', + 'nsIServerSocket.idl', + 'nsISimpleStreamListener.idl', + 'nsISocketFilter.idl', + 'nsISocketTransport.idl', + 'nsISocketTransportService.idl', + 'nsISpeculativeConnect.idl', + 'nsIStandardURL.idl', + 'nsIStreamingProtocolController.idl', + 'nsIStreamingProtocolService.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', + 'nsIUnicharStreamLoader.idl', + 'nsIUploadChannel.idl', + 'nsIUploadChannel2.idl', + 'nsIURI.idl', + 'nsIURIClassifier.idl', + 'nsIURIWithBlobImpl.idl', + 'nsIURIWithPrincipal.idl', + 'nsIURIWithQuery.idl', + 'nsIURL.idl', + 'nsIURLParser.idl', + 'nsPILoadGroupInternal.idl', + 'nsPISocketTransportService.idl', +] + +if CONFIG['MOZ_TOOLKIT_SEARCH']: + XPIDL_SOURCES += [ + 'nsIBrowserSearchService.idl', + ] + +XPIDL_MODULE = 'necko' + +EXPORTS += [ + 'netCore.h', + 'nsASocketHandler.h', + 'nsAsyncRedirectVerifyHelper.h', + 'nsFileStreams.h', + 'nsInputStreamPump.h', + 'nsMIMEInputStream.h', + 'nsNetUtil.h', + 'nsNetUtilInlines.h', + 'nsReadLine.h', + 'nsSerializationHelper.h', + 'nsSimpleNestedURI.h', + 'nsSimpleURI.h', + 'nsStreamListenerWrapper.h', + 'nsTemporaryFileInputStream.h', + 'nsURIHashKey.h', + 'nsURLHelper.h', + 'nsURLParsers.h', +] + +EXPORTS.mozilla += [ + 'LoadContextInfo.h', + 'LoadInfo.h', + 'LoadTainting.h', +] + +EXPORTS.mozilla.net += [ + 'CaptivePortalService.h', + 'ChannelDiverterChild.h', + 'ChannelDiverterParent.h', + 'Dashboard.h', + 'DashboardTypes.h', + 'MemoryDownloader.h', + 'Predictor.h', + 'ReferrerPolicy.h', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + EXPORTS += [ + 'NetStatistics.h', + ] + +UNIFIED_SOURCES += [ + 'ArrayBufferInputStream.cpp', + 'BackgroundFileSaver.cpp', + 'CaptivePortalService.cpp', + 'ChannelDiverterChild.cpp', + 'ChannelDiverterParent.cpp', + 'Dashboard.cpp', + 'EventTokenBucket.cpp', + 'LoadContextInfo.cpp', + 'LoadInfo.cpp', + 'MemoryDownloader.cpp', + 'NetworkActivityMonitor.cpp', + 'nsAsyncRedirectVerifyHelper.cpp', + 'nsAsyncStreamCopier.cpp', + 'nsAuthInformationHolder.cpp', + 'nsBase64Encoder.cpp', + 'nsBaseChannel.cpp', + 'nsBaseContentStream.cpp', + 'nsBufferedStreams.cpp', + 'nsChannelClassifier.cpp', + 'nsDirectoryIndexStream.cpp', + 'nsDNSPrefetch.cpp', + 'nsDownloader.cpp', + 'nsFileStreams.cpp', + 'nsIncrementalDownload.cpp', + 'nsIncrementalStreamLoader.cpp', + 'nsInputStreamChannel.cpp', + 'nsInputStreamPump.cpp', + 'nsIOService.cpp', + 'nsLoadGroup.cpp', + 'nsMediaFragmentURIParser.cpp', + 'nsMIMEInputStream.cpp', + 'nsNetAddr.cpp', + 'nsNetUtil.cpp', + 'nsPACMan.cpp', + 'nsPreloadedStream.cpp', + 'nsProtocolProxyService.cpp', + 'nsProxyInfo.cpp', + 'nsRequestObserverProxy.cpp', + 'nsSecCheckWrapChannel.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', + 'nsTemporaryFileInputStream.cpp', + 'nsTransportUtils.cpp', + 'nsUDPSocket.cpp', + 'nsUnicharStreamLoader.cpp', + 'nsURLHelper.cpp', + 'nsURLParsers.cpp', + 'PollableEvent.cpp', + 'Predictor.cpp', + 'ProxyAutoConfig.cpp', + 'RedirectChannelRegistrar.cpp', + 'RequestContextService.cpp', + 'SimpleBuffer.cpp', + 'StreamingProtocolService.cpp', + 'ThrottleQueue.cpp', + 'Tickler.cpp', + 'TLSServerSocket.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', + ] + +# nsINetworkInfoService support. +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + SOURCES += [ + 'NetworkInfoServiceWindows.cpp', + 'nsNetworkInfoService.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'NetworkInfoServiceCocoa.cpp', + 'nsNetworkInfoService.cpp', + ] +elif CONFIG['OS_ARCH'] == 'Linux': + SOURCES += [ + 'NetworkInfoServiceLinux.cpp', + 'nsNetworkInfoService.cpp', + ] + +EXTRA_JS_MODULES += [ + 'NetUtil.jsm', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '/docshell/base', + '/dom/base', + '/netwerk/protocol/http', + '/netwerk/socket', + '/security/pkix/include' +] + +if 'rtsp' in CONFIG['NECKO_PROTOCOLS']: + LOCAL_INCLUDES += [ + '/netwerk/protocol/rtsp/controller', + '/netwerk/protocol/rtsp/rtsp', + ] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/netwerk/base/mozIThirdPartyUtil.idl b/netwerk/base/mozIThirdPartyUtil.idl new file mode 100644 index 000000000..2eea9550a --- /dev/null +++ b/netwerk/base/mozIThirdPartyUtil.idl @@ -0,0 +1,167 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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; + +/** + * Utility functions for determining whether a given URI, channel, or window + * hierarchy is third party with respect to a known URI. + */ +[scriptable, 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); + + /** + * getURIFromWindow + * + * Returns the URI associated with the script object principal for the + * window. + */ + nsIURI getURIFromWindow(in mozIDOMWindowProxy aWindow); + + /** + * getTopWindowForChannel + * + * Returns the top-level window associated with the given channel. + */ + mozIDOMWindowProxy getTopWindowForChannel(in nsIChannel aChannel); +}; + +%{ C++ +/** + * The mozIThirdPartyUtil implementation is an XPCOM service registered + * under the ContractID: + */ +#define THIRDPARTYUTIL_CONTRACTID "@mozilla.org/thirdpartyutil;1" +%} + diff --git a/netwerk/base/netCore.h b/netwerk/base/netCore.h new file mode 100644 index 000000000..7a0738cf9 --- /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 000000000..c15daecd8 --- /dev/null +++ b/netwerk/base/nsASocketHandler.h @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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__ + +// socket handler used by nsISocketTransportService. +// methods are only called on the socket thread. + +class nsASocketHandler : public nsISupports +{ +public: + nsASocketHandler() + : mCondition(NS_OK) + , mPollFlags(0) + , mPollTimeout(UINT16_MAX) + , mIsPrivate(false) + {} + + // + // 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; + + // + // 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; + + // + // 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; + + bool mIsPrivate; + + // + // 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) { } + + // + // 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 000000000..3b19b93c7 --- /dev/null +++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp @@ -0,0 +1,288 @@ +/* -*- 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 "mozilla/Logging.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) + +class nsAsyncVerifyRedirectCallbackEvent : public Runnable { +public: + nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback *cb, + nsresult result) + : mCallback(cb), mResult(result) { + } + + NS_IMETHOD Run() override + { + LOG(("nsAsyncVerifyRedirectCallbackEvent::Run() " + "callback to %p with result %x", + mCallback.get(), mResult)); + (void) mCallback->OnRedirectVerifyCallback(mResult); + return NS_OK; + } +private: + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback; + nsresult mResult; +}; + +nsAsyncRedirectVerifyHelper::nsAsyncRedirectVerifyHelper() + : mFlags(0), + mWaitingForRedirectCallback(false), + mCallbackInitiated(false), + mExpectedCallbacks(0), + mResult(NS_OK) +{ +} + +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, bool synchronize) +{ + LOG(("nsAsyncRedirectVerifyHelper::Init() " + "oldChan=%p newChan=%p", oldChan, newChan)); + mOldChan = oldChan; + mNewChan = newChan; + mFlags = flags; + mCallbackThread = do_GetCurrentThread(); + + if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE))) { + nsCOMPtr<nsILoadInfo> loadInfo = oldChan->GetLoadInfo(); + if (loadInfo && loadInfo->GetDontFollowRedirects()) { + ExplicitCallback(NS_BINDING_ABORTED); + return NS_OK; + } + } + + if (synchronize) + mWaitingForRedirectCallback = true; + + nsresult rv; + rv = NS_DispatchToMainThread(this); + NS_ENSURE_SUCCESS(rv, rv); + + if (synchronize) { + nsIThread *thread = NS_GetCurrentThread(); + while (mWaitingForRedirectCallback) { + if (!NS_ProcessNextEvent(thread)) { + return NS_ERROR_UNEXPECTED; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result) +{ + LOG(("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() " + "result=%x expectedCBs=%u mResult=%x", + result, mExpectedCallbacks, 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=%x", + sink, mExpectedCallbacks, 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=%x expectedCBs=%u", 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=%x expectedCBs=%u mCallbackInitiated=%u mResult=%x", + result, mExpectedCallbacks, mCallbackInitiated, mResult)); + + nsCOMPtr<nsIAsyncVerifyRedirectCallback> + callback(do_QueryInterface(mOldChan)); + + if (!callback || !mCallbackThread) { + LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "callback=%p mCallbackThread=%p", callback.get(), mCallbackThread.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 = mCallbackThread->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=%x", mExpectedCallbacks, mResult)); + + mCallbackInitiated = true; + + // Invoke the callback if we are done + if (mExpectedCallbacks == 0) + ExplicitCallback(mResult); +} + +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() +{ + bool canceled; + nsCOMPtr<nsIHttpChannelInternal> oldChannelInternal = + do_QueryInterface(mOldChan); + if (oldChannelInternal) { + oldChannelInternal->GetCanceled(&canceled); + if (canceled) { + return true; + } + } else if (mOldChan) { + // For non-HTTP channels check on the status, failure + // indicates the channel has probably been canceled. + nsresult status = NS_ERROR_FAILURE; + mOldChan->GetStatus(&status); + if (NS_FAILED(status)) { + return true; + } + } + + return false; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.h b/netwerk/base/nsAsyncRedirectVerifyHelper.h new file mode 100644 index 000000000..f67785498 --- /dev/null +++ b/netwerk/base/nsAsyncRedirectVerifyHelper.h @@ -0,0 +1,129 @@ +/* -*- 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/. */ + +#ifndef nsAsyncRedirectVerifyHelper_h +#define nsAsyncRedirectVerifyHelper_h + +#include "nsIRunnable.h" +#include "nsIThread.h" +#include "nsIChannelEventSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.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 nsIAsyncVerifyRedirectCallback +{ + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + +public: + nsAsyncRedirectVerifyHelper(); + + /* + * 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 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, + bool synchronize = false); + +protected: + nsCOMPtr<nsIChannel> mOldChan; + nsCOMPtr<nsIChannel> mNewChan; + uint32_t mFlags; + bool mWaitingForRedirectCallback; + nsCOMPtr<nsIThread> mCallbackThread; + bool mCallbackInitiated; + int32_t mExpectedCallbacks; + nsresult mResult; // 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 000000000..6eec29d61 --- /dev/null +++ b/netwerk/base/nsAsyncStreamCopier.cpp @@ -0,0 +1,419 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "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; + +#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) + : mCopier(aCopier) + , mTarget(NS_GetCurrentThread()) + { } + NS_IMETHOD Run() override + { + nsresult rv = mCopier->ApplyBufferingPolicy(); + if (NS_FAILED(rv)) { + mCopier->Cancel(rv); + return NS_OK; + } + + rv = mTarget->Dispatch(NewRunnableMethod(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() + : mLock("nsAsyncStreamCopier.mLock") + , mMode(NS_ASYNCCOPY_VIA_READSEGMENTS) + , mChunkSize(nsIOService::gDefaultSegmentSize) + , mStatus(NS_OK) + , mIsPending(false) + , mShouldSniffBuffering(false) +{ + LOG(("Creating nsAsyncStreamCopier @%x\n", this)); +} + +nsAsyncStreamCopier::~nsAsyncStreamCopier() +{ + LOG(("Destroying nsAsyncStreamCopier @%x\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=%x]\n", this, 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=%x]\n", status)); + observer->OnStopRequest(AsRequest(), ctx, status); + } +} + +void +nsAsyncStreamCopier::OnAsyncCopyComplete(void *closure, nsresult status) +{ + nsAsyncStreamCopier *self = (nsAsyncStreamCopier *) closure; + self->Complete(status); + NS_RELEASE(self); // addref'd in AsyncCopy +} + +//----------------------------------------------------------------------------- +// 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::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() +{ + NS_NOTREACHED("nsAsyncStreamCopier::Suspend"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::Resume() +{ + NS_NOTREACHED("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::GetLoadGroup(nsILoadGroup **aLoadGroup) +{ + *aLoadGroup = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncStreamCopier::SetLoadGroup(nsILoadGroup *aLoadGroup) +{ + return NS_OK; +} + +nsresult +nsAsyncStreamCopier::InitInternal(nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + uint32_t chunkSize, + bool closeSource, + bool closeSink) +{ + 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=%x]\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. + mIsPending = true; + + if (mObserver) { + rv = mObserver->OnStartRequest(AsRequest(), nullptr); + 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. + NS_ADDREF_THIS(); + { + MutexAutoLock lock(mLock); + rv = NS_AsyncCopy(mSource, mSink, mTarget, mMode, mChunkSize, + OnAsyncCopyComplete, this, mCloseSource, mCloseSink, + getter_AddRefs(mCopierCtx)); + } + if (NS_FAILED(rv)) { + NS_RELEASE_THIS(); + Cancel(rv); + } +} + + diff --git a/netwerk/base/nsAsyncStreamCopier.h b/netwerk/base/nsAsyncStreamCopier.h new file mode 100644 index 000000000..7529a327a --- /dev/null +++ b/netwerk/base/nsAsyncStreamCopier.h @@ -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/. */ + +#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; + + mozilla::Mutex mLock; + + nsAsyncCopyMode mMode; + uint32_t mChunkSize; + nsresult mStatus; + bool mIsPending; + bool mCloseSource; + bool mCloseSink; + bool mShouldSniffBuffering; + + 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 000000000..f52ff5454 --- /dev/null +++ b/netwerk/base/nsAuthInformationHolder.cpp @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 000000000..b24bc743f --- /dev/null +++ b/netwerk/base/nsAuthInformationHolder.h @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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() {} + +public: + // aAuthType must be ASCII + nsAuthInformationHolder(uint32_t aFlags, const nsString& aRealm, + const nsCString& 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 000000000..f112be750 --- /dev/null +++ b/netwerk/base/nsBase64Encoder.cpp @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "plbase64.h" +#include "prmem.h" + +NS_IMPL_ISUPPORTS(nsBase64Encoder, nsIOutputStream) + +NS_IMETHODIMP +nsBase64Encoder::Close() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsBase64Encoder::Flush() +{ + 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(nsCSubstring& result) +{ + char* b64 = PL_Base64Encode(mData.get(), mData.Length(), nullptr); + if (!b64) + return NS_ERROR_OUT_OF_MEMORY; + + result.Assign(b64); + PR_Free(b64); + // 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 000000000..ff61de51e --- /dev/null +++ b/netwerk/base/nsBase64Encoder.h @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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() {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + + nsresult Finish(nsCSubstring& _result); + private: + ~nsBase64Encoder() {} + + /// 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 000000000..200804c1e --- /dev/null +++ b/netwerk/base/nsBaseChannel.cpp @@ -0,0 +1,937 @@ +/* -*- 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 "nsIContentSniffer.h" +#include "nsIScriptSecurityManager.h" +#include "nsMimeTypes.h" +#include "nsIHttpEventSink.h" +#include "nsIHttpChannel.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" + +// 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 mPump 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__(mPump) + +//----------------------------------------------------------------------------- +// nsBaseChannel + +nsBaseChannel::nsBaseChannel() + : mLoadFlags(LOAD_NORMAL) + , mQueriedProgressSink(true) + , mSynthProgressEvents(false) + , mAllowThreadRetargeting(true) + , mWaitingOnAsyncRedirect(false) + , mStatus(NS_OK) + , mContentDispositionHint(UINT32_MAX) + , mContentLength(-1) + , mWasOpened(false) +{ + mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); +} + +nsBaseChannel::~nsBaseChannel() +{ + NS_ReleaseOnMainThread(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 + if (mLoadInfo) { + nsSecurityFlags secFlags = mLoadInfo->GetSecurityFlags() & + ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + nsCOMPtr<nsILoadInfo> newLoadInfo = + static_cast<mozilla::LoadInfo*>(mLoadInfo.get())->CloneWithNewSecFlags(secFlags); + + nsCOMPtr<nsIPrincipal> uriPrincipal; + nsIScriptSecurityManager *sm = nsContentUtils::GetSecurityManager(); + sm->GetChannelURIPrincipal(this, getter_AddRefs(uriPrincipal)); + bool isInternalRedirect = + (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE)); + newLoadInfo->AppendRedirectedPrincipal(uriPrincipal, isInternalRedirect); + newChannel->SetLoadInfo(newLoadInfo); + } + else { + // the newChannel was created with a dummy loadInfo, we should clear + // it in case the original channel does not have a loadInfo + newChannel->SetLoadInfo(nullptr); + } + + // Preserve the privacy bit if it has been overridden + if (mPrivateBrowsingOverriden) { + nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel = + do_QueryInterface(newChannel); + if (newPBChannel) { + newPBChannel->SetPrivate(mPrivateBrowsing); + } + } + + nsCOMPtr<nsIWritablePropertyBag> bag = ::do_QueryInterface(newChannel); + if (bag) { + for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) { + bag->SetProperty(iter.Key(), iter.UserData()); + } + } + + // Notify consumer, giving chance to cancel redirect. For backwards compat, + // we support nsIHttpEventSink if we are an HTTP channel and if this is not + // an internal redirect. + + RefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper = + new nsAsyncRedirectVerifyHelper(); + + bool checkRedirectSynchronously = !openNewChannel; + + mRedirectChannel = newChannel; + mRedirectFlags = redirectFlags; + mOpenRedirectChannel = openNewChannel; + nsresult rv = redirectCallbackHelper->Init(this, newChannel, redirectFlags, + checkRedirectSynchronously); + if (NS_FAILED(rv)) + return rv; + + if (checkRedirectSynchronously && NS_FAILED(mStatus)) + return mStatus; + + return NS_OK; +} + +nsresult +nsBaseChannel::ContinueRedirect() +{ + // Backwards compat for non-internal redirects from a HTTP channel. + // XXX Is our http channel implementation going to derive from nsBaseChannel? + // If not, this code can be removed. + if (!(mRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) { + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(); + if (httpChannel) { + nsCOMPtr<nsIHttpEventSink> httpEventSink; + GetCallback(httpEventSink); + if (httpEventSink) { + nsresult rv = httpEventSink->OnRedirect(httpChannel, mRedirectChannel); + if (NS_FAILED(rv)) { + return rv; + } + } + } + } + + // 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; + if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) { + MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!"); + rv = mRedirectChannel->AsyncOpen2(mListener); + } + else { + rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext); + } + 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::PushStreamConverter(const char *fromType, + const char *toType, + bool invalidatesContentLength, + nsIStreamListener **result) +{ + NS_ASSERTION(mListener, "no listener"); + + nsresult rv; + nsCOMPtr<nsIStreamConverterService> scs = + do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIStreamListener> converter; + rv = scs->AsyncConvertData(fromType, toType, mListener, mListenerContext, + getter_AddRefs(converter)); + if (NS_SUCCEEDED(rv)) { + mListener = converter; + if (invalidatesContentLength) + mContentLength = -1; + if (result) { + *result = nullptr; + converter.swap(*result); + } + } + return rv; +} + +nsresult +nsBaseChannel::BeginPumpingData() +{ + nsCOMPtr<nsIInputStream> stream; + nsCOMPtr<nsIChannel> channel; + nsresult 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) { + rv = NS_DispatchToCurrentThread(new RedirectRunnable(this, channel)); + 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. + + rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, -1, -1, 0, 0, + true); + if (NS_SUCCEEDED(rv)) + rv = mPump->AsyncRead(this, nullptr); + + return rv; +} + +void +nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel) +{ + NS_ASSERTION(!mPump, "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, mListenerContext); + mListener->OnStopRequest(this, mListenerContext, 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 (mLoadFlags & LOAD_CLASSIFY_URI) { + RefPtr<nsChannelClassifier> classifier = new nsChannelClassifier(); + if (classifier) { + classifier->Start(this); + } else { + Cancel(NS_ERROR_OUT_OF_MEMORY); + } + } +} + +//----------------------------------------------------------------------------- +// nsBaseChannel::nsISupports + +NS_IMPL_ISUPPORTS_INHERITED(nsBaseChannel, + nsHashPropertyBag, + nsIRequest, + nsIChannel, + nsIThreadRetargetableRequest, + nsIInterfaceRequestor, + nsITransportEventSink, + nsIRequestObserver, + nsIStreamListener, + nsIThreadRetargetableStreamListener, + nsIAsyncVerifyRedirectCallback, + nsIPrivateBrowsingChannel) + +//----------------------------------------------------------------------------- +// 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 (mPump && NS_SUCCEEDED(mStatus)) { + mPump->GetStatus(status); + } else { + *status = mStatus; + } + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::Cancel(nsresult status) +{ + // Ignore redundant cancelation + if (NS_FAILED(mStatus)) + return NS_OK; + + mStatus = status; + + if (mPump) + mPump->Cancel(status); + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::Suspend() +{ + NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED); + return mPump->Suspend(); +} + +NS_IMETHODIMP +nsBaseChannel::Resume() +{ + NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED); + return mPump->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::GetLoadGroup(nsILoadGroup **aLoadGroup) +{ + NS_IF_ADDREF(*aLoadGroup = mLoadGroup); + 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) +{ + *aURI = OriginalURI(); + NS_ADDREF(*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) +{ + NS_IF_ADDREF(*aURI = mURI); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetOwner(nsISupports **aOwner) +{ + NS_IF_ADDREF(*aOwner = mOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetOwner(nsISupports *aOwner) +{ + mOwner = aOwner; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) +{ + mLoadInfo = aLoadInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) +{ + NS_IF_ADDREF(*aLoadInfo = mLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) +{ + NS_IF_ADDREF(*aCallbacks = mCallbacks); + 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(nsISupports **aSecurityInfo) +{ + NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); + 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 = new nsString(aContentDispositionFilename); + 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 **result) +{ + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS); + + nsCOMPtr<nsIChannel> chan; + nsresult rv = OpenContentStream(false, result, getter_AddRefs(chan)); + NS_ASSERTION(!chan || !*result, "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(result); + } else if (rv == NS_ERROR_NOT_IMPLEMENTED) + return NS_ImplementChannelOpen(this, result); + + if (NS_SUCCEEDED(rv)) { + mWasOpened = true; + ClassifyURI(); + } + + return rv; +} + +NS_IMETHODIMP +nsBaseChannel::Open2(nsIInputStream** aStream) +{ + nsCOMPtr<nsIStreamListener> listener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return Open(aStream); +} + +NS_IMETHODIMP +nsBaseChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) +{ + MOZ_ASSERT(!mLoadInfo || + mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone() || + (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL && + nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())), + "security flags in loadInfo but asyncOpen2() not called"); + + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); + NS_ENSURE_ARG(listener); + + // 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. + nsresult rv = NS_CheckPortSafety(mURI); + if (NS_FAILED(rv)) { + mCallbacks = nullptr; + return rv; + } + + // Store the listener and context early so that OpenContentStream and the + // stream's AsyncWait method (called by AsyncRead) can have access to them + // via PushStreamConverter and 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; + mListenerContext = ctxt; + + // 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; + 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; +} + +NS_IMETHODIMP +nsBaseChannel::AsyncOpen2(nsIStreamListener *aListener) +{ + nsCOMPtr<nsIStreamListener> listener = aListener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return AsyncOpen(listener, nullptr); +} + +//----------------------------------------------------------------------------- +// 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 (!mPump || 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, mListenerContext, status, statusArg.get()); + } + } + + if (progress) { + mProgressSink->OnProgress(this, mListenerContext, 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); + + nsCOMPtr<nsIContentSniffer> sniffer = + do_CreateInstance(NS_GENERIC_CONTENT_SNIFFER); + if (!sniffer) + return; + + nsAutoCString detected; + nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected); + if (NS_SUCCEEDED(rv)) + chan->SetContentType(detected); +} + +NS_IMETHODIMP +nsBaseChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + MOZ_ASSERT(request == 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, mListenerContext); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, + 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; + + if (mListener) // null in case of redirect + mListener->OnStopRequest(this, mListenerContext, 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, nsISupports *ctxt, + nsIInputStream *stream, uint64_t offset, + uint32_t count) +{ + SUSPEND_PUMP_FOR_SCOPE(); + + nsresult rv = mListener->OnDataAvailable(this, mListenerContext, 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 mozilla::Runnable + { + RefPtr<nsBaseChannel> mChannel; + int64_t mProgress; + int64_t mContentLength; + public: + OnTransportStatusAsyncEvent(nsBaseChannel* aChannel, + int64_t aProgress, + int64_t aContentLength) + : 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); + NS_DispatchToMainThread(runnable); + } + } + + 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(nsIEventTarget* aEventTarget) +{ + MOZ_ASSERT(NS_IsMainThread()); + + NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED); + + if (!mAllowThreadRetargeting) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + return mPump->RetargetDeliveryTo(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(); +} diff --git a/netwerk/base/nsBaseChannel.h b/netwerk/base/nsBaseChannel.h new file mode 100644 index 000000000..b98609e85 --- /dev/null +++ b/netwerk/base/nsBaseChannel.h @@ -0,0 +1,300 @@ +/* -*- 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 "nsString.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsHashPropertyBag.h" +#include "nsInputStreamPump.h" + +#include "nsIChannel.h" +#include "nsIURI.h" +#include "nsILoadGroup.h" +#include "nsILoadInfo.h" +#include "nsIStreamListener.h" +#include "nsIInterfaceRequestor.h" +#include "nsIProgressEventSink.h" +#include "nsITransport.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "PrivateBrowsingChannel.h" +#include "nsThreadUtils.h" + +class nsIInputStream; + +//----------------------------------------------------------------------------- +// 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> + , protected nsIStreamListener + , 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(); + + // This method must be called to initialize the basechannel instance. + nsresult Init() { + return NS_OK; + } + +protected: + // ----------------------------------------------- + // Methods to be implemented by the derived class: + + virtual ~nsBaseChannel(); + +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; + + // 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. + nsISupports *SecurityInfo() { + return mSecurityInfo; + } + void SetSecurityInfo(nsISupports *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 mPump || mWaitingOnAsyncRedirect; + } + + // 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)); + } + + // Helper function for calling QueryInterface on this. + nsQueryInterface do_QueryInterface() { + return nsQueryInterface(static_cast<nsIChannel *>(this)); + } + // MSVC needs this: + nsQueryInterface do_QueryInterface(nsISupports *obj) { + return nsQueryInterface(obj); + } + + // 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; + } + + // Pushes a new stream converter in front of the channel's stream listener. + // The fromType and toType values are passed to nsIStreamConverterService's + // AsyncConvertData method. If invalidatesContentLength is true, then the + // channel's content-length property will be assigned a value of -1. This is + // necessary when the converter changes the length of the resulting data + // stream, which is almost always the case for a "stream converter" ;-) + // This function optionally returns a reference to the new converter. + nsresult PushStreamConverter(const char *fromType, const char *toType, + bool invalidatesContentLength = true, + nsIStreamListener **converter = nullptr); + +protected: + void DisallowThreadRetargeting() { + mAllowThreadRetargeting = false; + } + +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; + mListenerContext = 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) + : mChannel(chan), mNewChannel(newChannel) + { + NS_PRECONDITION(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; + nsCOMPtr<nsIProgressEventSink> mProgressSink; + nsCOMPtr<nsIURI> mOriginalURI; + nsCOMPtr<nsISupports> mOwner; + nsCOMPtr<nsISupports> mSecurityInfo; + nsCOMPtr<nsIChannel> mRedirectChannel; + nsCString mContentType; + nsCString mContentCharset; + uint32_t mLoadFlags; + bool mQueriedProgressSink; + bool mSynthProgressEvents; + bool mAllowThreadRetargeting; + bool mWaitingOnAsyncRedirect; + bool mOpenRedirectChannel; + uint32_t mRedirectFlags; + +protected: + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsILoadGroup> mLoadGroup; + nsCOMPtr<nsILoadInfo> mLoadInfo; + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; + nsCOMPtr<nsIStreamListener> mListener; + nsCOMPtr<nsISupports> mListenerContext; + nsresult mStatus; + uint32_t mContentDispositionHint; + nsAutoPtr<nsString> mContentDispositionFilename; + int64_t mContentLength; + bool mWasOpened; + + 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 000000000..ee5a8ef3c --- /dev/null +++ b/netwerk/base/nsBaseContentStream.cpp @@ -0,0 +1,137 @@ +/* -*- 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 "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(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_THREADSAFE + +//----------------------------------------------------------------------------- +// 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::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 000000000..992c8733e --- /dev/null +++ b/netwerk/base/nsBaseContentStream.h @@ -0,0 +1,82 @@ +/* -*- 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/. */ + +#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() {} + +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 000000000..e67c3009b --- /dev/null +++ b/netwerk/base/nsBufferedStreams.cpp @@ -0,0 +1,832 @@ +/* -*- 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 "ipc/IPCMessageUtils.h" + +#include "nsBufferedStreams.h" +#include "nsStreamUtils.h" +#include "nsNetCID.h" +#include "nsIClassInfoImpl.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 mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +//////////////////////////////////////////////////////////////////////////////// +// nsBufferedStream + +nsBufferedStream::nsBufferedStream() + : mBuffer(nullptr), + mBufferStartOffset(0), + mCursor(0), + mFillPoint(0), + mStream(nullptr), + mBufferDisabled(false), + mEOF(false), + mGetBufferCount(0) +{ +} + +nsBufferedStream::~nsBufferedStream() +{ + Close(); +} + +NS_IMPL_ISUPPORTS(nsBufferedStream, nsISeekableStream) + +nsresult +nsBufferedStream::Init(nsISupports* stream, uint32_t bufferSize) +{ + NS_ASSERTION(stream, "need to supply a stream"); + NS_ASSERTION(mStream == nullptr, "already inited"); + mStream = stream; + NS_IF_ADDREF(mStream); + mBufferSize = bufferSize; + mBufferStartOffset = 0; + mCursor = 0; + mBuffer = new (mozilla::fallible) char[bufferSize]; + if (mBuffer == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; +} + +nsresult +nsBufferedStream::Close() +{ + NS_IF_RELEASE(mStream); + 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 + return NS_OK; +} + +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)) 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: + NS_NOTREACHED("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)) return rv; + + rv = ras->Seek(whence, offset); + if (NS_FAILED(rv)) 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; + 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; +} + +//////////////////////////////////////////////////////////////////////////////// +// 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) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream) + NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) + NS_IMPL_QUERY_CLASSINFO(nsBufferedInputStream) +NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream) + +NS_IMPL_CI_INTERFACE_GETTER(nsBufferedInputStream, + nsIInputStream, + nsIBufferedInputStream, + nsISeekableStream, + nsIStreamBufferAccess) + +nsresult +nsBufferedInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + nsBufferedInputStream* stream = new nsBufferedInputStream(); + if (stream == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(stream); + nsresult rv = stream->QueryInterface(aIID, aResult); + NS_RELEASE(stream); + return rv; +} + +NS_IMETHODIMP +nsBufferedInputStream::Init(nsIInputStream* stream, uint32_t bufferSize) +{ + return nsBufferedStream::Init(stream, bufferSize); +} + +NS_IMETHODIMP +nsBufferedInputStream::Close() +{ + nsresult rv1 = NS_OK, rv2; + if (mStream) { + rv1 = Source()->Close(); + NS_RELEASE(mStream); + } + rv2 = nsBufferedStream::Close(); + if (NS_FAILED(rv1)) return rv1; + return rv2; +} + +NS_IMETHODIMP +nsBufferedInputStream::Available(uint64_t *result) +{ + nsresult rv = NS_OK; + *result = 0; + if (mStream) { + rv = Source()->Available(result); + } + *result += (mFillPoint - mCursor); + return rv; +} + +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; + while (count > 0) { + uint32_t amt = std::min(count, mFillPoint - mCursor); + if (amt > 0) { + uint32_t read = 0; + rv = writer(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 (NS_FAILED(rv) || 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); + + 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; + + 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; + + *aStream = mStream; + NS_IF_ADDREF(*aStream); + return NS_OK; +} + +void +nsBufferedInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + BufferedInputStreamParams params; + + if (mStream) { + nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream); + MOZ_ASSERT(stream); + + InputStreamParams wrappedParams; + SerializeInputStream(stream, wrappedParams, aFileDescriptors); + + params.optionalStream() = wrappedParams; + } + else { + params.optionalStream() = mozilla::void_t(); + } + + params.bufferSize() = mBufferSize; + + aParams = params; +} + +bool +nsBufferedInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) +{ + if (aParams.type() != InputStreamParams::TBufferedInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const BufferedInputStreamParams& params = + aParams.get_BufferedInputStreamParams(); + const OptionalInputStreamParams& wrappedParams = params.optionalStream(); + + nsCOMPtr<nsIInputStream> stream; + if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) { + stream = DeserializeInputStream(wrappedParams.get_InputStreamParams(), + aFileDescriptors); + if (!stream) { + NS_WARNING("Failed to deserialize wrapped stream!"); + return false; + } + } + else { + NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t, + "Unknown type for OptionalInputStreamParams!"); + } + + nsresult rv = Init(stream, params.bufferSize()); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + +Maybe<uint64_t> +nsBufferedInputStream::ExpectedSerializedLength() +{ + nsCOMPtr<nsIIPCSerializableInputStream> stream = do_QueryInterface(mStream); + if (stream) { + return stream->ExpectedSerializedLength(); + } + return Nothing(); +} + +//////////////////////////////////////////////////////////////////////////////// +// 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(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + nsBufferedOutputStream* stream = new nsBufferedOutputStream(); + if (stream == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(stream); + nsresult rv = stream->QueryInterface(aIID, aResult); + NS_RELEASE(stream); + return rv; +} + +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() +{ + nsresult rv1, rv2 = NS_OK, rv3; + rv1 = Flush(); + // 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(); + NS_RELEASE(mStream); + } + rv3 = nsBufferedStream::Close(); + if (NS_FAILED(rv1)) return rv1; + if (NS_FAILED(rv2)) return rv2; + return rv3; +} + +NS_IMETHODIMP +nsBufferedOutputStream::Write(const char *buf, uint32_t count, uint32_t *result) +{ + nsresult rv = NS_OK; + uint32_t written = 0; + 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, "iloop in nsBufferedOutputStream::Write!"); + rv = Flush(); + if (NS_FAILED(rv)) 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; + } + 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 rv = nsBufferedOutputStream::Flush(); + if (NS_FAILED(rv)) + NS_WARNING("failed to flush buffered data! possible dataloss"); + + // ... and finish the underlying stream... + if (NS_SUCCEEDED(rv)) + rv = mSafeStream->Finish(); + else + Sink()->Close(); + + // ... and close the buffered stream, so any further attempts to flush/close + // the buffered stream won't cause errors. + nsBufferedStream::Close(); + + return rv; +} + +static nsresult +nsReadFromInputStream(nsIOutputStream* outStr, + void* closure, + char* toRawSegment, + uint32_t offset, + uint32_t count, + uint32_t *readCount) +{ + nsIInputStream* fromStream = (nsIInputStream*)closure; + return fromStream->Read(toRawSegment, count, readCount); +} + +NS_IMETHODIMP +nsBufferedOutputStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) +{ + return WriteSegments(nsReadFromInputStream, inStr, count, _retval); +} + +NS_IMETHODIMP +nsBufferedOutputStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) +{ + *_retval = 0; + nsresult rv; + 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 written 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; + + 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; + } + + *aStream = mStream; + NS_IF_ADDREF(*aStream); + return NS_OK; +} + +#undef METER + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/nsBufferedStreams.h b/netwerk/base/nsBufferedStreams.h new file mode 100644 index 000000000..93a770beb --- /dev/null +++ b/netwerk/base/nsBufferedStreams.h @@ -0,0 +1,122 @@ +/* -*- 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/. */ + +#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" + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedStream : public nsISeekableStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISEEKABLESTREAM + + nsBufferedStream(); + + nsresult Close(); + +protected: + virtual ~nsBufferedStream(); + + nsresult Init(nsISupports* stream, uint32_t bufferSize); + NS_IMETHOD Fill() = 0; + NS_IMETHOD Flush() = 0; + + uint32_t mBufferSize; + char* mBuffer; + + // mBufferStartOffset is the offset relative to the start of mStream. + int64_t mBufferStartOffset; + + // mCursor is the read cursor for input streams, or write cursor for + // output streams, and is relative to mBufferStartOffset. + uint32_t mCursor; + + // 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; + + nsISupports* mStream; // cast to appropriate subclass + + bool mBufferDisabled; + bool mEOF; // True if mStream is at EOF + uint8_t mGetBufferCount; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedInputStream : public nsBufferedStream, + public nsIBufferedInputStream, + public nsIStreamBufferAccess, + public nsIIPCSerializableInputStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIBUFFEREDINPUTSTREAM + NS_DECL_NSISTREAMBUFFERACCESS + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + + nsBufferedInputStream() : nsBufferedStream() {} + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + + nsIInputStream* Source() { + return (nsIInputStream*)mStream; + } + +protected: + virtual ~nsBufferedInputStream() {} + + NS_IMETHOD Fill() override; + NS_IMETHOD Flush() override { return NS_OK; } // no-op for input streams +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsBufferedOutputStream final : 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() : nsBufferedStream() {} + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + + nsIOutputStream* Sink() { + return (nsIOutputStream*)mStream; + } + +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/nsChannelClassifier.cpp b/netwerk/base/nsChannelClassifier.cpp new file mode 100644 index 000000000..6b9f9ede3 --- /dev/null +++ b/netwerk/base/nsChannelClassifier.cpp @@ -0,0 +1,696 @@ +/* -*- Mode: C++; tab-width: 8; 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 "nsChannelClassifier.h" + +#include "mozIThirdPartyUtil.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsICacheEntry.h" +#include "nsICachingChannel.h" +#include "nsIChannel.h" +#include "nsIDocShell.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIHttpChannelInternal.h" +#include "nsIIOService.h" +#include "nsIParentChannel.h" +#include "nsIPermissionManager.h" +#include "nsIPrivateBrowsingTrackingProtectionWhitelist.h" +#include "nsIProtocolHandler.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsISecureBrowserUI.h" +#include "nsISecurityEventSink.h" +#include "nsIURL.h" +#include "nsIWebProgressListener.h" +#include "nsNetUtil.h" +#include "nsPIDOMWindow.h" +#include "nsXULAppAPI.h" + +#include "mozilla/ErrorNames.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" + +namespace mozilla { +namespace net { + +// +// MOZ_LOG=nsChannelClassifier:5 +// +static LazyLogModule gChannelClassifierLog("nsChannelClassifier"); + +#undef LOG +#define LOG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(gChannelClassifierLog, LogLevel::Debug) + +NS_IMPL_ISUPPORTS(nsChannelClassifier, + nsIURIClassifierCallback) + +nsChannelClassifier::nsChannelClassifier() + : mIsAllowListed(false), + mSuspendedChannel(false) +{ +} + +nsresult +nsChannelClassifier::ShouldEnableTrackingProtection(nsIChannel *aChannel, + bool *result) +{ + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + NS_ENSURE_ARG(result); + *result = false; + + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(aChannel, loadContext); + if (!loadContext || !(loadContext->UseTrackingProtection())) { + return NS_OK; + } + + nsresult rv; + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(aChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> topWinURI; + rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!topWinURI) { + LOG(("nsChannelClassifier[%p]: No window URI\n", this)); + } + + nsCOMPtr<nsIURI> chanURI; + rv = aChannel->GetURI(getter_AddRefs(chanURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // Third party checks don't work for chrome:// URIs in mochitests, so just + // default to isThirdParty = true. We check isThirdPartyWindow to expand + // the list of domains that are considered first party (e.g., if + // facebook.com includes an iframe from fatratgames.com, all subsources + // included in that iframe are considered third-party with + // isThirdPartyChannel, even if they are not third-party w.r.t. + // facebook.com), and isThirdPartyChannel to prevent top-level navigations + // from being detected as third-party. + bool isThirdPartyChannel = true; + bool isThirdPartyWindow = true; + thirdPartyUtil->IsThirdPartyURI(chanURI, topWinURI, &isThirdPartyWindow); + thirdPartyUtil->IsThirdPartyChannel(aChannel, nullptr, &isThirdPartyChannel); + if (!isThirdPartyWindow || !isThirdPartyChannel) { + *result = false; + if (LOG_ENABLED()) { + LOG(("nsChannelClassifier[%p]: Skipping tracking protection checks " + "for first party or top-level load channel[%p] with uri %s", + this, aChannel, chanURI->GetSpecOrDefault().get())); + } + return NS_OK; + } + + nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + const char ALLOWLIST_EXAMPLE_PREF[] = "channelclassifier.allowlist_example"; + if (!topWinURI && Preferences::GetBool(ALLOWLIST_EXAMPLE_PREF, false)) { + LOG(("nsChannelClassifier[%p]: Allowlisting test domain\n", this)); + rv = ios->NewURI(NS_LITERAL_CSTRING("http://allowlisted.example.com"), + nullptr, nullptr, getter_AddRefs(topWinURI)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Take the host/port portion so we can allowlist by site. Also ignore the + // scheme, since users who put sites on the allowlist probably don't expect + // allowlisting to depend on scheme. + nsCOMPtr<nsIURL> url = do_QueryInterface(topWinURI, &rv); + if (NS_FAILED(rv)) { + return rv; // normal for some loads, no need to print a warning + } + + nsCString escaped(NS_LITERAL_CSTRING("https://")); + nsAutoCString temp; + rv = url->GetHostPort(temp); + NS_ENSURE_SUCCESS(rv, rv); + escaped.Append(temp); + + // Stuff the whole thing back into a URI for the permission manager. + rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPermissionManager> permMgr = + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION; + rv = permMgr->TestPermission(topWinURI, "trackingprotection", &permissions); + NS_ENSURE_SUCCESS(rv, rv); + + if (permissions == nsIPermissionManager::ALLOW_ACTION) { + LOG(("nsChannelClassifier[%p]: Allowlisting channel[%p] for %s", this, + aChannel, escaped.get())); + mIsAllowListed = true; + *result = false; + } else { + *result = true; + } + + // In Private Browsing Mode we also check against an in-memory list. + if (NS_UsePrivateBrowsing(aChannel)) { + nsCOMPtr<nsIPrivateBrowsingTrackingProtectionWhitelist> pbmtpWhitelist = + do_GetService(NS_PBTRACKINGPROTECTIONWHITELIST_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + rv = pbmtpWhitelist->ExistsInAllowList(topWinURI, &exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + mIsAllowListed = true; + LOG(("nsChannelClassifier[%p]: Allowlisting channel[%p] in PBM for %s", + this, aChannel, escaped.get())); + } + + *result = !exists; + } + + // Tracking protection will be enabled so return without updating + // the security state. If any channels are subsequently cancelled + // (page elements blocked) the state will be then updated. + if (*result) { + if (LOG_ENABLED()) { + LOG(("nsChannelClassifier[%p]: Enabling tracking protection checks on " + "channel[%p] with uri %s for toplevel window %s", this, aChannel, + chanURI->GetSpecOrDefault().get(), + topWinURI->GetSpecOrDefault().get())); + } + return NS_OK; + } + + // Tracking protection will be disabled so update the security state + // of the document and fire a secure change event. If we can't get the + // window for the channel, then the shield won't show up so we can't send + // an event to the securityUI anyway. + return NotifyTrackingProtectionDisabled(aChannel); +} + +// static +nsresult +nsChannelClassifier::NotifyTrackingProtectionDisabled(nsIChannel *aChannel) +{ + // Can be called in EITHER the parent or child process. + nsCOMPtr<nsIParentChannel> parentChannel; + NS_QueryNotificationCallbacks(aChannel, parentChannel); + if (parentChannel) { + // This channel is a parent-process proxy for a child process request. + // Tell the child process channel to do this instead. + parentChannel->NotifyTrackingProtectionDisabled(); + return NS_OK; + } + + nsresult rv; + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIDOMWindowProxy> win; + rv = thirdPartyUtil->GetTopWindowForChannel(aChannel, getter_AddRefs(win)); + NS_ENSURE_SUCCESS(rv, rv); + + auto* pwin = nsPIDOMWindowOuter::From(win); + nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell(); + if (!docShell) { + return NS_OK; + } + nsCOMPtr<nsIDocument> doc = docShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_OK); + + // Notify nsIWebProgressListeners of this security event. + // Can be used to change the UI state. + nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv); + NS_ENSURE_SUCCESS(rv, NS_OK); + uint32_t state = 0; + nsCOMPtr<nsISecureBrowserUI> securityUI; + docShell->GetSecurityUI(getter_AddRefs(securityUI)); + if (!securityUI) { + return NS_OK; + } + doc->SetHasTrackingContentLoaded(true); + securityUI->GetState(&state); + state |= nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT; + eventSink->OnSecurityChange(nullptr, state); + + return NS_OK; +} + +void +nsChannelClassifier::Start(nsIChannel *aChannel) +{ + mChannel = aChannel; + + nsresult rv = StartInternal(); + if (NS_FAILED(rv)) { + // If we aren't getting a callback for any reason, assume a good verdict and + // make sure we resume the channel if necessary. + OnClassifyComplete(NS_OK); + } +} + +nsresult +nsChannelClassifier::StartInternal() +{ + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + // Don't bother to run the classifier on a load that has already failed. + // (this might happen after a redirect) + nsresult status; + mChannel->GetStatus(&status); + if (NS_FAILED(status)) + return status; + + // Don't bother to run the classifier on a cached load that was + // previously classified as good. + if (HasBeenClassified(mChannel)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = mChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + // Don't bother checking certain types of URIs. + bool hasFlags; + rv = NS_URIChainHasFlags(uri, + nsIProtocolHandler::URI_DANGEROUS_TO_LOAD, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + rv = NS_URIChainHasFlags(uri, + nsIProtocolHandler::URI_IS_LOCAL_FILE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + rv = NS_URIChainHasFlags(uri, + nsIProtocolHandler::URI_IS_UI_RESOURCE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + rv = NS_URIChainHasFlags(uri, + nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, + &hasFlags); + NS_ENSURE_SUCCESS(rv, rv); + if (hasFlags) return NS_ERROR_UNEXPECTED; + + // Skip whitelisted hostnames. + nsAutoCString whitelisted; + Preferences::GetCString("urlclassifier.skipHostnames", &whitelisted); + if (!whitelisted.IsEmpty()) { + ToLowerCase(whitelisted); + LOG(("nsChannelClassifier[%p]:StartInternal whitelisted hostnames = %s", + this, whitelisted.get())); + if (IsHostnameWhitelisted(uri, whitelisted)) { + return NS_ERROR_UNEXPECTED; + } + } + + nsCOMPtr<nsIURIClassifier> uriClassifier = + do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv); + if (rv == NS_ERROR_FACTORY_NOT_REGISTERED || + rv == NS_ERROR_NOT_AVAILABLE) { + // no URI classifier, ignore this failure. + return NS_ERROR_NOT_AVAILABLE; + } + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIScriptSecurityManager> securityManager = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> principal; + rv = securityManager->GetChannelURIPrincipal(mChannel, getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + bool expectCallback; + bool trackingProtectionEnabled = false; + (void)ShouldEnableTrackingProtection(mChannel, &trackingProtectionEnabled); + + if (LOG_ENABLED()) { + nsCOMPtr<nsIURI> principalURI; + principal->GetURI(getter_AddRefs(principalURI)); + LOG(("nsChannelClassifier[%p]: Classifying principal %s on channel with " + "uri %s", this, principalURI->GetSpecOrDefault().get(), + uri->GetSpecOrDefault().get())); + } + rv = uriClassifier->Classify(principal, trackingProtectionEnabled, this, + &expectCallback); + if (NS_FAILED(rv)) { + return rv; + } + + if (expectCallback) { + // Suspend the channel, it will be resumed when we get the classifier + // callback. + rv = mChannel->Suspend(); + if (NS_FAILED(rv)) { + // Some channels (including nsJSChannel) fail on Suspend. This + // shouldn't be fatal, but will prevent malware from being + // blocked on these channels. + LOG(("nsChannelClassifier[%p]: Couldn't suspend channel", this)); + return rv; + } + + mSuspendedChannel = true; + LOG(("nsChannelClassifier[%p]: suspended channel %p", + this, mChannel.get())); + } else { + LOG(("nsChannelClassifier[%p]: not expecting callback", this)); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool +nsChannelClassifier::IsHostnameWhitelisted(nsIURI *aUri, + const nsACString &aWhitelisted) +{ + nsAutoCString host; + nsresult rv = aUri->GetHost(host); + if (NS_FAILED(rv) || host.IsEmpty()) { + return false; + } + ToLowerCase(host); + + nsCCharSeparatedTokenizer tokenizer(aWhitelisted, ','); + while (tokenizer.hasMoreTokens()) { + const nsCSubstring& token = tokenizer.nextToken(); + if (token.Equals(host)) { + LOG(("nsChannelClassifier[%p]:StartInternal skipping %s (whitelisted)", + this, host.get())); + return true; + } + } + + return false; +} + +// Note in the cache entry that this URL was classified, so that future +// cached loads don't need to be checked. +void +nsChannelClassifier::MarkEntryClassified(nsresult status) +{ + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + // Don't cache tracking classifications because we support allowlisting. + if (status == NS_ERROR_TRACKING_URI || mIsAllowListed) { + return; + } + + if (LOG_ENABLED()) { + nsAutoCString errorName; + GetErrorName(status, errorName); + nsCOMPtr<nsIURI> uri; + mChannel->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + uri->GetAsciiSpec(spec); + LOG(("nsChannelClassifier::MarkEntryClassified[%s] %s", + errorName.get(), spec.get())); + } + + nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel); + if (!cachingChannel) { + return; + } + + nsCOMPtr<nsISupports> cacheToken; + cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); + if (!cacheToken) { + return; + } + + nsCOMPtr<nsICacheEntry> cacheEntry = + do_QueryInterface(cacheToken); + if (!cacheEntry) { + return; + } + + cacheEntry->SetMetaDataElement("necko:classified", + NS_SUCCEEDED(status) ? "1" : nullptr); +} + +bool +nsChannelClassifier::HasBeenClassified(nsIChannel *aChannel) +{ + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr<nsICachingChannel> cachingChannel = + do_QueryInterface(aChannel); + if (!cachingChannel) { + return false; + } + + // Only check the tag if we are loading from the cache without + // validation. + bool fromCache; + if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) { + return false; + } + + nsCOMPtr<nsISupports> cacheToken; + cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); + if (!cacheToken) { + return false; + } + + nsCOMPtr<nsICacheEntry> cacheEntry = + do_QueryInterface(cacheToken); + if (!cacheEntry) { + return false; + } + + nsXPIDLCString tag; + cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag)); + return tag.EqualsLiteral("1"); +} + +//static +bool +nsChannelClassifier::SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel) +{ + nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI(); + nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->GetLoadInfo(); + if (!channelLoadInfo || !docURI) { + return false; + } + + nsCOMPtr<nsIPrincipal> channelLoadingPrincipal = channelLoadInfo->LoadingPrincipal(); + if (!channelLoadingPrincipal) { + // TYPE_DOCUMENT loads will not have a channelLoadingPrincipal. But top level + // loads should not be blocked by Tracking Protection, so we will return + // false + return false; + } + nsCOMPtr<nsIURI> channelLoadingURI; + channelLoadingPrincipal->GetURI(getter_AddRefs(channelLoadingURI)); + if (!channelLoadingURI) { + return false; + } + bool equals = false; + nsresult rv = docURI->EqualsExceptRef(channelLoadingURI, &equals); + return NS_SUCCEEDED(rv) && equals; +} + +// static +nsresult +nsChannelClassifier::SetBlockedTrackingContent(nsIChannel *channel) +{ + // Can be called in EITHER the parent or child process. + nsCOMPtr<nsIParentChannel> parentChannel; + NS_QueryNotificationCallbacks(channel, parentChannel); + if (parentChannel) { + // This channel is a parent-process proxy for a child process request. The + // actual channel will be notified via the status passed to + // nsIRequest::Cancel and do this for us. + return NS_OK; + } + + nsresult rv; + nsCOMPtr<mozIDOMWindowProxy> win; + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, NS_OK); + rv = thirdPartyUtil->GetTopWindowForChannel(channel, getter_AddRefs(win)); + NS_ENSURE_SUCCESS(rv, NS_OK); + auto* pwin = nsPIDOMWindowOuter::From(win); + nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell(); + if (!docShell) { + return NS_OK; + } + nsCOMPtr<nsIDocument> doc = docShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_OK); + + // This event might come after the user has navigated to another page. + // To prevent showing the TrackingProtection UI on the wrong page, we need to + // check that the loading URI for the channel is the same as the URI currently + // loaded in the document. + if (!SameLoadingURI(doc, channel)) { + return NS_OK; + } + + // Notify nsIWebProgressListeners of this security event. + // Can be used to change the UI state. + nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv); + NS_ENSURE_SUCCESS(rv, NS_OK); + uint32_t state = 0; + nsCOMPtr<nsISecureBrowserUI> securityUI; + docShell->GetSecurityUI(getter_AddRefs(securityUI)); + if (!securityUI) { + return NS_OK; + } + doc->SetHasTrackingContentBlocked(true); + securityUI->GetState(&state); + state |= nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT; + eventSink->OnSecurityChange(nullptr, state); + + // Log a warning to the web console. + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + NS_ConvertUTF8toUTF16 spec(uri->GetSpecOrDefault()); + const char16_t* params[] = { spec.get() }; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Tracking Protection"), + doc, + nsContentUtils::eNECKO_PROPERTIES, + "TrackingUriBlocked", + params, ArrayLength(params)); + + return NS_OK; +} + +nsresult +nsChannelClassifier::IsTrackerWhitelisted() +{ + nsresult rv; + nsCOMPtr<nsIURIClassifier> uriClassifier = + do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString tables; + Preferences::GetCString("urlclassifier.trackingWhitelistTable", &tables); + + if (tables.IsEmpty()) { + LOG(("nsChannelClassifier[%p]:IsTrackerWhitelisted whitelist disabled", + this)); + return NS_ERROR_TRACKING_URI; + } + + nsCOMPtr<nsIHttpChannelInternal> chan = do_QueryInterface(mChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> topWinURI; + rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI)); + NS_ENSURE_SUCCESS(rv, rv); + if (!topWinURI) { + LOG(("nsChannelClassifier[%p]: No window URI", this)); + return NS_ERROR_TRACKING_URI; + } + + nsCOMPtr<nsIScriptSecurityManager> securityManager = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIPrincipal> chanPrincipal; + rv = securityManager->GetChannelURIPrincipal(mChannel, + getter_AddRefs(chanPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + + // Craft a whitelist URL like "toplevel.page/?resource=third.party.domain" + nsAutoCString pageHostname, resourceDomain; + rv = topWinURI->GetHost(pageHostname); + NS_ENSURE_SUCCESS(rv, rv); + rv = chanPrincipal->GetBaseDomain(resourceDomain); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString whitelistEntry = NS_LITERAL_CSTRING("http://") + + pageHostname + NS_LITERAL_CSTRING("/?resource=") + resourceDomain; + LOG(("nsChannelClassifier[%p]: Looking for %s in the whitelist", + this, whitelistEntry.get())); + + nsCOMPtr<nsIURI> whitelistURI; + rv = NS_NewURI(getter_AddRefs(whitelistURI), whitelistEntry); + NS_ENSURE_SUCCESS(rv, rv); + + // Check whether or not the tracker is in the entity whitelist + nsAutoCString results; + rv = uriClassifier->ClassifyLocalWithTables(whitelistURI, tables, results); + NS_ENSURE_SUCCESS(rv, rv); + if (!results.IsEmpty()) { + return NS_OK; // found it on the whitelist, must not be blocked + } + + LOG(("nsChannelClassifier[%p]: %s is not in the whitelist", + this, whitelistEntry.get())); + return NS_ERROR_TRACKING_URI; +} + +NS_IMETHODIMP +nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode) +{ + // Should only be called in the parent process. + MOZ_ASSERT(XRE_IsParentProcess()); + + if (aErrorCode == NS_ERROR_TRACKING_URI && + NS_SUCCEEDED(IsTrackerWhitelisted())) { + LOG(("nsChannelClassifier[%p]:OnClassifyComplete tracker found " + "in whitelist so we won't block it", this)); + aErrorCode = NS_OK; + } + + if (mSuspendedChannel) { + nsAutoCString errorName; + if (LOG_ENABLED()) { + GetErrorName(aErrorCode, errorName); + LOG(("nsChannelClassifier[%p]:OnClassifyComplete %s (suspended channel)", + this, errorName.get())); + } + MarkEntryClassified(aErrorCode); + + if (NS_FAILED(aErrorCode)) { + if (LOG_ENABLED()) { + nsCOMPtr<nsIURI> uri; + mChannel->GetURI(getter_AddRefs(uri)); + LOG(("nsChannelClassifier[%p]: cancelling channel %p for %s " + "with error code %s", this, mChannel.get(), + uri->GetSpecOrDefault().get(), errorName.get())); + } + + // Channel will be cancelled (page element blocked) due to tracking. + // Do update the security state of the document and fire a security + // change event. + if (aErrorCode == NS_ERROR_TRACKING_URI) { + SetBlockedTrackingContent(mChannel); + } + + mChannel->Cancel(aErrorCode); + } + LOG(("nsChannelClassifier[%p]: resuming channel %p from " + "OnClassifyComplete", this, mChannel.get())); + mChannel->Resume(); + } + + mChannel = nullptr; + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsChannelClassifier.h b/netwerk/base/nsChannelClassifier.h new file mode 100644 index 000000000..20575f3c1 --- /dev/null +++ b/netwerk/base/nsChannelClassifier.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 nsChannelClassifier_h__ +#define nsChannelClassifier_h__ + +#include "nsIURIClassifier.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +class nsIChannel; +class nsIHttpChannelInternal; +class nsIDocument; + +namespace mozilla { +namespace net { + +class nsChannelClassifier final : public nsIURIClassifierCallback +{ +public: + nsChannelClassifier(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIURICLASSIFIERCALLBACK + + // Calls nsIURIClassifier.Classify with the principal of the given channel, + // and cancels the channel on a bad verdict. + void Start(nsIChannel *aChannel); + // Whether or not tracking protection should be enabled on this channel. + nsresult ShouldEnableTrackingProtection(nsIChannel *aChannel, bool *result); + +private: + // True if the channel is on the allow list. + bool mIsAllowListed; + // True if the channel has been suspended. + bool mSuspendedChannel; + nsCOMPtr<nsIChannel> mChannel; + + ~nsChannelClassifier() {} + // Caches good classifications for the channel principal. + void MarkEntryClassified(nsresult status); + bool HasBeenClassified(nsIChannel *aChannel); + // Helper function so that we ensure we call ContinueBeginConnect once + // Start is called. Returns NS_OK if and only if we will get a callback + // from the classifier service. + nsresult StartInternal(); + // Helper function to check a tracking URI against the whitelist + nsresult IsTrackerWhitelisted(); + // Helper function to check a URI against the hostname whitelist + bool IsHostnameWhitelisted(nsIURI *aUri, const nsACString &aWhitelisted); + // Checks that the channel was loaded by the URI currently loaded in aDoc + static bool SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel); + +public: + // If we are blocking tracking content, update the corresponding flag in + // the respective docshell and call nsISecurityEventSink::onSecurityChange. + static nsresult SetBlockedTrackingContent(nsIChannel *channel); + static nsresult NotifyTrackingProtectionDisabled(nsIChannel *aChannel); +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/nsDNSPrefetch.cpp b/netwerk/base/nsDNSPrefetch.cpp new file mode 100644 index 000000000..e09315ed1 --- /dev/null +++ b/netwerk/base/nsDNSPrefetch.cpp @@ -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 "nsDNSPrefetch.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsThreadUtils.h" + +#include "nsIDNSListener.h" +#include "nsIDNSService.h" +#include "nsICancelable.h" +#include "nsIURI.h" + +static nsIDNSService *sDNSService = nullptr; + +nsresult +nsDNSPrefetch::Initialize(nsIDNSService *aDNSService) +{ + NS_IF_RELEASE(sDNSService); + sDNSService = aDNSService; + NS_IF_ADDREF(sDNSService); + return NS_OK; +} + +nsresult +nsDNSPrefetch::Shutdown() +{ + NS_IF_RELEASE(sDNSService); + return NS_OK; +} + +nsDNSPrefetch::nsDNSPrefetch(nsIURI *aURI, + nsIDNSListener *aListener, + bool storeTiming) + : mStoreTiming(storeTiming) + , mListener(do_GetWeakReference(aListener)) +{ + aURI->GetAsciiHost(mHostname); +} + +nsresult +nsDNSPrefetch::Prefetch(uint16_t 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<nsIThread> mainThread = do_GetMainThread(); + return sDNSService->AsyncResolve(mHostname, + flags | nsIDNSService::RESOLVE_SPECULATE, + this, mainThread, + getter_AddRefs(tmpOutstanding)); +} + +nsresult +nsDNSPrefetch::PrefetchLow(bool refreshDNS) +{ + return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW | + (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0)); +} + +nsresult +nsDNSPrefetch::PrefetchMedium(bool refreshDNS) +{ + return Prefetch(nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0)); +} + +nsresult +nsDNSPrefetch::PrefetchHigh(bool refreshDNS) +{ + return Prefetch(refreshDNS ? + nsIDNSService::RESOLVE_BYPASS_CACHE : 0); +} + + +NS_IMPL_ISUPPORTS(nsDNSPrefetch, nsIDNSListener) + +NS_IMETHODIMP +nsDNSPrefetch::OnLookupComplete(nsICancelable *request, + nsIDNSRecord *rec, + nsresult status) +{ + MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread."); + + 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 000000000..3ad6d4bf0 --- /dev/null +++ b/netwerk/base/nsDNSPrefetch.h @@ -0,0 +1,53 @@ +/* -*- 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/. */ + +#ifndef nsDNSPrefetch_h___ +#define nsDNSPrefetch_h___ + +#include "nsWeakReference.h" +#include "nsString.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Attributes.h" + +#include "nsIDNSListener.h" + +class nsIURI; +class nsIDNSService; + +class nsDNSPrefetch final : public nsIDNSListener +{ + ~nsDNSPrefetch() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + nsDNSPrefetch(nsIURI *aURI, nsIDNSListener *aListener, bool storeTiming); + 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(bool refreshDNS = false); + nsresult PrefetchMedium(bool refreshDNS = false); + nsresult PrefetchLow(bool refreshDNS = false); + +private: + nsCString mHostname; + bool mStoreTiming; + mozilla::TimeStamp mStartTimestamp; + mozilla::TimeStamp mEndTimestamp; + nsWeakPtr mListener; + + nsresult Prefetch(uint16_t flags); +}; + +#endif diff --git a/netwerk/base/nsDirectoryIndexStream.cpp b/netwerk/base/nsDirectoryIndexStream.cpp new file mode 100644 index 000000000..87a57fd57 --- /dev/null +++ b/netwerk/base/nsDirectoryIndexStream.cpp @@ -0,0 +1,359 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set sw=4 sts=4 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 "nsISimpleEnumerator.h" +#ifdef THREADSAFE_I18N +#include "nsCollationCID.h" +#include "nsICollation.h" +#include "nsILocale.h" +#include "nsILocaleService.h" +#endif +#include "nsIFile.h" +#include "nsURLHelper.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. +// When this is fixed, #define THREADSAFE_I18N to get this code working + +//#define THREADSAFE_I18N + +using namespace mozilla; +static LazyLogModule gLog("nsDirectoryIndexStream"); + +nsDirectoryIndexStream::nsDirectoryIndexStream() + : mOffset(0), mStatus(NS_OK), mPos(0) +{ + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: created", this)); +} + +static int compare(nsIFile* aElement1, nsIFile* aElement2, void* aData) +{ + 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; + NS_PRECONDITION(isDir, "not a directory"); + if (!isDir) + return NS_ERROR_ILLEGAL_VALUE; + + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + nsAutoCString path; + aDir->GetNativePath(path); + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: initialized on %s", + this, path.get())); + } + + // Sigh. We have to allocate on the heap because there are no + // assignment operators defined. + nsCOMPtr<nsISimpleEnumerator> 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? + + bool more; + nsCOMPtr<nsISupports> elem; + while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) { + rv = iter->GetNext(getter_AddRefs(elem)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> file = do_QueryInterface(elem); + if (file) + mArray.AppendObject(file); // addrefs + } + } + +#ifdef THREADSAFE_I18N + nsCOMPtr<nsILocaleService> ls = do_GetService(NS_LOCALESERVICE_CONTRACTID, + &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsILocale> locale; + rv = ls->GetApplicationLocale(getter_AddRefs(locale)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsICollationFactory> cf = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, + &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsICollation> coll; + rv = cf->CreateCollation(locale, getter_AddRefs(coll)); + if (NS_FAILED(rv)) return rv; + + mArray.Sort(compare, coll); +#else + mArray.Sort(compare, nullptr); +#endif + + mBuf.AppendLiteral("300: "); + nsAutoCString url; + rv = net_GetURLSpecFromFile(aDir, url); + if (NS_FAILED(rv)) return rv; + mBuf.Append(url); + mBuf.Append('\n'); + + 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::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)) { + nsAutoCString path; + current->GetNativePath(path); + MOZ_LOG(gLog, LogLevel::Debug, + ("nsDirectoryIndexStream[%p]: iterated %s", + this, path.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 000000000..5403c7af2 --- /dev/null +++ b/netwerk/base/nsDirectoryIndexStream.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#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; + nsresult mStatus; + + int32_t mPos; // 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** aStreamResult); + + // 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 000000000..6248be8f1 --- /dev/null +++ b/netwerk/base/nsDownloader.cpp @@ -0,0 +1,114 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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, nsISupports *ctxt) +{ + 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, + nsISupports *ctxt, + nsresult status) +{ + if (mSink) { + mSink->Close(); + mSink = nullptr; + } + + mObserver->OnDownloadComplete(this, request, ctxt, 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, nsISupports *ctxt, + 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 000000000..b5c22393d --- /dev/null +++ b/netwerk/base/nsDownloader.h @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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() : mLocationIsTemp(false) {} + +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; +}; + +#endif // nsDownloader_h__ diff --git a/netwerk/base/nsFileStreams.cpp b/netwerk/base/nsFileStreams.cpp new file mode 100644 index 000000000..2ddb7ae98 --- /dev/null +++ b/netwerk/base/nsFileStreams.cpp @@ -0,0 +1,1153 @@ +/* -*- 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 "ipc/IPCMessageUtils.h" + +#if defined(XP_UNIX) || defined(XP_BEOS) +#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 "nsFileStreams.h" +#include "nsIFile.h" +#include "nsReadLine.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/Unused.h" +#include "mozilla/FileUtils.h" +#include "nsNetCID.h" +#include "nsXULAppAPI.h" + +#define NS_NO_INPUT_BUFFERING 1 // see http://bugzilla.mozilla.org/show_bug.cgi?id=41067 + +typedef mozilla::ipc::FileDescriptor::PlatformHandleType FileHandleType; + +using namespace mozilla::ipc; +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +//////////////////////////////////////////////////////////////////////////////// +// nsFileStreamBase + +nsFileStreamBase::nsFileStreamBase() + : mFD(nullptr) + , mBehaviorFlags(0) + , mDeferredOpen(false) +{ +} + +nsFileStreamBase::~nsFileStreamBase() +{ + Close(); +} + +NS_IMPL_ISUPPORTS(nsFileStreamBase, + nsISeekableStream, + nsIFileMetadata) + +NS_IMETHODIMP +nsFileStreamBase::Seek(int32_t whence, int64_t offset) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mFD == nullptr) + return NS_BASE_STREAM_CLOSED; + + 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) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mFD == nullptr) + return NS_BASE_STREAM_CLOSED; + + 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 (mFD == nullptr) + return NS_BASE_STREAM_CLOSED; + +#if defined(XP_UNIX) || defined(XP_BEOS) + // Some system calls require an EOF offset. + int64_t offset; + rv = Tell(&offset); + if (NS_FAILED(rv)) return rv; +#endif + +#if defined(XP_UNIX) || defined(XP_BEOS) + 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); + + if (!mFD) { + return NS_BASE_STREAM_CLOSED; + } + + 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); + + if (!mFD) { + return NS_BASE_STREAM_CLOSED; + } + + 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(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!mFD) { + return NS_BASE_STREAM_CLOSED; + } + + *_retval = mFD; + return NS_OK; +} + +nsresult +nsFileStreamBase::Close() +{ + CleanUpOpen(); + + nsresult rv = NS_OK; + if (mFD) { + if (PR_Close(mFD) == PR_FAILURE) + rv = NS_BASE_STREAM_OSERROR; + mFD = nullptr; + } + return rv; +} + +nsresult +nsFileStreamBase::Available(uint64_t* aResult) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mFD) { + return NS_BASE_STREAM_CLOSED; + } + + // 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_ERROR_FILE_NOT_FOUND) { + // Don't warn if this is just a deferred file not found. + return rv; + } + + NS_ENSURE_SUCCESS(rv, rv); + + if (!mFD) { + *aResult = 0; + return NS_OK; + } + + 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(). + + // If this is ever implemented you might need to modify + // nsPartialFileInputStream::ReadSegments + + 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); + + if (mFD == nullptr) + return NS_BASE_STREAM_CLOSED; + + int32_t cnt = PR_Sync(mFD); + if (cnt == -1) { + return NS_ErrorAccordingToNSPR(); + } + return NS_OK; +} + +nsresult +nsFileStreamBase::Write(const char *buf, uint32_t count, uint32_t *result) +{ + nsresult rv = DoPendingOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mFD == nullptr) + return NS_BASE_STREAM_CLOSED; + + 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) +{ + NS_NOTREACHED("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 = do_QueryInterface(file); + NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED); + + mDeferredOpen = true; + 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; + mDeferredOpen = false; +} + +nsresult +nsFileStreamBase::DoOpen() +{ + NS_ASSERTION(!mFD, "Already have a file descriptor!"); + NS_ASSERTION(mOpenParams.localFile, "Must have a file to open"); + + PRFileDesc* fd; + nsresult rv; + +#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); + } + + CleanUpOpen(); + if (NS_FAILED(rv)) + return rv; + mFD = fd; + + return NS_OK; +} + +nsresult +nsFileStreamBase::DoPendingOpen() +{ + if (!mDeferredOpen) { + return NS_OK; + } + + return DoOpen(); +} + +//////////////////////////////////////////////////////////////////////////////// +// 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_END_INHERITING(nsFileStreamBase) + +NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream, + nsIInputStream, + nsIFileInputStream, + nsISeekableStream, + nsILineInputStream) + +nsresult +nsFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + nsFileInputStream* stream = new nsFileInputStream(); + if (stream == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(stream); + nsresult rv = stream->QueryInterface(aIID, aResult); + NS_RELEASE(stream); + return rv; +} + +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; + + rv = MaybeOpen(aFile, aIOFlags, aPerm, + mBehaviorFlags & nsIFileInputStream::DEFER_OPEN); + + if (NS_FAILED(rv)) return rv; + + // if defer open is set, do not remove the file here. + // remove the file while Close() is called. + if ((mBehaviorFlags & DELETE_ON_CLOSE) && + !(mBehaviorFlags & nsIFileInputStream::DEFER_OPEN)) { + // POSIX compatible filesystems allow a file to be unlinked while a + // file descriptor is still referencing the file. since we've already + // opened the file descriptor, we'll try to remove the file. if that + // fails, then we'll just remember the nsIFile and remove it after we + // close the file descriptor. + rv = aFile->Remove(false); + if (NS_SUCCEEDED(rv)) { + // No need to remove it later. Clear the flag. + mBehaviorFlags &= ~DELETE_ON_CLOSE; + } + } + + return NS_OK; +} + +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(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = aBehaviorFlags; + + mFile = aFile; + mIOFlags = aIOFlags; + mPerm = aPerm; + + return Open(aFile, aIOFlags, aPerm); +} + +NS_IMETHODIMP +nsFileInputStream::Close() +{ + // 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); + } + + // null out mLineBuffer in case Close() is called again after failing + mLineBuffer = nullptr; + nsresult rv = nsFileStreamBase::Close(); + if (NS_FAILED(rv)) return rv; + if (mFile && (mBehaviorFlags & DELETE_ON_CLOSE)) { + rv = mFile->Remove(false); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to delete file"); + // If we don't need to save the file for reopening, free it up + if (!(mBehaviorFlags & REOPEN_ON_REWIND)) { + mFile = nullptr; + } + } + return rv; +} + +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; + } + + NS_ENSURE_SUCCESS(rv, 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 = new 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(); + NS_ENSURE_SUCCESS(rv, rv); + + if (aClearBuf) { + mLineBuffer = nullptr; + } + if (!mFD) { + 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; + } + } 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); +} + +void +nsFileInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + FileInputStreamParams params; + + if (NS_SUCCEEDED(DoPendingOpen()) && mFD) { + FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD)); + NS_ASSERTION(fd, "This should never be null!"); + + DebugOnly<FileDescriptor*> dbgFD = aFileDescriptors.AppendElement(fd); + NS_ASSERTION(dbgFD->IsValid(), "Sending an invalid file descriptor!"); + + params.fileDescriptorIndex() = aFileDescriptors.Length() - 1; + + 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.fileDescriptorIndex() = UINT32_MAX; + } + + 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, + const FileDescriptorArray& aFileDescriptors) +{ + NS_ASSERTION(!mFD, "Already have a file descriptor?!"); + NS_ASSERTION(!mDeferredOpen, "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(); + + uint32_t fileDescriptorIndex = params.fileDescriptorIndex(); + + FileDescriptor fd; + if (fileDescriptorIndex < aFileDescriptors.Length()) { + fd = aFileDescriptors[fileDescriptorIndex]; + NS_WARNING_ASSERTION(fd.IsValid(), + "Received an invalid file descriptor!"); + } else { + NS_WARNING("Received a bad file descriptor index!"); + } + + 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; + } + + 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; +} + +Maybe<uint64_t> +nsFileInputStream::ExpectedSerializedLength() +{ + return Nothing(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsPartialFileInputStream + +NS_IMPL_ADDREF_INHERITED(nsPartialFileInputStream, nsFileStreamBase) +NS_IMPL_RELEASE_INHERITED(nsPartialFileInputStream, nsFileStreamBase) + +NS_IMPL_CLASSINFO(nsPartialFileInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_PARTIALLOCALFILEINPUTSTREAM_CID) + +// Don't forward to nsFileInputStream as we don't want to QI to +// nsIFileInputStream +NS_INTERFACE_MAP_BEGIN(nsPartialFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIPartialFileInputStream) + NS_INTERFACE_MAP_ENTRY(nsILineInputStream) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) + NS_IMPL_QUERY_CLASSINFO(nsPartialFileInputStream) +NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase) + +NS_IMPL_CI_INTERFACE_GETTER(nsPartialFileInputStream, + nsIInputStream, + nsIPartialFileInputStream, + nsISeekableStream, + nsILineInputStream) + +nsresult +nsPartialFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + nsPartialFileInputStream* stream = new nsPartialFileInputStream(); + + NS_ADDREF(stream); + nsresult rv = stream->QueryInterface(aIID, aResult); + NS_RELEASE(stream); + return rv; +} + +NS_IMETHODIMP +nsPartialFileInputStream::Init(nsIFile* aFile, uint64_t aStart, + uint64_t aLength, int32_t aIOFlags, + int32_t aPerm, int32_t aBehaviorFlags) +{ + mStart = aStart; + mLength = aLength; + mPosition = 0; + + nsresult rv = nsFileInputStream::Init(aFile, aIOFlags, aPerm, + aBehaviorFlags); + + // aFile is a partial file, it must exist. + NS_ENSURE_SUCCESS(rv, rv); + + mDeferredSeek = true; + + return rv; +} + +NS_IMETHODIMP +nsPartialFileInputStream::Tell(int64_t *aResult) +{ + int64_t tell = 0; + + nsresult rv = DoPendingSeek(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsFileInputStream::Tell(&tell); + NS_ENSURE_SUCCESS(rv, rv); + + + *aResult = tell - mStart; + return rv; +} + +NS_IMETHODIMP +nsPartialFileInputStream::Available(uint64_t* aResult) +{ + uint64_t available = 0; + + nsresult rv = DoPendingSeek(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsFileInputStream::Available(&available); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = TruncateSize(available); + return rv; +} + +NS_IMETHODIMP +nsPartialFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) +{ + nsresult rv = DoPendingSeek(); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t readsize = (uint32_t) TruncateSize(aCount); + if (readsize == 0 && mBehaviorFlags & CLOSE_ON_EOF) { + Close(); + *aResult = 0; + return NS_OK; + } + + rv = nsFileInputStream::Read(aBuf, readsize, aResult); + NS_ENSURE_SUCCESS(rv, rv); + + mPosition += readsize; + return rv; +} + +NS_IMETHODIMP +nsPartialFileInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + nsresult rv = DoPendingSeek(); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t offset; + switch (aWhence) { + case NS_SEEK_SET: + offset = mStart + aOffset; + break; + case NS_SEEK_CUR: + offset = mStart + mPosition + aOffset; + break; + case NS_SEEK_END: + offset = mStart + mLength + aOffset; + break; + default: + return NS_ERROR_ILLEGAL_VALUE; + } + + if (offset < (int64_t)mStart || offset > (int64_t)(mStart + mLength)) { + return NS_ERROR_INVALID_ARG; + } + + rv = nsFileInputStream::Seek(NS_SEEK_SET, offset); + NS_ENSURE_SUCCESS(rv, rv); + + mPosition = offset - mStart; + return rv; +} + +void +nsPartialFileInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + // Serialize the base class first. + InputStreamParams fileParams; + nsFileInputStream::Serialize(fileParams, aFileDescriptors); + + PartialFileInputStreamParams params; + + params.fileStreamParams() = fileParams.get_FileInputStreamParams(); + params.begin() = mStart; + params.length() = mLength; + + aParams = params; +} + +bool +nsPartialFileInputStream::Deserialize( + const InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) +{ + NS_ASSERTION(!mFD, "Already have a file descriptor?!"); + NS_ASSERTION(!mStart, "Already have a start?!"); + NS_ASSERTION(!mLength, "Already have a length?!"); + NS_ASSERTION(!mPosition, "Already have a position?!"); + + if (aParams.type() != InputStreamParams::TPartialFileInputStreamParams) { + NS_WARNING("Received unknown parameters from the other process!"); + return false; + } + + const PartialFileInputStreamParams& params = + aParams.get_PartialFileInputStreamParams(); + + // Deserialize the base class first. + InputStreamParams fileParams(params.fileStreamParams()); + if (!nsFileInputStream::Deserialize(fileParams, aFileDescriptors)) { + NS_WARNING("Base class deserialize failed!"); + return false; + } + + NS_ASSERTION(mFD, "Must have a file descriptor now!"); + + mStart = params.begin(); + mLength = params.length(); + mPosition = 0; + + if (!mStart) { + return true; + } + + // XXX This is so broken. Main thread IO alert. + return NS_SUCCEEDED(nsFileInputStream::Seek(NS_SEEK_SET, mStart)); +} + +Maybe<uint64_t> +nsPartialFileInputStream::ExpectedSerializedLength() +{ + return Some(mLength); +} + + +nsresult +nsPartialFileInputStream::DoPendingSeek() +{ + if (!mDeferredSeek) { + return NS_OK; + } + + mDeferredSeek = false; + + // This is the first time to open the file, don't clear mLinebuffer. + // mLineBuffer might be already initialized by ReadLine(). + return nsFileInputStream::SeekInternal(NS_SEEK_SET, mStart, false); +} +//////////////////////////////////////////////////////////////////////////////// +// nsFileOutputStream + +NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream, + nsFileStreamBase, + nsIOutputStream, + nsIFileOutputStream) + +nsresult +nsFileOutputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + nsFileOutputStream* stream = new nsFileOutputStream(); + if (stream == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(stream); + nsresult rv = stream->QueryInterface(aIID, aResult); + NS_RELEASE(stream); + return rv; +} + +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(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = behaviorFlags; + + 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); +} + +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)) { + tempResult->SetFollowLinks(true); + + // XP_UNIX ignores SetFollowLinks(), so we have to normalize. + if (mTargetFileExists) { + tempResult->Normalize(); + } + } + + if (NS_SUCCEEDED(rv) && mTargetFileExists) { + 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(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFileStream + +NS_IMPL_ISUPPORTS_INHERITED(nsFileStream, + nsFileStreamBase, + nsIInputStream, + nsIOutputStream, + nsIFileStream) + +NS_IMETHODIMP +nsFileStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm, + int32_t behaviorFlags) +{ + NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED); + NS_ENSURE_TRUE(!mDeferredOpen, NS_ERROR_ALREADY_INITIALIZED); + + mBehaviorFlags = behaviorFlags; + + if (ioFlags == -1) + ioFlags = PR_RDWR; + if (perm <= 0) + perm = 0; + + return MaybeOpen(file, ioFlags, perm, + mBehaviorFlags & nsIFileStream::DEFER_OPEN); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/nsFileStreams.h b/netwerk/base/nsFileStreams.h new file mode 100644 index 000000000..22ef91770 --- /dev/null +++ b/netwerk/base/nsFileStreams.h @@ -0,0 +1,333 @@ +// /* -*- 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/. */ + +#ifndef nsFileStreams_h__ +#define nsFileStreams_h__ + +#include "nsAutoPtr.h" +#include "nsIFileStreams.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsISeekableStream.h" +#include "nsILineInputStream.h" +#include "nsCOMPtr.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsReadLine.h" +#include <algorithm> + + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileStreamBase : public nsISeekableStream, + public nsIFileMetadata +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIFILEMETADATA + + nsFileStreamBase(); + +protected: + virtual ~nsFileStreamBase(); + + nsresult Close(); + nsresult Available(uint64_t* _retval); + nsresult Read(char* aBuf, uint32_t aCount, uint32_t* _retval); + nsresult ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval); + nsresult IsNonBlocking(bool* _retval); + nsresult Flush(); + nsresult Write(const char* aBuf, uint32_t aCount, uint32_t* _retval); + nsresult WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval); + nsresult WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval); + + PRFileDesc* mFD; + + /** + * Flags describing our behavior. See the IDL file for possible values. + */ + int32_t mBehaviorFlags; + + /** + * Whether we have a pending open (see DEFER_OPEN in the IDL file). + */ + bool mDeferredOpen; + + struct OpenParams { + nsCOMPtr<nsIFile> localFile; + int32_t ioFlags; + int32_t perm; + }; + + /** + * Data we need to do an open. + */ + OpenParams mOpenParams; + + /** + * 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(); + + /** + * If there is a pending open, do it now. It's important for this to be + * inline since we do it in almost every stream API call. + */ + inline nsresult DoPendingOpen(); +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileInputStream : public nsFileStreamBase, + public nsIFileInputStream, + public nsILineInputStream, + public nsIIPCSerializableInputStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILEINPUTSTREAM + NS_DECL_NSILINEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + + NS_IMETHOD Close() override; + NS_IMETHOD Tell(int64_t *aResult) override; + NS_IMETHOD Available(uint64_t* _retval) 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), mIOFlags(0), mPerm(0), mCachedPosition(0) + {} + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + virtual ~nsFileInputStream() + { + Close(); + } + + nsresult SeekInternal(int32_t aWhence, int64_t aOffset, bool aClearBuf=true); + + nsAutoPtr<nsLineBuffer<char> > mLineBuffer; + + /** + * The file being opened. + */ + nsCOMPtr<nsIFile> mFile; + /** + * The IO flags passed to Init() for the file open. + */ + int32_t mIOFlags; + /** + * The permissions passed to Init() for the file open. + */ + int32_t mPerm; + + /** + * Cached position for Tell for automatically reopening streams. + */ + int64_t mCachedPosition; + +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); +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsPartialFileInputStream : public nsFileInputStream, + public nsIPartialFileInputStream +{ +public: + using nsFileInputStream::Init; + using nsFileInputStream::Read; + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPARTIALFILEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + + nsPartialFileInputStream() + : mStart(0), mLength(0), mPosition(0), mDeferredSeek(false) + { } + + NS_IMETHOD Tell(int64_t *aResult) override; + NS_IMETHOD Available(uint64_t *aResult) override; + NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* aResult) override; + NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override; + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + ~nsPartialFileInputStream() + { } + + inline nsresult DoPendingSeek(); + +private: + uint64_t TruncateSize(uint64_t aSize) { + return std::min<uint64_t>(mLength - mPosition, aSize); + } + + uint64_t mStart; + uint64_t mLength; + uint64_t mPosition; + bool mDeferredSeek; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsFileOutputStream : public nsFileStreamBase, + public nsIFileOutputStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILEOUTPUTSTREAM + NS_FORWARD_NSIOUTPUTSTREAM(nsFileStreamBase::) + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + virtual ~nsFileOutputStream() + { + Close(); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +/** + * 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() : + mTargetFileExists(true), + mWriteResult(NS_OK) {} + + 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() + { + Close(); + } + + nsCOMPtr<nsIFile> mTargetFile; + nsCOMPtr<nsIFile> mTempFile; + + bool mTargetFileExists; + nsresult mWriteResult; // 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 nsFileStream : public nsFileStreamBase, + public nsIInputStream, + public nsIOutputStream, + public nsIFileStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILESTREAM + 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 ~nsFileStream() + { + Close(); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +#endif // nsFileStreams_h__ diff --git a/netwerk/base/nsIApplicationCache.idl b/netwerk/base/nsIApplicationCache.idl new file mode 100644 index 000000000..9922feb59 --- /dev/null +++ b/netwerk/base/nsIApplicationCache.idl @@ -0,0 +1,205 @@ +/* -*- 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 nsIArray; +interface nsIFile; +interface nsIURI; + +/** + * Application caches can store a set of namespace entries that affect + * loads from the application cache. If a load from the cache fails + * to match an exact cache entry, namespaces entries will be searched + * for a substring match, and should be applied appropriately. + */ +[scriptable, uuid(96e4c264-2065-4ce9-93bb-43734c62c4eb)] +interface nsIApplicationCacheNamespace : nsISupports +{ + /** + * Items matching this namespace can be fetched from the network + * when loading from this cache. The "data" attribute is unused. + */ + const unsigned long NAMESPACE_BYPASS = 1 << 0; + + /** + * Items matching this namespace can be fetched from the network + * when loading from this cache. If the load fails, the cache entry + * specified by the "data" attribute should be loaded instead. + */ + const unsigned long NAMESPACE_FALLBACK = 1 << 1; + + /** + * Items matching this namespace should be cached + * opportunistically. Successful toplevel loads of documents + * in this namespace should be placed in the application cache. + * Namespaces specifying NAMESPACE_OPPORTUNISTIC may also specify + * NAMESPACE_FALLBACK to supply a fallback entry. + */ + const unsigned long NAMESPACE_OPPORTUNISTIC = 1 << 2; + + /** + * Initialize the namespace. + */ + void init(in unsigned long itemType, + in ACString namespaceSpec, + in ACString data); + + /** + * The namespace type. + */ + readonly attribute unsigned long itemType; + + /** + * The prefix of this namespace. This should be the asciiSpec of the + * URI prefix. + */ + readonly attribute ACString namespaceSpec; + + /** + * Data associated with this namespace, such as a fallback. URI data should + * use the asciiSpec of the URI. + */ + readonly attribute ACString data; +}; + +/** + * Application caches store resources for offline use. Each + * application cache has a unique client ID for use with + * nsICacheService::openSession() to access the cache's entries. + * + * Each entry in the application cache can be marked with a set of + * types, as discussed in the WHAT-WG offline applications + * specification. + * + * All application caches with the same group ID belong to a cache + * group. Each group has one "active" cache that will service future + * loads. Inactive caches will be removed from the cache when they are + * no longer referenced. + */ +[scriptable, uuid(06568DAE-C374-4383-A122-0CC96C7177F2)] +interface nsIApplicationCache : nsISupports +{ + /** + * Init this application cache instance to just hold the group ID and + * the client ID to work just as a handle to the real cache. Used on + * content process to simplify the application cache code. + */ + void initAsHandle(in ACString groupId, in ACString clientId); + + /** + * Entries in an application cache can be marked as one or more of + * the following types. + */ + + /* This item is the application manifest. */ + const unsigned long ITEM_MANIFEST = 1 << 0; + + /* This item was explicitly listed in the application manifest. */ + const unsigned long ITEM_EXPLICIT = 1 << 1; + + /* This item was navigated in a toplevel browsing context, and + * named this cache's group as its manifest. */ + const unsigned long ITEM_IMPLICIT = 1 << 2; + + /* This item was added by the dynamic scripting API */ + const unsigned long ITEM_DYNAMIC = 1 << 3; + + /* This item was listed in the application manifest, but named a + * different cache group as its manifest. */ + const unsigned long ITEM_FOREIGN = 1 << 4; + + /* This item was listed as a fallback entry. */ + const unsigned long ITEM_FALLBACK = 1 << 5; + + /* This item matched an opportunistic cache namespace and was + * cached accordingly. */ + const unsigned long ITEM_OPPORTUNISTIC = 1 << 6; + + /** + * URI of the manfiest specifying this application cache. + **/ + readonly attribute nsIURI manifestURI; + + /** + * The group ID for this cache group. It is an internally generated string + * and cannot be used as manifest URL spec. + **/ + readonly attribute ACString groupID; + + /** + * The client ID for this application cache. Clients can open a + * session with nsICacheService::createSession() using this client + * ID and a storage policy of STORE_OFFLINE to access this cache. + */ + readonly attribute ACString clientID; + + /** + * TRUE if the cache is the active cache for this group. + */ + readonly attribute boolean active; + + /** + * The disk usage of the application cache, in bytes. + */ + readonly attribute unsigned long usage; + + /** + * Makes this cache the active application cache for this group. + * Future loads associated with this group will come from this + * cache. Other caches from this cache group will be deactivated. + */ + void activate(); + + /** + * Discard this application cache. Removes all cached resources + * for this cache. If this is the active application cache for the + * group, the group will be removed. + */ + void discard(); + + /** + * Adds item types to a given entry. + */ + void markEntry(in ACString key, in unsigned long typeBits); + + /** + * Removes types from a given entry. If the resulting entry has + * no types left, the entry is removed. + */ + void unmarkEntry(in ACString key, in unsigned long typeBits); + + /** + * Gets the types for a given entry. + */ + unsigned long getTypes(in ACString key); + + /** + * Returns any entries in the application cache whose type matches + * one or more of the bits in typeBits. + */ + void gatherEntries(in uint32_t typeBits, + out unsigned long count, + [array, size_is(count)] out string keys); + + /** + * Add a set of namespace entries to the application cache. + * @param namespaces + * An nsIArray of nsIApplicationCacheNamespace entries. + */ + void addNamespaces(in nsIArray namespaces); + + /** + * Get the most specific namespace matching a given key. + */ + nsIApplicationCacheNamespace getMatchingNamespace(in ACString key); + + /** + * If set, this offline cache is placed in a different directory + * than the current application profile. + */ + readonly attribute nsIFile profileDirectory; +}; diff --git a/netwerk/base/nsIApplicationCacheChannel.idl b/netwerk/base/nsIApplicationCacheChannel.idl new file mode 100644 index 000000000..410e2946d --- /dev/null +++ b/netwerk/base/nsIApplicationCacheChannel.idl @@ -0,0 +1,57 @@ +/* -*- 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 "nsIApplicationCacheContainer.idl" + +/** + * Interface implemented by channels that support application caches. + */ +[scriptable, uuid(6FA816B1-6D5F-4380-9704-054D0908CFA3)] +interface nsIApplicationCacheChannel : nsIApplicationCacheContainer +{ + /** + * TRUE when the resource came from the application cache. This + * might be false even there is assigned an application cache + * e.g. in case of fallback of load of an entry matching bypass + * namespace. + */ + readonly attribute boolean loadedFromApplicationCache; + + /** + * When true, the channel will ask its notification callbacks for + * an application cache if one is not explicitly provided. Default + * value is true. + * + * NS_ERROR_ALREADY_OPENED will be thrown if set after AsyncOpen() + * is called. + */ + attribute boolean inheritApplicationCache; + + /** + * When true, the channel will choose an application cache if one + * was not explicitly provided and none is available from the + * notification callbacks. Default value is false. + * + * This attribute will not be transferred through a redirect. + * + * NS_ERROR_ALREADY_OPENED will be thrown if set after AsyncOpen() + * is called. + */ + attribute boolean chooseApplicationCache; + + /** + * A shortcut method to mark the cache item of this channel as 'foreign'. + * See the 'cache selection algorithm' and CACHE_SELECTION_RELOAD + * action handling in nsContentSink. + */ + void markOfflineCacheEntryAsForeign(); + + /** + * Set offline application cache object to instruct the channel + * to cache for offline use using this application cache. + */ + attribute nsIApplicationCache applicationCacheForWrite; +}; diff --git a/netwerk/base/nsIApplicationCacheContainer.idl b/netwerk/base/nsIApplicationCacheContainer.idl new file mode 100644 index 000000000..af0b74cbe --- /dev/null +++ b/netwerk/base/nsIApplicationCacheContainer.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" + +interface nsIApplicationCache; + +/** + * Interface used by objects that can be associated with an + * application cache. + */ +[scriptable, uuid(bbb80700-1f7f-4258-aff4-1743cc5a7d23)] +interface nsIApplicationCacheContainer : nsISupports +{ + attribute nsIApplicationCache applicationCache; +}; diff --git a/netwerk/base/nsIApplicationCacheService.idl b/netwerk/base/nsIApplicationCacheService.idl new file mode 100644 index 000000000..9b2b16955 --- /dev/null +++ b/netwerk/base/nsIApplicationCacheService.idl @@ -0,0 +1,110 @@ +/* -*- 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 nsIApplicationCache; +interface nsIFile; +interface nsIURI; +interface nsILoadContextInfo; + +/** + * The application cache service manages the set of application cache + * groups. + */ +[scriptable, uuid(b8b6546c-6cec-4bda-82df-08e006a97b56)] +interface nsIApplicationCacheService : nsISupports +{ + /** + * Create group string identifying cache group according the manifest + * URL and the given principal. + */ + ACString buildGroupIDForInfo(in nsIURI aManifestURL, + in nsILoadContextInfo aLoadContextInfo); + ACString buildGroupIDForSuffix(in nsIURI aManifestURL, + in ACString aOriginSuffix); + + /** + * Create a new, empty application cache for the given cache + * group. + */ + nsIApplicationCache createApplicationCache(in ACString group); + + /** + * Create a new, empty application cache for the given cache + * group residing in a custom directory with a custom quota. + * + * @param group + * URL of the manifest + * @param directory + * Actually a reference to a profile directory where to + * create the OfflineCache sub-dir. + * @param quota + * Optional override of the default quota. + */ + nsIApplicationCache createCustomApplicationCache(in ACString group, + in nsIFile profileDir, + in int32_t quota); + + /** + * Get an application cache object for the given client ID. + */ + nsIApplicationCache getApplicationCache(in ACString clientID); + + /** + * Get the currently active cache object for a cache group. + */ + nsIApplicationCache getActiveCache(in ACString group); + + /** + * Deactivate the currently-active cache object for a cache group. + */ + void deactivateGroup(in ACString group); + + /** + * Evict offline cache entries, either all of them or those belonging + * to the given origin. + */ + void evict(in nsILoadContextInfo aLoadContextInfo); + + /** + * Delete caches whom origin attributes matches the given pattern. + */ + void evictMatchingOriginAttributes(in AString aPattern); + + /** + * Try to find the best application cache to serve a resource. + */ + nsIApplicationCache chooseApplicationCache(in ACString key, + [optional] in nsILoadContextInfo aLoadContextInfo); + + /** + * Flags the key as being opportunistically cached. + * + * This method should also propagate the entry to other + * application caches with the same opportunistic namespace, but + * this is not currently implemented. + * + * @param cache + * The cache in which the entry is cached now. + * @param key + * The cache entry key. + */ + void cacheOpportunistically(in nsIApplicationCache cache, in ACString key); + + /** + * Get the list of application cache groups. + */ + void getGroups([optional] out unsigned long count, + [array, size_is(count), retval] out string groupIDs); + + /** + * Get the list of application cache groups in the order of + * activating time. + */ + void getGroupsTimeOrdered([optional] out unsigned long count, + [array, size_is(count), retval] out string groupIDs); +}; diff --git a/netwerk/base/nsIArrayBufferInputStream.idl b/netwerk/base/nsIArrayBufferInputStream.idl new file mode 100644 index 000000000..430f63b2e --- /dev/null +++ b/netwerk/base/nsIArrayBufferInputStream.idl @@ -0,0 +1,26 @@ +/* -*- 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" + +/** + * nsIArrayBufferInputStream + * + * Provides scriptable methods for initializing a nsIInputStream + * implementation with an ArrayBuffer. + */ +[scriptable, 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 + */ + [implicit_jscontext] + void setData(in jsval buffer, in unsigned long byteOffset, in unsigned long byteLen); +}; diff --git a/netwerk/base/nsIAsyncStreamCopier.idl b/netwerk/base/nsIAsyncStreamCopier.idl new file mode 100644 index 000000000..633fe72b6 --- /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 000000000..7de793f51 --- /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 000000000..8c81a142f --- /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 000000000..484d59a8c --- /dev/null +++ b/netwerk/base/nsIAuthInformation.idl @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 000000000..8a446cb21 --- /dev/null +++ b/netwerk/base/nsIAuthModule.idl @@ -0,0 +1,145 @@ +/* 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 string aServiceName, + in unsigned long aServiceFlags, + in wstring aDomain, + in wstring aUsername, + in wstring 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++ +/** + * nsIAuthModule implementations are registered under the following contract + * ID prefix: + */ +#define NS_AUTH_MODULE_CONTRACTID_PREFIX \ + "@mozilla.org/network/auth-module;1?name=" +%} diff --git a/netwerk/base/nsIAuthPrompt.idl b/netwerk/base/nsIAuthPrompt.idl new file mode 100644 index 000000000..bb9f88f60 --- /dev/null +++ b/netwerk/base/nsIAuthPrompt.idl @@ -0,0 +1,80 @@ +/* -*- 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. + * 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 + * @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 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); +}; diff --git a/netwerk/base/nsIAuthPrompt2.idl b/netwerk/base/nsIAuthPrompt2.idl new file mode 100644 index 000000000..23c27c3d1 --- /dev/null +++ b/netwerk/base/nsIAuthPrompt2.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" + +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. + */ + 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 000000000..e763d4714 --- /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 000000000..bf9f2f2ac --- /dev/null +++ b/netwerk/base/nsIAuthPromptCallback.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" + +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 000000000..e8ff122ec --- /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 000000000..df560d02f --- /dev/null +++ b/netwerk/base/nsIBackgroundFileSaver.idl @@ -0,0 +1,182 @@ +/* -*- 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 nsIArray of nsIX509CertList, representing a chain of X.509 signatures on + * 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 nsIArray 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/nsIBrowserSearchService.idl b/netwerk/base/nsIBrowserSearchService.idl new file mode 100644 index 000000000..045973e0c --- /dev/null +++ b/netwerk/base/nsIBrowserSearchService.idl @@ -0,0 +1,530 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIInputStream; + +[scriptable, uuid(5799251f-5b55-4df7-a9e7-0c27812c469a)] +interface nsISearchSubmission : nsISupports +{ + /** + * The POST data associated with a search submission, wrapped in a MIME + * input stream. May be null. + */ + readonly attribute nsIInputStream postData; + + /** + * The URI to submit a search to. + */ + readonly attribute nsIURI uri; +}; + +[scriptable, uuid(620bd920-0491-48c8-99a8-d6047e64802d)] +interface nsISearchEngine : nsISupports +{ + /** + * Gets a nsISearchSubmission object that contains information about what to + * send to the search engine, including the URI and postData, if applicable. + * + * @param data + * Data to add to the submission object. + * i.e. the search terms. + * + * @param responseType [optional] + * The MIME type that we'd like to receive in response + * to this submission. If null, will default to "text/html". + * + * @param purpose [optional] + * A string meant to indicate the context of the search request. This + * allows the search service to provide a different nsISearchSubmission + * depending on e.g. where the search is triggered in the UI. + * + * @returns A nsISearchSubmission object that contains information about what + * to send to the search engine. If no submission can be + * obtained for the given responseType, returns null. + */ + nsISearchSubmission getSubmission(in AString data, + [optional] in AString responseType, + [optional] in AString purpose); + + /** + * Adds a parameter to the search engine's submission data. This should only + * be called on engines created via addEngineWithDetails. + * + * @param name + * The parameter's name. Must not be null. + * + * @param value + * The value to pass. If value is "{searchTerms}", it will be + * substituted with the user-entered data when retrieving the + * submission. Must not be null. + * + * @param responseType + * Since an engine can have several different request URLs, + * differentiated by response types, this parameter selects + * a request to add parameters to. If null, will default + * to "text/html" + * + * @throws NS_ERROR_FAILURE if the search engine is read-only. + * @throws NS_ERROR_INVALID_ARG if name or value are null. + */ + void addParam(in AString name, in AString value, in AString responseType); + + /** + * Determines whether the engine can return responses in the given + * MIME type. Returns true if the engine spec has a URL with the + * given responseType, false otherwise. + * + * @param responseType + * The MIME type to check for + */ + boolean supportsResponseType(in AString responseType); + + /** + * Returns a string with the URL to an engine's icon matching both width and + * height. Returns null if icon with specified dimensions is not found. + * + * @param width + * Width of the requested icon. + * @param height + * Height of the requested icon. + */ + AString getIconURLBySize(in long width, in long height); + + /** + * Gets an array of all available icons. Each entry is an object with + * width, height and url properties. width and height are numeric and + * represent the icon's dimensions. url is a string with the URL for + * the icon. + */ + jsval getIcons(); + + /** + * Opens a speculative connection to the engine's search URI + * (and suggest URI, if different) to reduce request latency + * + * @param options + * An object that must contain the following fields: + * {window} the content window for the window performing the search + * + * @throws NS_ERROR_INVALID_ARG if options is omitted or lacks required + * elemeents + */ + void speculativeConnect(in jsval options); + + /** + * An optional shortcut alias for the engine. + * When non-null, this is a unique identifier. + */ + attribute AString alias; + + /** + * A text description describing the engine. + */ + readonly attribute AString description; + + /** + * Whether the engine should be hidden from the user. + */ + attribute boolean hidden; + + /** + * A nsIURI corresponding to the engine's icon, stored locally. May be null. + */ + readonly attribute nsIURI iconURI; + + /** + * The display name of the search engine. This is a unique identifier. + */ + readonly attribute AString name; + + /** + * A URL string pointing to the engine's search form. + */ + readonly attribute AString searchForm; + + /** + * An optional unique identifier for this search engine within the context of + * the distribution, as provided by the distributing entity. + */ + readonly attribute AString identifier; + + /** + * Gets a string representing the hostname from which search results for a + * given responseType are returned, minus the leading "www." (if present). + * This can be specified as an url attribute in the engine description file, + * but will default to host from the <Url>'s template otherwise. + * + * @param responseType [optional] + * The MIME type to get resultDomain for. Defaults to "text/html". + * + * @return the resultDomain for the given responseType. + */ + AString getResultDomain([optional] in AString responseType); +}; + +[scriptable, uuid(0dc93e51-a7bf-4a16-862d-4b3469ff6206)] +interface nsISearchParseSubmissionResult : nsISupports +{ + /** + * The search engine associated with the URL passed in to + * nsISearchEngine::parseSubmissionURL, or null if the URL does not represent + * a search submission. + */ + readonly attribute nsISearchEngine engine; + + /** + * String containing the sought terms. This can be an empty string in case no + * terms were specified or the URL does not represent a search submission. + */ + readonly attribute AString terms; + + /** + * The offset of the string |terms| in the URL passed in to + * nsISearchEngine::parseSubmissionURL, or -1 if the URL does not represent + * a search submission. + */ + readonly attribute long termsOffset; + + /** + * The length of the |terms| in the original encoding of the URL passed in to + * nsISearchEngine::parseSubmissionURL. If the search term in the original + * URL is encoded then this will be bigger than |terms.length|. + */ + readonly attribute long termsLength; +}; + +[scriptable, uuid(9fc39136-f08b-46d3-b232-96f4b7b0e235)] +interface nsISearchInstallCallback : nsISupports +{ + const unsigned long ERROR_UNKNOWN_FAILURE = 0x1; + const unsigned long ERROR_DUPLICATE_ENGINE = 0x2; + + /** + * Called to indicate that the engine addition process succeeded. + * + * @param engine + * The nsISearchEngine object that was added (will not be null). + */ + void onSuccess(in nsISearchEngine engine); + + /** + * Called to indicate that the engine addition process failed. + * + * @param errorCode + * One of the ERROR_* values described above indicating the cause of + * the failure. + */ + void onError(in unsigned long errorCode); +}; + +/** + * Callback for asynchronous initialization of nsIBrowserSearchService + */ +[scriptable, function, uuid(02256156-16e4-47f1-9979-76ff98ceb590)] +interface nsIBrowserSearchInitObserver : nsISupports +{ + /** + * Called once initialization of the browser search service is complete. + * + * @param aStatus The status of that service. + */ + void onInitComplete(in nsresult aStatus); +}; + +[scriptable, uuid(150ef720-bbe2-4169-b9f3-ef7ec0654ced)] +interface nsIBrowserSearchService : nsISupports +{ + /** + * Start asynchronous initialization. + * + * The callback is triggered once initialization is complete, which may be + * immediately, if initialization has already been completed by some previous + * call to this method. The callback is always invoked asynchronously. + * + * @param aObserver An optional object observing the end of initialization. + */ + void init([optional] in nsIBrowserSearchInitObserver aObserver); + + /** + * Determine whether initialization has been completed. + * + * Clients of the service can use this attribute to quickly determine whether + * initialization is complete, and decide to trigger some immediate treatment, + * to launch asynchronous initialization or to bailout. + * + * Note that this attribute does not indicate that initialization has succeeded. + * + * @return |true| if the search service is now initialized, |false| if + * initialization has not been triggered yet. + */ + readonly attribute bool isInitialized; + + /** + * Resets the default engine to its original value. + */ + void resetToOriginalDefaultEngine(); + + /** + * Checks if an EngineURL of type URLTYPE_SEARCH_HTML exists for + * any engine, with a matching method, template URL, and query params. + * + * @param method + * The HTTP request method used when submitting a search query. + * Must be a case insensitive value of either "get" or "post". + * + * @param url + * The URL to which search queries should be sent. + * Must not be null. + * + * @param formData + * The un-sorted form data used as query params. + */ + boolean hasEngineWithURL(in AString method, in AString url, in jsval formData); + + /** + * Adds a new search engine from the file at the supplied URI, optionally + * asking the user for confirmation first. If a confirmation dialog is + * shown, it will offer the option to begin using the newly added engine + * right away. + * + * @param engineURL + * The URL to the search engine's description file. + * + * @param dataType + * Obsolete, the value is ignored. + * + * @param iconURL + * A URL string to an icon file to be used as the search engine's + * icon. This value may be overridden by an icon specified in the + * engine description file. + * + * @param confirm + * A boolean value indicating whether the user should be asked for + * confirmation before this engine is added to the list. If this + * value is false, the engine will be added to the list upon successful + * load, but it will not be selected as the current engine. + * + * @param callback + * A nsISearchInstallCallback that will be notified when the + * addition is complete, or if the addition fails. It will not be + * called if addEngine throws an exception. + * + * @throws NS_ERROR_FAILURE if the description file cannot be successfully + * loaded. + */ + void addEngine(in AString engineURL, in long dataType, in AString iconURL, + in boolean confirm, [optional] in nsISearchInstallCallback callback); + + /** + * Adds a new search engine, without asking the user for confirmation and + * without starting to use it right away. + * + * @param name + * The search engine's name. Must be unique. Must not be null. + * + * @param iconURL + * Optional: A URL string pointing to the icon to be used to represent + * the engine. + * + * @param alias + * Optional: A unique shortcut that can be used to retrieve the + * search engine. + * + * @param description + * Optional: a description of the search engine. + * + * @param method + * The HTTP request method used when submitting a search query. + * Must be a case insensitive value of either "get" or "post". + * + * @param url + * The URL to which search queries should be sent. + * Must not be null. + * + * @param extensionID [optional] + * Optional: The correct extensionID if called by an add-on. + */ + void addEngineWithDetails(in AString name, + in AString iconURL, + in AString alias, + in AString description, + in AString method, + in AString url, + [optional] in AString extensionID); + + /** + * Un-hides all engines installed in the directory corresponding to + * the directory service's NS_APP_SEARCH_DIR key. (i.e. the set of + * engines returned by getDefaultEngines) + */ + void restoreDefaultEngines(); + + /** + * Returns an engine with the specified alias. + * + * @param alias + * The search engine's alias. + * @returns The corresponding nsISearchEngine object, or null if it doesn't + * exist. + */ + nsISearchEngine getEngineByAlias(in AString alias); + + /** + * Returns an engine with the specified name. + * + * @param aEngineName + * The name of the engine. + * @returns The corresponding nsISearchEngine object, or null if it doesn't + * exist. + */ + nsISearchEngine getEngineByName(in AString aEngineName); + + /** + * Returns an array of all installed search engines. + * + * @returns an array of nsISearchEngine objects. + */ + void getEngines( + [optional] out unsigned long engineCount, + [retval, array, size_is(engineCount)] out nsISearchEngine engines); + + /** + * Returns an array of all installed search engines whose hidden attribute is + * false. + * + * @returns an array of nsISearchEngine objects. + */ + void getVisibleEngines( + [optional] out unsigned long engineCount, + [retval, array, size_is(engineCount)] out nsISearchEngine engines); + + /** + * Returns an array of all default search engines. This includes all loaded + * engines that aren't in the user's profile directory + * (NS_APP_USER_SEARCH_DIR). + * + * @returns an array of nsISearchEngine objects. + */ + void getDefaultEngines( + [optional] out unsigned long engineCount, + [retval, array, size_is(engineCount)] out nsISearchEngine engines); + + /** + * Moves a visible search engine. + * + * @param engine + * The engine to move. + * @param newIndex + * The engine's new index in the set of visible engines. + * + * @throws NS_ERROR_FAILURE if newIndex is out of bounds, or if engine is + * hidden. + */ + void moveEngine(in nsISearchEngine engine, in long newIndex); + + /** + * Removes the search engine. If the search engine is installed in a global + * location, this will just hide the engine. If the engine is in the user's + * profile directory, it will be removed from disk. + * + * @param engine + * The engine to remove. + */ + void removeEngine(in nsISearchEngine engine); + + /** + * The original Engine object that is the default for this region, + * ignoring changes the user may have subsequently made. + */ + readonly attribute nsISearchEngine originalDefaultEngine; + + /** + * Alias for the currentEngine attribute, kept for add-on compatibility. + */ + attribute nsISearchEngine defaultEngine; + + /** + * The currently active search engine. + * Unless the application doesn't ship any search plugin, this should never + * be null. If the currently active engine is removed, this attribute will + * fallback first to the original default engine if it's not hidden, then to + * the first visible engine, and as a last resort it will unhide the original + * default engine. + */ + attribute nsISearchEngine currentEngine; + + /** + * Gets a representation of the default engine in an anonymized JSON + * string suitable for recording in the Telemetry environment. + * + * @return an object containing anonymized info about the default engine: + * name, loadPath, submissionURL (for default engines). + */ + jsval getDefaultEngineInfo(); + + /** + * Determines if the provided URL represents results from a search engine, and + * provides details about the match. + * + * The lookup mechanism checks whether the domain name and path of the + * provided HTTP or HTTPS URL matches one of the known values for the visible + * search engines. The match does not depend on which of the schemes is used. + * The expected URI parameter for the search terms must exist in the query + * string, but other parameters are ignored. + * + * @param url + * String containing the URL to parse, for example + * "https://www.google.com/search?q=terms". + */ + nsISearchParseSubmissionResult parseSubmissionURL(in AString url); +}; + +%{ C++ +/** + * The observer topic to listen to for actions performed on installed + * search engines. + */ +#define SEARCH_ENGINE_TOPIC "browser-search-engine-modified" + +/** + * Sent when an engine is removed from the data store. + */ +#define SEARCH_ENGINE_REMOVED "engine-removed" + +/** + * Sent when an engine is changed. This includes when the engine's "hidden" + * property is changed. + */ +#define SEARCH_ENGINE_CHANGED "engine-changed" + +/** + * Sent when an engine is added to the list of available engines. + */ +#define SEARCH_ENGINE_ADDED "engine-added" + +/** + * Sent when a search engine being installed from a remote plugin description + * file is completely loaded. This is used internally by the search service as + * an indication of when the engine can be added to the internal store, and + * therefore should not be used to detect engine availability. It is always + * followed by an "added" notification. + */ +#define SEARCH_ENGINE_LOADED "engine-loaded" + +/** + * Sent when the "current" engine is changed. + */ +#define SEARCH_ENGINE_CURRENT "engine-current"; + +/** + * Sent when the "default" engine is changed. + */ +#define SEARCH_ENGINE_DEFAULT "engine-default"; + + + +%} diff --git a/netwerk/base/nsIBufferedStreams.idl b/netwerk/base/nsIBufferedStreams.idl new file mode 100644 index 000000000..60b9768b9 --- /dev/null +++ b/netwerk/base/nsIBufferedStreams.idl @@ -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/. */ + +#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, 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); +}; + +/** + * 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, 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); +}; diff --git a/netwerk/base/nsIByteRangeRequest.idl b/netwerk/base/nsIByteRangeRequest.idl new file mode 100644 index 000000000..b558016a5 --- /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 000000000..f6d3c7b73 --- /dev/null +++ b/netwerk/base/nsICacheInfoChannel.idl @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIOutputStream; + +[scriptable, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)] +interface nsICacheInfoChannel : nsISupports +{ + /** + * Get expiration time from cache token. This attribute is equivalent to + * nsICachingChannel.cacheToken.expirationTime. + */ + readonly attribute uint32_t cacheTokenExpirationTime; + + /** + * Set/get charset of cache entry. Accessing this attribute is equivalent to + * calling nsICachingChannel.cacheToken.getMetaDataElement("charset") and + * nsICachingChannel.cacheToken.setMetaDataElement("charset"). + */ + attribute ACString cacheTokenCachedCharset; + + /** + * 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(); + + /** + * Set/get the cache key... uniquely identifies the data in the cache + * for this channel. Holding a reference to this key does NOT prevent + * the cached data from being removed. + * + * 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 NULL indicating that the URI of the channel is + * sufficient to locate the same cache entry. Setting a NULL cache key + * is likewise valid. + */ + attribute nsISupports 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; + + /** + * 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. + * Must be called before AsyncOpen. + */ + void preferAlternativeDataType(in ACString type); + + /** + * 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; + + /** + * 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. + */ + nsIOutputStream openAlternativeOutputStream(in ACString type); +}; diff --git a/netwerk/base/nsICachingChannel.idl b/netwerk/base/nsICachingChannel.idl new file mode 100644 index 000000000..63f65b1a4 --- /dev/null +++ b/netwerk/base/nsICachingChannel.idl @@ -0,0 +1,129 @@ +/* -*- 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, 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; + + /** + * The same as above but accessing the offline app cache token if there + * is any. + * + * @throws + * NS_ERROR_NOT_AVAILABLE when there is not offline cache token + */ + attribute nsISupports offlineCacheToken; + + /** + * 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. This flag differs from LOAD_ONLY_FROM_CACHE in that + * this flag fails the load if validation is required while + * LOAD_ONLY_FROM_CACHE skips validation where possible. + */ + const unsigned long LOAD_NO_NETWORK_IO = 1 << 26; + + /** + * This load flag causes the offline cache to be checked when fetching + * a request. It will be set automatically if the browser is offline. + * + * This flag will not be transferred through a redirect. + */ + const unsigned long LOAD_CHECK_OFFLINE_CACHE = 1 << 27; + + /** + * 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 000000000..c558dc6e2 --- /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 000000000..94d9d6e9a --- /dev/null +++ b/netwerk/base/nsICaptivePortalService.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 "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; + + /** + * 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" + +%} diff --git a/netwerk/base/nsIChannel.idl b/netwerk/base/nsIChannel.idl new file mode 100644 index 000000000..743e94292 --- /dev/null +++ b/netwerk/base/nsIChannel.idl @@ -0,0 +1,357 @@ +/* -*- 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 nsIURI; +interface nsIInterfaceRequestor; +interface nsIInputStream; +interface nsIStreamListener; + +%{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). + */ + 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. For example, document.open() may + * create an nsWyciwygChannel to store the data that will be written to the + * document. In that case, if the caller has TLS information, we propagate + * that info onto the nsWyciwygChannel given that it is likely that the + * caller will be writing data that was delivered over TLS to the document. + */ + readonly attribute nsISupports 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(); + + /** + * Performs content security check and calls open() + */ + nsIInputStream open2(); + + /** + * 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. + * + * @param aListener the nsIStreamListener implementation + * @param aContext an opaque parameter forwarded to aListener's methods + * @see nsIChannelEventSink for onChannelRedirect + */ + void asyncOpen(in nsIStreamListener aListener, in nsISupports aContext); + + /** + * Performs content security check and calls asyncOpen(). + */ + void asyncOpen2(in nsIStreamListener aListener); + + /************************************************************************** + * Channel specific load flags: + * + * Bits 26-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. + */ + 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 use URI classifier service to check + * the URI when opening the channel. + */ + const unsigned long LOAD_CLASSIFY_URI = 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 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 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; + + /** + * 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; + +%{ C++ + inline already_AddRefed<nsILoadInfo> GetLoadInfo() + { + nsCOMPtr<nsILoadInfo> result; + mozilla::DebugOnly<nsresult> rv = GetLoadInfo(getter_AddRefs(result)); + MOZ_ASSERT(NS_SUCCEEDED(rv) || !result); + return result.forget(); + } +%} + +}; diff --git a/netwerk/base/nsIChannelEventSink.idl b/netwerk/base/nsIChannelEventSink.idl new file mode 100644 index 000000000..64c5b9eec --- /dev/null +++ b/netwerk/base/nsIChannelEventSink.idl @@ -0,0 +1,101 @@ +/* -*- 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; + + /** + * 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! + * + * @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/nsIChannelWithDivertableParentListener.idl b/netwerk/base/nsIChannelWithDivertableParentListener.idl new file mode 100644 index 000000000..1fb52931f --- /dev/null +++ b/netwerk/base/nsIChannelWithDivertableParentListener.idl @@ -0,0 +1,46 @@ +/* -*- 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" + +%{C++ +namespace mozilla { +namespace net { +class ADivertableParentChannel; +} +} +%} + +[ptr] native ADivertableParentChannelPtr(mozilla::net::ADivertableParentChannel); + +/** When we are diverting messages from the child to the parent. The + * nsHttpChannel and nsFtpChannel must know that there is a ChannelParent to + * be able to suspend message delivery if the channel is suspended. + */ +[uuid(c073d79f-2503-4dff-ba87-d3071f8b433b)] +interface nsIChannelWithDivertableParentListener : nsISupports +{ + /** + * Informs nsHttpChannel or nsFtpChannel that a ParentChannel starts + * diverting messages. During this time all suspend/resume calls to the + * channel must also suspend the ParentChannel by calling + * SuspendMessageDiversion/ResumeMessageDiversion. + */ + void MessageDiversionStarted(in ADivertableParentChannelPtr aParentChannel); + + /** + * The message diversion has finished the calls to + * SuspendMessageDiversion/ResumeMessageDiversion are not necessary anymore. + */ + void MessageDiversionStop(); + + /** + * Internal versions of Suspend/Resume that suspend (or resume) the channel + * but do not suspend the ParentChannel's IPDL message queue. + */ + void SuspendInternal(); + void ResumeInternal(); +}; diff --git a/netwerk/base/nsIChildChannel.idl b/netwerk/base/nsIChildChannel.idl new file mode 100644 index 000000000..fae77f322 --- /dev/null +++ b/netwerk/base/nsIChildChannel.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 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, + in nsISupports aContext); +}; diff --git a/netwerk/base/nsIClassOfService.idl b/netwerk/base/nsIClassOfService.idl new file mode 100644 index 000000000..30590b324 --- /dev/null +++ b/netwerk/base/nsIClassOfService.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 "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. + * + * The default gecko HTTP/1 stack makes Followers wait for Leaders to + * complete before dispatching followers. Other classes run in + * parallel - neither being blocked nor blocking. All grouping is done + * based on the Load Group - separate load groups proceed + * independently. + * + * HTTP/2 does not use the load group, but prioritization is done per + * HTTP/2 session. HTTP/2 dispatches all the requests as soon as + * possible. + * The various classes are assigned logical priority + * dependency groups and then transactions of that class depend on the + * group. In this model Followers block on Leaders and Speculative + * depends on Background. See Http2Stream.cpp for weighting details. + * + */ + +[scriptable, uuid(1ccb58ec-5e07-4cf9-a30d-ac5490d23b41)] +interface nsIClassOfService : nsISupports +{ + attribute unsigned long classFlags; + + void clearClassFlags(in unsigned long flags); + void addClassFlags(in unsigned long flags); + + const unsigned long Leader = 1 << 0; + const unsigned long Follower = 1 << 1; + const unsigned long Speculative = 1 << 2; + const unsigned long Background = 1 << 3; + const unsigned long Unblocked = 1 << 4; + const unsigned long Throttleable = 1 << 5; + const unsigned long UrgentStart = 1 << 6; +}; diff --git a/netwerk/base/nsIContentSniffer.idl b/netwerk/base/nsIContentSniffer.idl new file mode 100644 index 000000000..f9052b8e6 --- /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/nsICryptoFIPSInfo.idl b/netwerk/base/nsICryptoFIPSInfo.idl new file mode 100644 index 000000000..1defc56ab --- /dev/null +++ b/netwerk/base/nsICryptoFIPSInfo.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" + +[scriptable, uuid(99e81922-7318-4431-b3aa-78b3cb4119bb)] +interface nsICryptoFIPSInfo : nsISupports +{ + readonly attribute boolean isFIPSModeActive; +}; + +%{C++ +#define NS_CRYPTO_FIPSINFO_SERVICE_CONTRACTID "@mozilla.org/crypto/fips-info-service;1" +%} diff --git a/netwerk/base/nsICryptoHMAC.idl b/netwerk/base/nsICryptoHMAC.idl new file mode 100644 index 000000000..92e06a0a4 --- /dev/null +++ b/netwerk/base/nsICryptoHMAC.idl @@ -0,0 +1,108 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsIKeyObject; + +/** + * nsICryptoHMAC + * This interface provides HMAC signature algorithms. + */ + +[scriptable, uuid(8FEB4C7C-1641-4a7b-BC6D-1964E2099497)] +interface nsICryptoHMAC : nsISupports +{ + /** + * Hashing Algorithms. These values are to be used by the + * |init| method to indicate which hashing function to + * use. These values map onto the values defined in + * mozilla/security/nss/lib/softoken/pkcs11t.h and are + * switched to CKM_*_HMAC constant. + */ + const short MD2 = 1; + const short MD5 = 2; + const short SHA1 = 3; + const short SHA256 = 4; + const short SHA384 = 5; + const short SHA512 = 6; + + /** + * Initialize the hashing object. This method may be + * called multiple times with different algorithm types. + * + * @param aAlgorithm the algorithm type to be used. + * This value must be one of the above valid + * algorithm types. + * + * @param aKeyObject + * Object holding a key. To create the key object use for instance: + * var keyObject = Components.classes["@mozilla.org/security/keyobjectfactory;1"] + * .getService(Components.interfaces.nsIKeyObjectFactory) + * .keyFromString(Components.interfaces.nsIKeyObject.HMAC, rawKeyData); + * + * WARNING: This approach is not FIPS compliant. + * + * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm + * type is passed. + * + * NOTE: This method must be called before any other method + * on this interface is called. + */ + void init(in unsigned long aAlgorithm, in nsIKeyObject aKeyObject); + + /** + * @param aData a buffer to calculate the hash over + * + * @param aLen the length of the buffer |aData| + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + */ + void update([const, array, size_is(aLen)] in octet aData, in unsigned long aLen); + + /** + * Calculates and updates a new hash based on a given data stream. + * + * @param aStream an input stream to read from. + * + * @param aLen how much to read from the given |aStream|. Passing + * UINT32_MAX indicates that all data available will be used + * to update the hash. + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + * + * @throws NS_ERROR_NOT_AVAILABLE if the requested amount of + * data to be calculated into the hash is not available. + * + */ + void updateFromStream(in nsIInputStream aStream, in unsigned long aLen); + + /** + * Completes this HMAC object and produces the actual HMAC diegest data. + * + * @param aASCII if true then the returned value is a base-64 + * encoded string. if false, then the returned value is + * binary data. + * + * @return a hash of the data that was read by this object. This can + * be either binary data or base 64 encoded. + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + * + * NOTE: This method may be called any time after |init| + * is called. This call resets the object to its + * pre-init state. + */ + ACString finish(in boolean aASCII); + + /** + * Reinitialize HMAC context to be reused with the same + * settings (the key and hash algorithm) but on different + * set of data. + */ + void reset(); +}; diff --git a/netwerk/base/nsICryptoHash.idl b/netwerk/base/nsICryptoHash.idl new file mode 100644 index 000000000..cd865a3a9 --- /dev/null +++ b/netwerk/base/nsICryptoHash.idl @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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; + +/** + * nsICryptoHash + * This interface provides crytographic hashing algorithms. + */ + +[scriptable, uuid(1e5b7c43-4688-45ce-92e1-77ed931e3bbe)] +interface nsICryptoHash : nsISupports +{ + /** + * Hashing Algorithms. These values are to be used by the + * |init| method to indicate which hashing function to + * use. These values map directly onto the values defined + * in mozilla/security/nss/lib/cryptohi/hasht.h. + */ + const short MD2 = 1; /* String value: "md2" */ + const short MD5 = 2; /* String value: "md5" */ + const short SHA1 = 3; /* String value: "sha1" */ + const short SHA256 = 4; /* String value: "sha256" */ + const short SHA384 = 5; /* String value: "sha384" */ + const short SHA512 = 6; /* String value: "sha512" */ + + /** + * Initialize the hashing object. This method may be + * called multiple times with different algorithm types. + * + * @param aAlgorithm the algorithm type to be used. + * This value must be one of the above valid + * algorithm types. + * + * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm + * type is passed. + * + * NOTE: This method or initWithString must be called + * before any other method on this interface is called. + */ + void init(in unsigned long aAlgorithm); + + /** + * Initialize the hashing object. This method may be + * called multiple times with different algorithm types. + * + * @param aAlgorithm the algorithm type to be used. + * + * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm + * type is passed. + * + * NOTE: This method or init must be called before any + * other method on this interface is called. + */ + void initWithString(in ACString aAlgorithm); + + /** + * @param aData a buffer to calculate the hash over + * + * @param aLen the length of the buffer |aData| + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + */ + void update([const, array, size_is(aLen)] in octet aData, in unsigned long aLen); + + /** + * Calculates and updates a new hash based on a given data stream. + * + * @param aStream an input stream to read from. + * + * @param aLen how much to read from the given |aStream|. Passing + * UINT32_MAX indicates that all data available will be used + * to update the hash. + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + * + * @throws NS_ERROR_NOT_AVAILABLE if the requested amount of + * data to be calculated into the hash is not available. + * + */ + void updateFromStream(in nsIInputStream aStream, in unsigned long aLen); + + /** + * Completes this hash object and produces the actual hash data. + * + * @param aASCII if true then the returned value is a base-64 + * encoded string. if false, then the returned value is + * binary data. + * + * @return a hash of the data that was read by this object. This can + * be either binary data or base 64 encoded. + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + * + * NOTE: This method may be called any time after |init| + * is called. This call resets the object to its + * pre-init state. + */ + ACString finish(in boolean aASCII); +}; diff --git a/netwerk/base/nsIDashboard.idl b/netwerk/base/nsIDashboard.idl new file mode 100644 index 000000000..a18710e5a --- /dev/null +++ b/netwerk/base/nsIDashboard.idl @@ -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 "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 NetDashboardCallback : 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 NetDashboardCallback cb); + + /* Arrays: host, port, spdy, ssl + * Array of arrays: active, idle */ + void requestHttpConnections(in NetDashboardCallback cb); + + /* Arrays: hostport, encrypted, msgsent, msgreceived, sentsize, receivedsize */ + void requestWebsocketConnections(in NetDashboardCallback cb); + + /* Arrays: hostname, family, hostaddr, expiration */ + void requestDNSInfo(in NetDashboardCallback 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 NetDashboardCallback 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 NetDashboardCallback cb); + + AUTF8String getLogPath(); +}; diff --git a/netwerk/base/nsIDashboardEventNotifier.idl b/netwerk/base/nsIDashboardEventNotifier.idl new file mode 100644 index 000000000..d84fd2ed4 --- /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" + +[builtinclass, 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/nsIDeprecationWarner.idl b/netwerk/base/nsIDeprecationWarner.idl new file mode 100644 index 000000000..72303b815 --- /dev/null +++ b/netwerk/base/nsIDeprecationWarner.idl @@ -0,0 +1,23 @@ +/* -*- 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 for warning about deprecated operations. Consumers should + * attach this interface to the channel's notification callbacks/loadgroup. + */ +[uuid(665c5124-2c52-41ba-ae72-2393f8e76c25)] +interface nsIDeprecationWarner : nsISupports +{ + /** + * Issue a deprecation warning. + * + * @param aWarning a warning code as declared in nsDeprecatedOperationList.h. + * @param aAsError optional boolean flag indicating whether the warning + * should be treated as an error. + */ + void issueWarning(in uint32_t aWarning, [optional] in bool aAsError); +}; diff --git a/netwerk/base/nsIDivertableChannel.idl b/netwerk/base/nsIDivertableChannel.idl new file mode 100644 index 000000000..4c28ca7dc --- /dev/null +++ b/netwerk/base/nsIDivertableChannel.idl @@ -0,0 +1,78 @@ +/* -*- 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" + +%{C++ +namespace mozilla { +namespace net { +class ChannelDiverterChild; +} +} +%} + +[ptr] native ChannelDiverterChild(mozilla::net::ChannelDiverterChild); + +interface nsIStreamListener; + +/** + * A channel implementing this interface allows diverting from an + * nsIStreamListener in the child process to one in the parent. + */ +[uuid(7a9bf52d-f828-4b31-b8df-b40fdd37d007)] +interface nsIDivertableChannel : nsISupports +{ + /** + * CHILD ONLY. + * Called by Necko client in child process during OnStartRequest to divert + * nsIStreamListener and nsIRequest callbacks to the parent process. + * + * The process should look like the following: + * + * 1) divertToParent is called in the child process. It can only be called + * during OnStartRequest(). + * + * 2) The ChannelDiverterChild that is returned is an IPDL object. It should + * be passed via some other IPDL method of the client's choosing to the + * parent. On the parent the ChannelDiverterParent's divertTo() function + * should be called with an nsIStreamListener that will then receive the + * OnStartRequest/OnDataAvailable/OnStopRequest for the channel. The + * ChannelDiverterParent can then be deleted (which will also destroy the + * ChannelDiverterChild in the child). + * + * After divertToParent() has been called, NO further function calls + * should be made on the channel. It is a dead object for all purposes. + * The reference that the channel holds to the listener in the child is + * released is once OnStartRequest completes, and no other + * nsIStreamListener calls (OnDataAvailable, OnStopRequest) will be made + * to it. + * + * @return ChannelDiverterChild IPDL actor to be passed to parent process by + * client IPDL message, e.g. PClient.DivertUsing(PDiverterChild). + * + * @throws exception if the channel was canceled early. Throws status code of + * canceled channel. + */ + ChannelDiverterChild divertToParent(); + + /** + * nsUnknownDecoder delays calling OnStartRequest until it gets enough data + * to decide about the content type (until OnDataAvaiable is called). In a + * OnStartRequest DivertToParent can be called but some OnDataAvailables are + * already called and therefore can not be diverted to parent. + * + * nsUnknownDecoder will call UnknownDecoderInvolvedKeepData in its + * OnStartRequest function and when it calls OnStartRequest of the next + * listener it will call UnknownDecoderInvolvedOnStartRequestCalled. In this + * function Child process will decide to discarge data if it is not diverting + * to parent or keep them if it is diverting to parent. + */ + void unknownDecoderInvolvedKeepData(); + + void unknownDecoderInvolvedOnStartRequestCalled(); + + readonly attribute bool divertingToParent; +}; diff --git a/netwerk/base/nsIDownloader.idl b/netwerk/base/nsIDownloader.idl new file mode 100644 index 000000000..738940b4b --- /dev/null +++ b/netwerk/base/nsIDownloader.idl @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsISupports ctxt, + in nsresult status, + in nsIFile result); +}; diff --git a/netwerk/base/nsIEncodedChannel.idl b/netwerk/base/nsIEncodedChannel.idl new file mode 100644 index 000000000..77cee0d28 --- /dev/null +++ b/netwerk/base/nsIEncodedChannel.idl @@ -0,0 +1,55 @@ +/* -*- 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 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 000000000..6f86dbb05 --- /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 000000000..e5f357f6a --- /dev/null +++ b/netwerk/base/nsIFileStreams.idl @@ -0,0 +1,237 @@ +/* -*- 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" + +interface nsIFile; + +%{C++ +struct PRFileDesc; +%} + +[ptr] native PRFileDescPtr(PRFileDesc); + +/** + * An input stream that allows you to read from a file. + */ +[scriptable, 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 be deleted by the time the stream is + * closed. It may be removed before the stream is closed if it is possible + * to delete it and still read from it. + * + * If OPEN_ON_READ is defined, and the file was recreated after the first + * delete, the file will be deleted again when it is closed again. + */ + const long DELETE_ON_CLOSE = 1<<1; + + /** + * 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, 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; +}; + +/** + * An input stream that allows you to read from a slice of a file. + */ +[scriptable, uuid(3ce03a2f-97f7-4375-b6bb-1788a60cad3b)] +interface nsIPartialFileInputStream : nsISupports +{ + /** + * Initialize with a file and new start/end positions. Both start and + * start+length must be smaller than the size of the file. Not doing so + * will lead to undefined behavior. + * You must initialize the stream, and only initialize it once, before it + * can be used. + * + * @param file file to read from + * @param start start offset of slice to read. Must be smaller + * than the size of the file. + * @param length length of slice to read. Must be small enough that + * start+length is smaller than the size of the file. + * @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 nsIFileInputStream) + */ + void init(in nsIFile file, in unsigned long long start, + in unsigned long long length, + in long ioFlags, in long perm, in long behaviorFlags); +}; + +/** + * A stream that allows you to read from a file or stream to a file. + */ +[scriptable, uuid(82cf605a-8393-4550-83ab-43cd5578e006)] +interface nsIFileStream : nsISupports +{ + /** + * @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. + */ +[scriptable, 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! + */ + [noscript] PRFileDescPtr getFileDescriptor(); +}; diff --git a/netwerk/base/nsIFileURL.idl b/netwerk/base/nsIFileURL.idl new file mode 100644 index 000000000..9f8b67cb8 --- /dev/null +++ b/netwerk/base/nsIFileURL.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 "nsIURL.idl" + +interface nsIFile; + +/** + * 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, uuid(e91ac988-27c2-448b-b1a1-3822e1ef1987)] +interface nsIFileURL : nsIURL +{ + /** + * Get/Set nsIFile corresponding to this URL. + * + * - Getter 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!! + * + * - Setter clones the nsIFile object (allowing the caller to safely modify + * the nsIFile object after setting it on this interface). + */ + attribute nsIFile file; +}; diff --git a/netwerk/base/nsIForcePendingChannel.idl b/netwerk/base/nsIForcePendingChannel.idl new file mode 100644 index 000000000..a4d6ef894 --- /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. + */ + +[noscript, 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 000000000..870886390 --- /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 000000000..9ce233515 --- /dev/null +++ b/netwerk/base/nsIHttpAuthenticatorCallback.idl @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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(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 string 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 000000000..f44605c00 --- /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 000000000..9bb777405 --- /dev/null +++ b/netwerk/base/nsIIOService.idl @@ -0,0 +1,218 @@ +/* -*- 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 nsIProtocolHandler; +interface nsIChannel; +interface nsIURI; +interface nsIFile; +interface nsIDOMNode; +interface nsIPrincipal; +interface nsILoadInfo; + +/** + * 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, 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 value of corresponding nsIProtocolHandler::protocolFlags + */ + unsigned long getProtocolFlags(in string aScheme); + + /** + * This method constructs a new URI by determining the scheme of the + * URI spec, and then delegating the construction of the URI to the + * protocol handler for that scheme. QueryInterface can be used on + * the resulting URI object to obtain a more specific type of URI. + * + * @see nsIProtocolHandler::newURI + */ + 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); + + /** + * 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 newChannelFromURI2(in nsIURI aURI, + in nsIDOMNode aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in unsigned long aContentPolicyType); + + /** + * Equivalent to newChannelFromURI2(aURI, aLoadingNode, ...) + */ + nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI, + in nsILoadInfo aLoadInfo); + + /** + * Equivalent to newChannelFromURI2(newURI(...)) + */ + nsIChannel newChannel2(in AUTF8String aSpec, + in string aOriginCharset, + in nsIURI aBaseURI, + in nsIDOMNode aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in unsigned long aContentPolicyType); + + /** + * ***** DEPRECATED ***** + * Please use NewChannelFromURI2() + * + * Creates a channel for a given URI. + * + * @param aURI nsIURI from which to make a channel + * @return reference to the new nsIChannel object + */ + nsIChannel newChannelFromURI(in nsIURI aURI); + + /** + * ***** DEPRECATED ***** + * Please use newChannel2(). + * + * Equivalent to newChannelFromURI(newURI(...)) + */ + nsIChannel newChannel(in AUTF8String aSpec, + in string aOriginCharset, + in nsIURI aBaseURI); + + + /** + * 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 + * + * @throws NS_ERROR_MALFORMED_URI if URL string is not of the right form. + */ + ACString extractScheme(in AUTF8String urlString); +}; + +%{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" + +%} + +[builtinclass, 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/nsIIOService2.idl b/netwerk/base/nsIIOService2.idl new file mode 100644 index 000000000..d7b434d17 --- /dev/null +++ b/netwerk/base/nsIIOService2.idl @@ -0,0 +1,82 @@ +/* -*- 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 "nsIIOService.idl" + +interface nsIDOMNode; +interface nsIPrincipal; + +/** + * nsIIOService2 extends nsIIOService + */ +[scriptable, uuid(52c5804b-0d3c-4d4f-8654-1c36fd310e69)] +interface nsIIOService2 : nsIIOService +{ + /** + * 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 newChannelFromURIWithProxyFlags2(in nsIURI aURI, + in nsIURI aProxyURI, + in unsigned long aProxyFlags, + in nsIDOMNode aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in unsigned long aContentPolicyType); + + /** + * ***** DEPRECATED ***** + * Please use newChannelFromURIWithProxyFlags2() + * + * 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 + * @return reference to the new nsIChannel object + */ + nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI, + in nsIURI aProxyURI, + in unsigned long aProxyFlags); + +}; diff --git a/netwerk/base/nsIIncrementalDownload.idl b/netwerk/base/nsIIncrementalDownload.idl new file mode 100644 index 000000000..3ae363c48 --- /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 000000000..60aa9cfef --- /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 "nsIStreamListener.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 : nsIStreamListener +{ + /** + * 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 000000000..3af16ed62 --- /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 000000000..83c29cdbb --- /dev/null +++ b/netwerk/base/nsIInputStreamPump.idl @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 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 aStreamPos + * specifies the stream offset from which to start reading. the + * offset value is absolute. pass -1 to specify the current offset. + * NOTE: this parameter is ignored if the underlying stream does not + * implement nsISeekableStream. + * @param aStreamLen + * specifies how much data to read from the stream. pass -1 to read + * all data available in the stream. + * @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. + */ + void init(in nsIInputStream aStream, + in long long aStreamPos, + in long long aStreamLen, + in unsigned long aSegmentSize, + in unsigned long aSegmentCount, + in boolean aCloseWhenDone); + + /** + * 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, + in nsISupports aListenerContext); +}; diff --git a/netwerk/base/nsILoadContextInfo.idl b/netwerk/base/nsILoadContextInfo.idl new file mode 100644 index 000000000..b344a1544 --- /dev/null +++ b/netwerk/base/nsILoadContextInfo.idl @@ -0,0 +1,89 @@ +/* -*- 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::NeckoOriginAttributes*); + +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 +{ + const unsigned long NO_APP_ID = 0; + const unsigned long UNKNOWN_APP_ID = 4294967295; // UINT32_MAX + + /** + * Whether the context is in a Private Browsing mode + */ + readonly attribute boolean isPrivate; + + /** + * Whether the load is initiated as anonymous + */ + readonly attribute boolean isAnonymous; + + /** + * NeckoOriginAttributes 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 NeckoOriginAttributes 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 000000000..4f89bd0e3 --- /dev/null +++ b/netwerk/base/nsILoadGroup.idl @@ -0,0 +1,104 @@ +/* -*- 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, nsIDocument::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. + */ +[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. + */ + [noscript] readonly attribute nsID 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; + + /** + * The cached user agent override created by UserAgentOverrides.jsm. Used + * for all sub-resource requests in the loadgroup. + */ + attribute ACString userAgentOverrideCache; +}; diff --git a/netwerk/base/nsILoadGroupChild.idl b/netwerk/base/nsILoadGroupChild.idl new file mode 100644 index 000000000..3ec60a1a2 --- /dev/null +++ b/netwerk/base/nsILoadGroupChild.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 "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. + */ + +[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 000000000..78433c8b8 --- /dev/null +++ b/netwerk/base/nsILoadInfo.idl @@ -0,0 +1,732 @@ +/* -*- 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 nsIDOMDocument; +interface nsINode; +interface nsIPrincipal; + +%{C++ +#include "nsTArray.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/LoadTainting.h" + +class nsCString; +%} + +[ref] native const_nsIPrincipalArray(const nsTArray<nsCOMPtr<nsIPrincipal>>); +native NeckoOriginAttributes(mozilla::NeckoOriginAttributes); +[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes); +[ref] native StringArrayRef(const nsTArray<nsCString>); + +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 +{ + /** + * *** DEPRECATED *** + * No LoadInfo created within Gecko should contain this security flag. + * Please use any of the five security flags defined underneath. + * We only keep this security flag to provide backwards compatibilty. + */ + const unsigned long SEC_NORMAL = 0; + + /** + * 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_DATA_INHERITS + * * SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED + * * SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS + * * SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL + * * SEC_REQUIRE_CORS_DATA_INHERITS + * + * 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 AsyncOpen2 will fail. + */ + + /* + * Enforce the same origin policy where data: loads inherit + * the principal. + */ + const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS = (1<<0); + + /* + * Enforce the same origin policy but data: loads are blocked. + */ + const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED = (1<<1); + + /** + * Allow loads from other origins. Loads from data: will inherit + * the principal of the origin that triggered the load. + * Commonly used by plain <img>, <video>, <link rel=stylesheet> etc. + */ + const unsigned long SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS = (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_DATA_IS_NULL = (1<<3); + + /** + * Allow loads from any origin, but require CORS for cross-origin + * loads. Loads from data: are allowed and the result will inherit + * the principal of the origin that triggered the load. + * Commonly used by <img crossorigin>, <video crossorigin>, + * XHR, fetch(), etc. + */ + const unsigned long SEC_REQUIRE_CORS_DATA_INHERITS = (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_DATA_INHERITS 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. The resulting resource will use the + * principal of the document which is doing the load. Setting this flag + * will cause GetChannelResultPrincipal to return the same principal as + * the loading principal that's passed in when creating the channel. + * + * This will happen independently of the scheme of the URI that the + * channel is loading. + * + * So if the loading document comes from "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 SEC_SANDBOXED. 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); + + /** + * Sandbox the load. The resulting resource will use a freshly created + * null principal. So GetChannelResultPrincipal will always return a + * null principal whenever this flag is set. + * + * This will happen independently of the scheme of the URI that the + * channel is loading. + * + * This flag can not be used together with SEC_FORCE_INHERIT_PRINCIPAL. + */ + const unsigned long SEC_SANDBOXED = (1<<8); + + /** + * 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, or about:tabcrashed. + */ + const unsigned long SEC_LOAD_ERROR_PAGE = (1<<13); + + /** + * Force inheriting of the principalToInherit, overruling any owner + * that might be set on the channel. (Please note that channel.owner + * is deprecated and will be removed within Bug 1286838). + * Setting this flag will cause GetChannelResultPrincipal to return the + * principalToInherit set in the loadInfo. + * + * 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 + * codebase-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 loadingPrincipal. + */ + [noscript, notxpcom, nostdcall, binaryname(LoadingPrincipal)] + nsIPrincipal binaryLoadingPrincipal(); + + /** + * 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 + * codebase-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(); + + /** + * 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(); + + /** + * 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 nsIDOMDocument 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(); + + /** + * 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; + } +%} + + /** + * Allows to query only the security mode bits from above. + */ + [infallible] readonly attribute unsigned long securityMode; + + /** + * 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] readonly attribute boolean isInThirdPartyContext; + + /** + * 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; + + /** + * If forceInheritPrincipal is true, the data coming from the channel should + * use loadingPrincipal for its principal, even when the data is loaded over + * http:// or another protocol that would normally use a URI-based principal. + * 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 use principalToInherit for its principal, even when the + * data is loaded over http:// or another protocol that would normally use + * a URI-based principal. + */ + [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; + + /** + * 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; + + /** + * 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; + +%{ C++ + inline nsContentPolicyType GetExternalContentPolicyType() + { + nsContentPolicyType result; + mozilla::DebugOnly<nsresult> rv = GetExternalContentPolicyType(&result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return 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] + nsContentPolicyType internalContentPolicyType(); + + /** + * Returns true if document or any of the documents ancestors + * up to the toplevel document make use of the CSP directive + * 'upgrade-insecure-requests'. Used to identify upgrade + * requests in e10s where the loadingDocument is not available. + * + * Warning: If the loadingDocument is null, then the + * upgradeInsecureRequests is false. + */ + [infallible] readonly attribute boolean upgradeInsecureRequests; + + /** + * If true, the content of the channel is queued up and checked + * if it matches a content signature. Note, setting this flag + * to true will negatively impact performance since the preloader + * can not start until all of the content is fetched from the + * netwerk. + * + * Only use that in combination with TYPE_DOCUMENT. + */ + [infallible] attribute boolean verifySignedContent; + + /** + * If true, this load will fail if it has no SRI integrity + */ + [infallible] attribute boolean enforceSRI; + + /** + * The SEC_FORCE_INHERIT_PRINCIPAL flag may be dropped when a load info + * object is created. Specifically, it will be dropped if the SEC_SANDBOXED + * flag is also present. This flag is set if SEC_FORCE_INHERIT_PRINCIPAL was + * dropped. + */ + [infallible] readonly attribute boolean forceInheritPrincipalDropped; + + /** + * These are the window IDs of the window in which the element being + * loaded lives. parentOuterWindowID is the window ID of this window's + * parent. + * + * Note that these window IDs can be 0 if the window is not + * available. parentOuterWindowID will be the same as outerWindowID if the + * window has no parent. + */ + [infallible] readonly attribute unsigned long long innerWindowID; + [infallible] readonly attribute unsigned long long outerWindowID; + [infallible] readonly attribute unsigned long long parentOuterWindowID; + + /** + * Only when the element being loaded is <frame src="foo.html"> + * (or, more generally, if the element QIs to nsIFrameLoaderOwner), + * the frameOuterWindowID is the outer window containing the + * foo.html document. + * + * Note: For other cases, frameOuterWindowID is 0. + */ + [infallible] readonly attribute unsigned long long frameOuterWindowID; + + /** + * For all loads of none TYPE_DOUCMENT this function resets the + * LoadingPrincipal, the TriggeringPrincipal and the + * PrincipalToInherit to a freshly created NullPrincipal which inherits + * the current origin attributes from the loadinfo. + * For loads of TYPE_DOCUMENT this function resets only the + * TriggeringPrincipal as well as the PrincipalToInherit to a freshly + * created NullPrincipal which inherits the origin attributes from + * the loadInfo. (Please note that the LoadingPrincipal for TYPE_DOCUMENT + * loads is always null.) + * + * WARNING: Please only use that function if you know exactly what + * you are doing!!! + */ + void resetPrincipalsToNullPrincipal(); + + /** + * Customized NeckoOriginAttributes 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)] + NeckoOriginAttributes binaryGetOriginAttributes(); + + [noscript, nostdcall, binaryname(SetOriginAttributes)] + void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs); + +%{ C++ + inline mozilla::NeckoOriginAttributes GetOriginAttributes() + { + mozilla::NeckoOriginAttributes result; + mozilla::DebugOnly<nsresult> rv = GetOriginAttributes(&result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return result; + } +%} + + /** + * Whenever a channel is openend by asyncOpen2() [or also open2()], + * lets set this flag so that redirects of such channels are also + * openend using asyncOpen2() [open2()]. + * + * 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 discareded. + * + */ + [infallible] attribute boolean enforceSecurity; + + /** + * Whenever a channel is evaluated by the ContentSecurityManager + * the first time, we set this flag to true to indicate that + * subsequent calls of AsyncOpen2() 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; + + /** + * Whenever a channel gets redirected, append the principal of the + * channel [before the channels got redirected] to the loadinfo, + * so that at every point this array lets us reason about all the + * redirects this channel went through. + * @param aPrincipal, the channelURIPrincipal before the channel + * got redirected. + * @param aIsInternalRedirect should be true if the channel is going + * through an internal redirect, otherwise false. + */ + void appendRedirectedPrincipal(in nsIPrincipal principal, + in boolean isInternalRedirect); + + /** + * An array of nsIPrincipals 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)] + const_nsIPrincipalArray 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)] + const_nsIPrincipalArray binaryRedirectChain(); + + /** + * 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_DATA_INHERITS mode. + */ + [noscript, notxpcom, nostdcall] + void setCorsPreflightInfo(in StringArrayRef 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)] + StringArrayRef corsUnsafeHeaders(); + + /** + * Returns value set through setCorsPreflightInfo. + */ + [infallible] readonly attribute boolean forcePreflight; + + /** + * A C++ friendly getter for the forcePreflight flag. + */ + [infallible] readonly attribute boolean isPreflight; + + /** + * When this request would be mixed-content and we do not have an + * entry in the HSTS cache, we send an HSTS priming request to + * determine if it is ok to upgrade the request to HTTPS. + */ + /** + * True if this is a mixed-content load and HSTS priming request will be sent. + */ + [noscript, infallible] readonly attribute boolean forceHSTSPriming; + /** + * Carry the decision whether this load would be blocked by mixed content so + * that if HSTS priming fails, the correct decision can be made. + */ + [noscript, infallible] readonly attribute boolean mixedContentWouldBlock; + + /** + * Mark this LoadInfo as needing HSTS Priming + * + * @param wouldBlock Carry the decision of Mixed Content Blocking to be + * applied when HSTS priming is complete. + */ + [noscript, notxpcom, nostdcall] + void setHSTSPriming(in boolean mixeContentWouldBlock); + [noscript, notxpcom, nostdcall] + void clearHSTSPriming(); + + /** + * 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; +}; diff --git a/netwerk/base/nsIMIMEInputStream.idl b/netwerk/base/nsIMIMEInputStream.idl new file mode 100644 index 000000000..82992d939 --- /dev/null +++ b/netwerk/base/nsIMIMEInputStream.idl @@ -0,0 +1,47 @@ +/* -*- 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 "nsIInputStream.idl" + +/** + * The MIME stream separates headers and a datastream. It also allows + * automatic creation of the content-length header. + */ + +[scriptable, uuid(dcbce63c-1dd1-11b2-b94d-91f6d49a3161)] +interface nsIMIMEInputStream : nsIInputStream +{ + /** + * When true a "Content-Length" header is automatically added to the + * stream. The value of the content-length is automatically calculated + * using the available() method on the data stream. The value is + * recalculated every time the stream is rewinded to the start. + * Not allowed to be changed once the stream has been started to be read. + */ + attribute boolean addContentLength; + + /** + * 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); + + /** + * 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 000000000..1b9517628 --- /dev/null +++ b/netwerk/base/nsIMultiPartChannel.idl @@ -0,0 +1,35 @@ +/* -*- 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, 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; + + /** + * 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; +}; diff --git a/netwerk/base/nsINSSErrorsService.idl b/netwerk/base/nsINSSErrorsService.idl new file mode 100644 index 000000000..95a6e6d0c --- /dev/null +++ b/netwerk/base/nsINSSErrorsService.idl @@ -0,0 +1,69 @@ +/* -*- 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" + +[scriptable, uuid(12f60021-e14b-4020-99d1-ed2c795be66a)] +interface nsINSSErrorsService : nsISupports +{ + /** + * @param aNSPRCode An error code obtained using PR_GetError() + * @return True if it is error code defined by the NSS library + */ + boolean isNSSErrorCode(in int32_t aNSPRCode); + + /** + * Function will fail if aNSPRCode is not an NSS error code. + * @param aNSPRCode An error code obtained using PR_GetError() + * @return The result of the conversion, an XPCOM error code + */ + nsresult getXPCOMFromNSSError(in int32_t aNSPRCode); + + /** + * Function will fail if aXPCOMErrorCode is not an NSS error code. + * @param aXPCOMErrorCode An error code obtained using getXPCOMFromNSSError + * return A localized human readable error explanation. + */ + AString getErrorMessage(in nsresult aXPCOMErrorCode); + + /** + * Function will fail if aXPCOMErrorCode is not an NSS error code. + * @param aXPCOMErrorCode An error code obtained using getXPCOMFromNSSError + * return the error class of the code, either ERROR_CLASS_BAD_CERT + * or ERROR_CLASS_SSL_PROTOCOL + */ + uint32_t getErrorClass(in nsresult aXPCOMErrorCode); + + const unsigned long ERROR_CLASS_SSL_PROTOCOL = 1; + const unsigned long ERROR_CLASS_BAD_CERT = 2; + + /** + * The following values define the range of NSPR error codes used by NSS. + * NSS remains the authorative source for these numbers, as a result, + * the values might change in the future. + * The security module will perform a runtime check and assertion + * to ensure the values are in synch with NSS. + */ + const long NSS_SEC_ERROR_BASE = -(0x2000); + const long NSS_SEC_ERROR_LIMIT = (NSS_SEC_ERROR_BASE + 1000); + const long NSS_SSL_ERROR_BASE = -(0x3000); + const long NSS_SSL_ERROR_LIMIT = (NSS_SSL_ERROR_BASE + 1000); + + /** + * The error codes within each module must fit in 16 bits. We want these + * errors to fit in the same module as the NSS errors but not overlap with + * any of them. Converting an NSS SEC, NSS SSL, or mozilla::pkix error to + * an NS error involves negating the value of the error and then + * synthesizing an error in the NS_ERROR_MODULE_SECURITY module. Hence, + * mozilla::pkix errors will start at a negative value that both doesn't + * overlap with the current value ranges for NSS errors and that will fit + * in 16 bits when negated. + * + * Keep these in sync with pkixnss.h. + */ + const long MOZILLA_PKIX_ERROR_BASE = -(0x4000); + const long MOZILLA_PKIX_ERROR_LIMIT = (MOZILLA_PKIX_ERROR_BASE + 1000); +}; diff --git a/netwerk/base/nsINestedURI.idl b/netwerk/base/nsINestedURI.idl new file mode 100644 index 000000000..48cd9b36a --- /dev/null +++ b/netwerk/base/nsINestedURI.idl @@ -0,0 +1,46 @@ +/* -*- 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, 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; +}; diff --git a/netwerk/base/nsINetAddr.idl b/netwerk/base/nsINetAddr.idl new file mode 100644 index 000000000..c7388354d --- /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::NetAddrToString. + * + * 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 000000000..800a9ae90 --- /dev/null +++ b/netwerk/base/nsINetUtil.idl @@ -0,0 +1,230 @@ +/* -*- 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); + + /** + * Take aURI and produce an immutable version of it for the caller. If aURI + * is immutable this will be aURI itself; otherwise this will be a clone, + * marked immutable if possible. Passing null to this method is allowed; in + * that case it will return null. + */ + nsIURI toImmutableURI(in nsIURI aURI); + + /** + * Create a simple nested URI using the result of + * toImmutableURI on the passed-in aURI which may not be null. + * Note: The return URI will not have had its spec set yet. + */ + nsIURI newSimpleNestedURI(in nsIURI aURI); + + /** 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; + + /** + * 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 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); + +/** + * Parse an attribute referrer policy string (no-referrer, origin, unsafe-url) + * and return the according integer code (defined in nsIHttpChannel.idl) + * + * @param aPolicyString + * the policy string given as attribute + * @return aPolicyEnum + * referrer policy code from nsIHttpChannel.idl, (see parser in + * ReferrerPolicy.h for details) + */ + unsigned long parseAttributePolicyString(in AString aPolicyString); +}; diff --git a/netwerk/base/nsINetworkInfoService.idl b/netwerk/base/nsINetworkInfoService.idl new file mode 100644 index 000000000..bd8804508 --- /dev/null +++ b/netwerk/base/nsINetworkInfoService.idl @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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([array, size_is(aAddressArraySize)] in string aAddressArray, + in unsigned long aAddressArraySize); + 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 000000000..17d27de42 --- /dev/null +++ b/netwerk/base/nsINetworkInterceptController.idl @@ -0,0 +1,144 @@ +/* -*- 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 "nsIContentPolicyBase.idl" + +interface nsIChannel; +interface nsIConsoleReportCollector; +interface nsIOutputStream; +interface nsIURI; + +%{C++ +#include "nsIConsoleReportCollector.h" +namespace mozilla { +namespace dom { +class ChannelInfo; +} +} +%} + +[ptr] native ChannelInfo(mozilla::dom::ChannelInfo); + +/** + * 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. + */ + void resetInterception(); + + /** + * 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 has been + * synthesized and can now be read. No further header modification is allowed + * after this point. 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. + */ + void finishSynthesizedResponse(in ACString finalURLSpec); + + /** + * 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 cancel(in nsresult status); + + /** + * The synthesized response body to be produced. + */ + readonly attribute nsIOutputStream responseBody; + + /** + * 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; + +%{C++ + already_AddRefed<nsIConsoleReportCollector> + GetConsoleReportCollector() + { + nsCOMPtr<nsIConsoleReportCollector> reporter; + GetConsoleReportCollector(getter_AddRefs(reporter)); + return reporter.forget(); + } +%} + + /** + * 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 being requested by a channel + * @param aIsNavigate True if the request is for a navigation, false for a fetch. + */ + bool shouldPrepareForIntercept(in nsIURI aURI, in bool aIsNonSubresourceRequest); + + /** + * 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 000000000..456c71b6c --- /dev/null +++ b/netwerk/base/nsINetworkLinkService.idl @@ -0,0 +1,108 @@ +/* -*- 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" + +/** + * 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_2G = 5; + const unsigned long LINK_TYPE_3G = 6; + const unsigned long LINK_TYPE_4G = 7; + + /** + * 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; +}; + +%{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" + +/** + * 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 '2G' mobile connection (e.g. GSM, GPRS, EDGE) */ +#define NS_NETWORK_LINK_TYPE_2G "2g" + +/** A '3G' mobile connection (e.g. UMTS, CDMA) */ +#define NS_NETWORK_LINK_TYPE_3G "3g" + +/** A '4G' mobile connection (e.g. LTE, UMB) */ +#define NS_NETWORK_LINK_TYPE_4G "4g" +%} diff --git a/netwerk/base/nsINetworkPredictor.idl b/netwerk/base/nsINetworkPredictor.idl new file mode 100644 index 000000000..1b6b9576b --- /dev/null +++ b/netwerk/base/nsINetworkPredictor.idl @@ -0,0 +1,165 @@ +/* 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 nsILoadContext; +interface nsINetworkPredictorVerifier; + +typedef unsigned long PredictorPredictReason; +typedef unsigned long PredictorLearnReason; + +/** + * 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, 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 loadContext - The nsILoadContext 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. + */ + void predict(in nsIURI targetURI, + in nsIURI sourceURI, + in PredictorPredictReason reason, + in nsILoadContext loadContext, + 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 loadContext - The nsILoadContext for the page load that we are + * learning about. + */ + void learn(in nsIURI targetURI, + in nsIURI sourceURI, + in PredictorLearnReason reason, + in nsILoadContext loadContext); + + /** + * 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 nsIDocument; +class nsILoadContext; +class nsILoadGroup; +class nsINetworkPredictorVerifier; + +namespace mozilla { +namespace net { + +nsresult PredictorPredict(nsIURI *targetURI, + nsIURI *sourceURI, + PredictorPredictReason reason, + nsILoadContext *loadContext, + nsINetworkPredictorVerifier *verifier); + +nsresult PredictorLearn(nsIURI *targetURI, + nsIURI *sourceURI, + PredictorLearnReason reason, + nsILoadContext *loadContext); + +nsresult PredictorLearn(nsIURI *targetURI, + nsIURI *sourceURI, + PredictorLearnReason reason, + nsILoadGroup *loadGroup); + +nsresult PredictorLearn(nsIURI *targetURI, + nsIURI *sourceURI, + PredictorLearnReason reason, + nsIDocument *document); + +nsresult PredictorLearnRedirect(nsIURI *targetURI, + nsIChannel *channel, + nsILoadContext *loadContext); + +} // mozilla::net +} // mozilla +%} diff --git a/netwerk/base/nsINetworkPredictorVerifier.idl b/netwerk/base/nsINetworkPredictorVerifier.idl new file mode 100644 index 000000000..b00aecc07 --- /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/nsINetworkProperties.idl b/netwerk/base/nsINetworkProperties.idl new file mode 100644 index 000000000..30f809324 --- /dev/null +++ b/netwerk/base/nsINetworkProperties.idl @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 supplemental information + to that which is provided by the network info definition. It is + reasonable to expect it to grow. +*/ + + +[scriptable, builtinclass, uuid(0af94dec-7ffc-4301-8937-766c214ac688)] +interface nsINetworkProperties : nsISupports +{ + readonly attribute boolean isWifi; + readonly attribute unsigned long dhcpGateway; +}; diff --git a/netwerk/base/nsINullChannel.idl b/netwerk/base/nsINullChannel.idl new file mode 100644 index 000000000..6c03a2743 --- /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 000000000..0da79c18a --- /dev/null +++ b/netwerk/base/nsIOService.cpp @@ -0,0 +1,1880 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 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/ArrayUtils.h" +#include "mozilla/DebugOnly.h" + +#include "nsIOService.h" +#include "nsIDOMNode.h" +#include "nsIProtocolHandler.h" +#include "nsIFileProtocolHandler.h" +#include "nscore.h" +#include "nsIURI.h" +#include "prprf.h" +#include "nsIErrorService.h" +#include "netCore.h" +#include "nsIObserverService.h" +#include "nsIPrefService.h" +#include "nsXPCOM.h" +#include "nsIProxiedProtocolHandler.h" +#include "nsIProxyInfo.h" +#include "nsEscape.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "nsCRT.h" +#include "nsSecCheckWrapChannel.h" +#include "nsSimpleNestedURI.h" +#include "nsTArray.h" +#include "nsIConsoleService.h" +#include "nsIUploadChannel2.h" +#include "nsXULAppAPI.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsIProtocolProxyCallback.h" +#include "nsICancelable.h" +#include "nsINetworkLinkService.h" +#include "nsPISocketTransportService.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsURLHelper.h" +#include "nsPIDNSService.h" +#include "nsIProtocolProxyService2.h" +#include "MainThreadUtils.h" +#include "nsINode.h" +#include "nsIWidget.h" +#include "nsThreadUtils.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/dom/ContentParent.h" +#include "mozilla/net/CaptivePortalService.h" +#include "ReferrerPolicy.h" +#include "nsContentSecurityManager.h" +#include "nsContentUtils.h" +#include "xpcpublic.h" + +#ifdef MOZ_WIDGET_GONK +#include "nsINetworkManager.h" +#include "nsINetworkInterface.h" +#endif + +namespace mozilla { +namespace net { + +#define PORT_PREF_PREFIX "network.security.ports." +#define PORT_PREF(x) PORT_PREF_PREFIX x +#define MANAGE_OFFLINE_STATUS_PREF "network.manage-offline-status" +#define OFFLINE_MIRRORS_CONNECTIVITY "network.offline-mirrors-connectivity" + +// 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_NOTIFY_CHANGED_PREF "network.notify.changed" +#define NETWORK_CAPTIVE_PORTAL_PREF "network.captive-portal-service.enabled" + +#define MAX_RECURSION_COUNT 50 + +nsIOService* gIOService = nullptr; +static bool gHasWarnedUploadChannel2; + +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. +// +// TODO: I am sure that there are more ports to be added. +// This cut is based on the classic mozilla codebase + +int16_t gBadPortList[] = { + 1, // tcpmux + 7, // echo + 9, // discard + 11, // systat + 13, // daytime + 15, // netstat + 17, // qotd + 19, // chargen + 20, // ftp-data + 21, // ftp-cntl + 22, // ssh + 23, // telnet + 25, // smtp + 37, // time + 42, // name + 43, // nicname + 53, // domain + 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 + 139, // netbios + 143, // imap2 + 179, // BGP + 389, // ldap + 465, // smtp+ssl + 512, // print / exec + 513, // login + 514, // shell + 515, // printer + 526, // tempo + 530, // courier + 531, // Chat + 532, // netnews + 540, // uucp + 556, // remotefs + 563, // nntp+ssl + 587, // + 601, // + 636, // ldap+ssl + 993, // imap+ssl + 995, // pop3+ssl + 2049, // nfs + 3659, // apple-sasl / PasswordServer + 4045, // lockd + 6000, // x11 + 6665, // Alternate IRC [Apple addition] + 6666, // Alternate IRC [Apple addition] + 6667, // Standard IRC [Apple addition] + 6668, // Alternate IRC [Apple addition] + 6669, // Alternate IRC [Apple addition] + 0, // This MUST be zero so that we can populating the array +}; + +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; + +bool nsIOService::sTelemetryEnabled = false; + +//////////////////////////////////////////////////////////////////////////////// + +nsIOService::nsIOService() + : mOffline(true) + , mOfflineForProfileChange(false) + , mManageLinkStatus(false) + , mConnectivity(true) + , mOfflineMirrorsConnectivity(true) + , mSettingOffline(false) + , mSetOfflineValue(false) + , mShutdown(false) + , mHttpHandlerAlreadyShutingDown(false) + , mNetworkLinkServiceInitialized(false) + , mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY) + , mNetworkNotifyChanged(true) + , mLastOfflineStateChange(PR_IntervalNow()) + , mLastConnectivityChange(PR_IntervalNow()) + , mLastNetworkLinkChange(PR_IntervalNow()) + , mNetTearingDownStarted(0) +{ +} + +nsresult +nsIOService::Init() +{ + nsresult rv; + + // We need to get references to the DNS service so that we can shut it + // down later. If we wait until the nsIOService is being shut down, + // GetService will fail at that point. + + mDNSService = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("failed to get DNS service"); + return rv; + } + + // XXX hack until xpidl supports error info directly (bug 13423) + nsCOMPtr<nsIErrorService> errorService = do_GetService(NS_ERRORSERVICE_CONTRACTID); + if (errorService) { + errorService->RegisterErrorStringBundle(NS_ERROR_MODULE_NETWORK, NECKO_MSGS_URL); + } + else + NS_WARNING("failed to get error service"); + + InitializeCaptivePortalService(); + + // setup our bad port list stuff + for(int i=0; gBadPortList[i]; i++) + mRestrictedPortList.AppendElement(gBadPortList[i]); + + // Further modifications to the port list come from prefs + nsCOMPtr<nsIPrefBranch> prefBranch; + GetPrefBranch(getter_AddRefs(prefBranch)); + if (prefBranch) { + prefBranch->AddObserver(PORT_PREF_PREFIX, this, true); + prefBranch->AddObserver(MANAGE_OFFLINE_STATUS_PREF, this, true); + prefBranch->AddObserver(NECKO_BUFFER_CACHE_COUNT_PREF, this, true); + prefBranch->AddObserver(NECKO_BUFFER_CACHE_SIZE_PREF, this, true); + prefBranch->AddObserver(NETWORK_NOTIFY_CHANGED_PREF, this, true); + prefBranch->AddObserver(NETWORK_CAPTIVE_PORTAL_PREF, this, true); + PrefsChanged(prefBranch); + } + + // Register for profile change notifications + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, kProfileChangeNetTeardownTopic, true); + observerService->AddObserver(this, kProfileChangeNetRestoreTopic, true); + observerService->AddObserver(this, kProfileDoChange, true); + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true); + observerService->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true); + } + else + NS_WARNING("failed to get observer service"); + + Preferences::AddBoolVarCache(&sTelemetryEnabled, "toolkit.telemetry.enabled", false); + Preferences::AddBoolVarCache(&mOfflineMirrorsConnectivity, OFFLINE_MIRRORS_CONNECTIVITY, true); + + gIOService = this; + + InitializeNetworkLinkService(); + + SetOffline(false); + + return NS_OK; +} + + +nsIOService::~nsIOService() +{ + gIOService = nullptr; +} + +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(); + } + + return NS_OK; +} + +nsresult +nsIOService::InitializeSocketTransportService() +{ + nsresult rv = NS_OK; + + 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()) + { + 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; +} + +nsIOService* +nsIOService::GetInstance() { + if (!gIOService) { + gIOService = new nsIOService(); + if (!gIOService) + return nullptr; + NS_ADDREF(gIOService); + + nsresult rv = gIOService->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(gIOService); + return nullptr; + } + return gIOService; + } + NS_ADDREF(gIOService); + return gIOService; +} + +NS_IMPL_ISUPPORTS(nsIOService, + nsIIOService, + nsIIOService2, + nsINetUtil, + nsISpeculativeConnect, + nsIObserver, + nsIIOServiceInternal, + nsISupportsWeakReference) + +//////////////////////////////////////////////////////////////////////////////// + +nsresult +nsIOService::RecheckCaptivePortal() +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread"); + if (mCaptivePortalService) { + mCaptivePortalService->RecheckCaptivePortal(); + } + return NS_OK; +} + +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; + } + + PRNetAddr prAddr; + if (PR_StringToNetAddr(host.BeginReading(), &prAddr) != PR_SUCCESS) { + // 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. + return NS_OK; + } + + NetAddr netAddr; + PRNetAddrToNetAddr(&prAddr, &netAddr); + if (IsIPAddrLocal(&netAddr)) { + // Redirects to local IP addresses are probably captive portals + mCaptivePortalService->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; + } + return NS_OK; +} + +nsresult +nsIOService::CacheProtocolHandler(const char *scheme, nsIProtocolHandler *handler) +{ + MOZ_ASSERT(NS_IsMainThread()); + + for (unsigned int i=0; i<NS_N(gScheme); i++) + { + if (!nsCRT::strcasecmp(scheme, gScheme[i])) + { + nsresult rv; + NS_ASSERTION(!mWeakHandler[i], "Protocol handler already cached"); + // Make sure the handler supports weak references. + nsCOMPtr<nsISupportsWeakReference> factoryPtr = do_QueryInterface(handler, &rv); + if (!factoryPtr) + { + // Don't cache handlers that don't support weak reference as + // there is real danger of a circular reference. +#ifdef DEBUG_dp + printf("DEBUG: %s protcol handler doesn't support weak ref. Not cached.\n", scheme); +#endif /* DEBUG_dp */ + return NS_ERROR_FAILURE; + } + mWeakHandler[i] = do_GetWeakReference(handler); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +nsresult +nsIOService::GetCachedProtocolHandler(const char *scheme, nsIProtocolHandler **result, uint32_t start, uint32_t end) +{ + MOZ_ASSERT(NS_IsMainThread()); + + uint32_t len = end - start - 1; + for (unsigned int i=0; i<NS_N(gScheme); i++) + { + if (!mWeakHandler[i]) + continue; + + // handle unterminated strings + // start is inclusive, end is exclusive, len = end - start - 1 + if (end ? (!nsCRT::strncasecmp(scheme + start, gScheme[i], len) + && gScheme[i][len] == '\0') + : (!nsCRT::strcasecmp(scheme, gScheme[i]))) + { + return CallQueryReferent(mWeakHandler[i].get(), result); + } + } + return NS_ERROR_FAILURE; +} + +static bool +UsesExternalProtocolHandler(const char* aScheme) +{ + if (NS_LITERAL_CSTRING("file").Equals(aScheme) || + NS_LITERAL_CSTRING("chrome").Equals(aScheme) || + NS_LITERAL_CSTRING("resource").Equals(aScheme)) { + // 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; + } + + nsAutoCString pref("network.protocol-handler.external."); + pref += aScheme; + + return Preferences::GetBool(pref.get(), false); +} + +NS_IMETHODIMP +nsIOService::GetProtocolHandler(const char* scheme, nsIProtocolHandler* *result) +{ + nsresult rv; + + NS_ENSURE_ARG_POINTER(scheme); + // XXX we may want to speed this up by introducing our own protocol + // scheme -> protocol handler mapping, avoiding the string manipulation + // and service manager stuff + + rv = GetCachedProtocolHandler(scheme, result); + if (NS_SUCCEEDED(rv)) + return rv; + + if (!UsesExternalProtocolHandler(scheme)) { + nsAutoCString contractID(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX); + contractID += scheme; + ToLowerCase(contractID); + + rv = CallGetService(contractID.get(), result); + if (NS_SUCCEEDED(rv)) { + CacheProtocolHandler(scheme, *result); + return rv; + } + +#ifdef MOZ_ENABLE_GIO + // check to see whether GVFS can handle this URI scheme. if it can + // create a nsIURI for the "scheme:", then we assume it has support for + // the requested protocol. otherwise, we failover to using the default + // protocol handler. + + rv = CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"moz-gio", + result); + if (NS_SUCCEEDED(rv)) { + nsAutoCString spec(scheme); + spec.Append(':'); + + nsIURI *uri; + rv = (*result)->NewURI(spec, nullptr, nullptr, &uri); + if (NS_SUCCEEDED(rv)) { + NS_RELEASE(uri); + return rv; + } + + NS_RELEASE(*result); + } +#endif + } + + // Okay we don't have a protocol handler to handle this url type, so use + // the default protocol handler. This will cause urls to get dispatched + // out to the OS ('cause we can't do anything with them) when we try to + // read from a channel created by the default protocol handler. + + rv = CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default", + result); + if (NS_FAILED(rv)) + return NS_ERROR_UNKNOWN_PROTOCOL; + + return rv; +} + +NS_IMETHODIMP +nsIOService::ExtractScheme(const nsACString &inURI, nsACString &scheme) +{ + return net_ExtractURLScheme(inURI, scheme); +} + +NS_IMETHODIMP +nsIOService::GetProtocolFlags(const char* scheme, uint32_t *flags) +{ + nsCOMPtr<nsIProtocolHandler> handler; + nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler)); + if (NS_FAILED(rv)) return rv; + + // We can't call DoGetProtocolFlags here because we don't have a URI. This + // API is used by (and only used by) extensions, which is why it's still + // around. Calling this on a scheme with dynamic flags will throw. + rv = handler->GetProtocolFlags(flags); + return rv; +} + +class AutoIncrement +{ + public: + explicit AutoIncrement(uint32_t *var) : mVar(var) + { + ++*var; + } + ~AutoIncrement() + { + --*mVar; + } + private: + uint32_t *mVar; +}; + +nsresult +nsIOService::NewURI(const nsACString &aSpec, const char *aCharset, nsIURI *aBaseURI, nsIURI **result) +{ + NS_ASSERTION(NS_IsMainThread(), "wrong thread"); + + static uint32_t recursionCount = 0; + if (recursionCount >= MAX_RECURSION_COUNT) + return NS_ERROR_MALFORMED_URI; + AutoIncrement inc(&recursionCount); + + nsAutoCString scheme; + nsresult rv = ExtractScheme(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 aBaseURI->CloneWithNewRef(aSpec, result); + } + + rv = aBaseURI->GetScheme(scheme); + if (NS_FAILED(rv)) return rv; + } + + // now get the handler for this scheme + nsCOMPtr<nsIProtocolHandler> handler; + rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); + if (NS_FAILED(rv)) return rv; + + return handler->NewURI(aSpec, aCharset, aBaseURI, result); +} + + +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); +} + +NS_IMETHODIMP +nsIOService::NewChannelFromURI2(nsIURI* aURI, + nsIDOMNode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + uint32_t aSecurityFlags, + uint32_t aContentPolicyType, + nsIChannel** result) +{ + return NewChannelFromURIWithProxyFlags2(aURI, + nullptr, // aProxyURI + 0, // aProxyFlags + aLoadingNode, + aLoadingPrincipal, + aTriggeringPrincipal, + aSecurityFlags, + aContentPolicyType, + result); +} + +/* ***** DEPRECATED ***** + * please use NewChannelFromURI2 providing the right arguments for: + * * aLoadingNode + * * aLoadingPrincipal + * * aTriggeringPrincipal + * * aSecurityFlags + * * aContentPolicyType + * + * See nsIIoService.idl for a detailed description of those arguments + */ +NS_IMETHODIMP +nsIOService::NewChannelFromURI(nsIURI *aURI, nsIChannel **result) +{ + NS_ASSERTION(false, "Deprecated, use NewChannelFromURI2 providing loadInfo arguments!"); + + const char16_t* params[] = { + u"nsIOService::NewChannelFromURI()", + u"nsIOService::NewChannelFromURI2()" + }; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Security by Default"), + nullptr, // aDocument + nsContentUtils::eNECKO_PROPERTIES, + "APIDeprecationWarning", + params, ArrayLength(params)); + + return NewChannelFromURI2(aURI, + nullptr, // aLoadingNode + nsContentUtils::GetSystemPrincipal(), + nullptr, // aTriggeringPrincipal + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + result); +} + +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, + nsILoadInfo* aLoadInfo, + nsIChannel** result) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aURI); + + 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; + + uint32_t protoFlags; + rv = handler->DoGetProtocolFlags(aURI, &protoFlags); + if (NS_FAILED(rv)) + return rv; + + // Ideally we are creating new channels by calling NewChannel2 (NewProxiedChannel2). + // Keep in mind that Addons can implement their own Protocolhandlers, hence + // NewChannel2() might *not* be implemented. + // We do not want to break those addons, therefore we first try to create a channel + // calling NewChannel2(); if that fails: + // * we fall back to creating a channel by calling NewChannel() + // * wrap the addon channel + // * and attach the loadInfo to the channel wrapper + nsCOMPtr<nsIChannel> channel; + nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler); + if (pph) { + rv = pph->NewProxiedChannel2(aURI, nullptr, aProxyFlags, aProxyURI, + aLoadInfo, getter_AddRefs(channel)); + // if calling NewProxiedChannel2() fails we try to fall back to + // creating a new proxied channel by calling NewProxiedChannel(). + if (NS_FAILED(rv)) { + rv = pph->NewProxiedChannel(aURI, nullptr, aProxyFlags, aProxyURI, + getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(rv, rv); + + // The protocol handler does not implement NewProxiedChannel2, so + // maybe we need to wrap the channel (see comment in MaybeWrap + // function). + channel = nsSecCheckWrapChannel::MaybeWrap(channel, aLoadInfo); + } + } + else { + rv = handler->NewChannel2(aURI, aLoadInfo, getter_AddRefs(channel)); + // if calling newChannel2() fails we try to fall back to + // creating a new channel by calling NewChannel(). + if (NS_FAILED(rv)) { + rv = handler->NewChannel(aURI, getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(rv, rv); + // The protocol handler does not implement NewChannel2, so + // maybe we need to wrap the channel (see comment in MaybeWrap + // function). + channel = nsSecCheckWrapChannel::MaybeWrap(channel, aLoadInfo); + } + } + + // Make sure that all the individual protocolhandlers attach a loadInfo. + if (aLoadInfo) { + // make sure we have the same instance of loadInfo on the newly created channel + nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo(); + 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(NS_LITERAL_STRING( + "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." + ).get()); + } + gHasWarnedUploadChannel2 = true; + } + } + + channel.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::NewChannelFromURIWithProxyFlags2(nsIURI* aURI, + nsIURI* aProxyURI, + uint32_t aProxyFlags, + nsIDOMNode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + uint32_t aSecurityFlags, + uint32_t aContentPolicyType, + nsIChannel** result) +{ + // Ideally all callers of NewChannelFromURIWithProxyFlags2 provide the + // necessary arguments to create a loadinfo. Keep in mind that addons + // might still call NewChannelFromURIWithProxyFlags() which forwards + // its calls to NewChannelFromURIWithProxyFlags2 using *null* values + // as the arguments for aLoadingNode, aLoadingPrincipal, and also + // aTriggeringPrincipal. + // We do not want to break those addons, hence we only create a Loadinfo + // if 'aLoadingNode' or 'aLoadingPrincipal' are provided. Note, that + // either aLoadingNode or aLoadingPrincipal is required to succesfully + // create a LoadInfo object. + // Except in the case of top level TYPE_DOCUMENT loads, where the + // loadingNode and loadingPrincipal are allowed to have null values. + nsCOMPtr<nsILoadInfo> loadInfo; + + // TYPE_DOCUMENT loads don't require a loadingNode or principal, but other + // types do. + if (aLoadingNode || aLoadingPrincipal || + aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) { + nsCOMPtr<nsINode> loadingNode(do_QueryInterface(aLoadingNode)); + loadInfo = new LoadInfo(aLoadingPrincipal, + aTriggeringPrincipal, + loadingNode, + aSecurityFlags, + aContentPolicyType); + } + NS_ASSERTION(loadInfo, "Please pass security info when creating a channel"); + return NewChannelFromURIWithProxyFlagsInternal(aURI, + aProxyURI, + aProxyFlags, + loadInfo, + result); +} + +/* ***** DEPRECATED ***** + * please use NewChannelFromURIWithProxyFlags2 providing the right arguments for: + * * aLoadingNode + * * aLoadingPrincipal + * * aTriggeringPrincipal + * * aSecurityFlags + * * aContentPolicyType + * + * See nsIIoService.idl for a detailed description of those arguments + */ +NS_IMETHODIMP +nsIOService::NewChannelFromURIWithProxyFlags(nsIURI *aURI, + nsIURI *aProxyURI, + uint32_t aProxyFlags, + nsIChannel **result) +{ + NS_ASSERTION(false, "Deprecated, use NewChannelFromURIWithProxyFlags2 providing loadInfo arguments!"); + + const char16_t* params[] = { + u"nsIOService::NewChannelFromURIWithProxyFlags()", + u"nsIOService::NewChannelFromURIWithProxyFlags2()" + }; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Security by Default"), + nullptr, // aDocument + nsContentUtils::eNECKO_PROPERTIES, + "APIDeprecationWarning", + params, ArrayLength(params)); + + return NewChannelFromURIWithProxyFlags2(aURI, + aProxyURI, + aProxyFlags, + nullptr, // aLoadingNode + nsContentUtils::GetSystemPrincipal(), + nullptr, // aTriggeringPrincipal + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + result); +} + +NS_IMETHODIMP +nsIOService::NewChannel2(const nsACString& aSpec, + const char* aCharset, + nsIURI* aBaseURI, + nsIDOMNode* aLoadingNode, + nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + uint32_t aSecurityFlags, + uint32_t aContentPolicyType, + nsIChannel** result) +{ + nsresult rv; + nsCOMPtr<nsIURI> uri; + rv = NewURI(aSpec, aCharset, aBaseURI, getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + return NewChannelFromURI2(uri, + aLoadingNode, + aLoadingPrincipal, + aTriggeringPrincipal, + aSecurityFlags, + aContentPolicyType, + result); +} + +/* ***** DEPRECATED ***** + * please use NewChannel2 providing the right arguments for: + * * aLoadingNode + * * aLoadingPrincipal + * * aTriggeringPrincipal + * * aSecurityFlags + * * aContentPolicyType + * + * See nsIIoService.idl for a detailed description of those arguments + */ +NS_IMETHODIMP +nsIOService::NewChannel(const nsACString &aSpec, const char *aCharset, nsIURI *aBaseURI, nsIChannel **result) +{ + NS_ASSERTION(false, "Deprecated, use NewChannel2 providing loadInfo arguments!"); + + const char16_t* params[] = { + u"nsIOService::NewChannel()", + u"nsIOService::NewChannel2()" + }; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Security by Default"), + nullptr, // aDocument + nsContentUtils::eNECKO_PROPERTIES, + "APIDeprecationWarning", + params, ArrayLength(params)); + + // Call NewChannel2 providing default arguments for the loadInfo. + return NewChannel2(aSpec, + aCharset, + aBaseURI, + nullptr, // aLoadingNode + nsContentUtils::GetSystemPrincipal(), // aLoadingPrincipal + nullptr, // aTriggeringPrincipal + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + result); +} + +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 (mOfflineMirrorsConnectivity) { + *offline = mOffline || !mConnectivity; + } else { + *offline = mOffline; + } + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::SetOffline(bool offline) +{ + 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"); + } + } + + nsIIOService *subject = static_cast<nsIIOService *>(this); + while (mSetOfflineValue != mOffline) { + offline = mSetOfflineValue; + + if (offline && !mOffline) { + NS_NAMED_LITERAL_STRING(offlineString, NS_IOSERVICE_OFFLINE); + mOffline = true; // indicate we're trying to shutdown + + // don't care if notifications fail + if (observerService) + observerService->NotifyObservers(subject, + NS_IOSERVICE_GOING_OFFLINE_TOPIC, + offlineString.get()); + + if (mSocketTransportService) + mSocketTransportService->SetOffline(true); + + mLastOfflineStateChange = PR_IntervalNow(); + if (observerService) + observerService->NotifyObservers(subject, + NS_IOSERVICE_OFFLINE_STATUS_TOPIC, + offlineString.get()); + } + else if (!offline && mOffline) { + // go online + if (mDNSService) { + DebugOnly<nsresult> rv = mDNSService->Init(); + NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service init failed"); + } + InitializeSocketTransportService(); + mOffline = false; // indicate success only AFTER we've + // brought up the services + + // trigger a PAC reload when we come back online + if (mProxyService) + mProxyService->ReloadPAC(); + + 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) { + // be sure to try and shutdown both (even if the first fails)... + // shutdown dns service first, because it has callbacks for socket transport + if (mDNSService) { + DebugOnly<nsresult> rv = mDNSService->Shutdown(); + NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service shutdown failed"); + } + 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 && !xpc::AreNonLocalConnectionsDisabled()) { + // 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 (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 + const nsLiteralString offlineString(u"" NS_IOSERVICE_OFFLINE); + observerService->NotifyObservers(static_cast<nsIIOService *>(this), + NS_IOSERVICE_GOING_OFFLINE_TOPIC, + offlineString.get()); + observerService->NotifyObservers(static_cast<nsIIOService *>(this), + NS_IOSERVICE_OFFLINE_STATUS_TOPIC, + offlineString.get()); + } + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::AllowPort(int32_t inPort, const char *scheme, bool *_retval) +{ + int16_t port = inPort; + if (port == -1) { + *_retval = true; + return NS_OK; + } + + if (port == 0) { + *_retval = false; + return NS_OK; + } + + // first check to see if the port is in our blacklist: + int32_t badPortListCnt = mRestrictedPortList.Length(); + for (int i=0; i<badPortListCnt; i++) + { + if (port == mRestrictedPortList[i]) + { + *_retval = false; + + // check to see if the protocol wants to override + if (!scheme) + 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; +} + +//////////////////////////////////////////////////////////////////////////////// + +void +nsIOService::PrefsChanged(nsIPrefBranch *prefs, const char *pref) +{ + if (!prefs) return; + + // Look for extra ports to block + if (!pref || strcmp(pref, PORT_PREF("banned")) == 0) + ParsePortList(prefs, PORT_PREF("banned"), false); + + // ...as well as previous blocks to remove. + if (!pref || strcmp(pref, PORT_PREF("banned.override")) == 0) + ParsePortList(prefs, PORT_PREF("banned.override"), true); + + if (!pref || strcmp(pref, MANAGE_OFFLINE_STATUS_PREF) == 0) { + bool manage; + if (mNetworkLinkServiceInitialized && + NS_SUCCEEDED(prefs->GetBoolPref(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(prefs->GetIntPref(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(prefs->GetIntPref(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_NOTIFY_CHANGED_PREF) == 0) { + bool allow; + nsresult rv = prefs->GetBoolPref(NETWORK_NOTIFY_CHANGED_PREF, &allow); + if (NS_SUCCEEDED(rv)) { + mNetworkNotifyChanged = allow; + } + } + + if (!pref || strcmp(pref, NETWORK_CAPTIVE_PORTAL_PREF) == 0) { + bool captivePortalEnabled; + nsresult rv = prefs->GetBoolPref(NETWORK_CAPTIVE_PORTAL_PREF, &captivePortalEnabled); + if (NS_SUCCEEDED(rv) && mCaptivePortalService) { + if (captivePortalEnabled && !xpc::AreNonLocalConnectionsDisabled()) { + static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Start(); + } else { + static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop(); + } + } + } +} + +void +nsIOService::ParsePortList(nsIPrefBranch *prefBranch, const char *pref, bool remove) +{ + nsXPIDLCString portList; + + // Get a pref string and chop it up into a list of ports. + prefBranch->GetCharPref(pref, getter_Copies(portList)); + if (portList) { + 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++) + mRestrictedPortList.RemoveElement(curPort); + } else { + for (curPort=portBegin; curPort <= portEnd; curPort++) + mRestrictedPortList.AppendElement(curPort); + } + } + } else { + nsresult aErrorCode; + int32_t port = portListArray[index].ToInteger(&aErrorCode); + if (NS_SUCCEEDED(aErrorCode) && port < 65536) { + if (remove) + mRestrictedPortList.RemoveElement(port); + else + mRestrictedPortList.AppendElement(port); + } + } + + } + } +} + +void +nsIOService::GetPrefBranch(nsIPrefBranch **result) +{ + *result = nullptr; + CallGetService(NS_PREFSERVICE_CONTRACTID, result); +} + +class nsWakeupNotifier : public Runnable +{ +public: + explicit nsWakeupNotifier(nsIIOServiceInternal *ioService) + :mIOService(ioService) + { } + + NS_IMETHOD Run() override + { + return mIOService->NotifyWakeup(); + } + +private: + virtual ~nsWakeupNotifier() { } + nsCOMPtr<nsIIOServiceInternal> mIOService; +}; + +NS_IMETHODIMP +nsIOService::NotifyWakeup() +{ + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + + NS_ASSERTION(observerService, "The observer service should not be null"); + + if (observerService && mNetworkNotifyChanged) { + (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 (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject); + if (prefBranch) + PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get()); + } else if (!strcmp(topic, kProfileChangeNetTeardownTopic)) { + if (!mHttpHandlerAlreadyShutingDown) { + mNetTearingDownStarted = PR_IntervalNow(); + } + mHttpHandlerAlreadyShutingDown = false; + if (!mOffline) { + mOfflineForProfileChange = true; + SetOffline(true); + } + } else if (!strcmp(topic, kProfileChangeNetRestoreTopic)) { + if (mOfflineForProfileChange) { + mOfflineForProfileChange = false; + SetOffline(false); + } + } else if (!strcmp(topic, kProfileDoChange)) { + if (data && NS_LITERAL_STRING("startup").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 + nsCOMPtr<nsIPrefBranch> prefBranch; + GetPrefBranch(getter_AddRefs(prefBranch)); + PrefsChanged(prefBranch, MANAGE_OFFLINE_STATUS_PREF); + } + } 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; + + SetOffline(true); + + if (mCaptivePortalService) { + static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop(); + mCaptivePortalService = nullptr; + } + + // Break circular reference. + mProxyService = nullptr; + } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) { + OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get()); + } 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); + + // Grab the protocol flags from the URI. + uint32_t protocolFlags; + nsCOMPtr<nsIProtocolHandler> handler; + rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); + NS_ENSURE_SUCCESS(rv, rv); + rv = handler->DoGetProtocolFlags(uri, &protocolFlags); + NS_ENSURE_SUCCESS(rv, rv); + + *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::ToImmutableURI(nsIURI* uri, nsIURI** result) +{ + if (!uri) { + *result = nullptr; + return NS_OK; + } + + nsresult rv = NS_EnsureSafeToReturn(uri, result); + NS_ENSURE_SUCCESS(rv, rv); + + NS_TryToSetImmutable(*result); + return NS_OK; +} + +NS_IMETHODIMP +nsIOService::NewSimpleNestedURI(nsIURI* aURI, nsIURI** aResult) +{ + NS_ENSURE_ARG(aURI); + + nsCOMPtr<nsIURI> safeURI; + nsresult rv = NS_EnsureSafeToReturn(aURI, getter_AddRefs(safeURI)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*aResult = new nsSimpleNestedURI(safeURI)); + return *aResult ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +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) +{ + LOG(("nsIOService::OnNetworkLinkEvent data:%s\n", data)); + if (!mNetworkLinkService) + return NS_ERROR_FAILURE; + + if (mShutdown) + return NS_ERROR_NOT_AVAILABLE; + + 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; + } else 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; +} + +// parse policyString to policy enum value (see ReferrerPolicy.h) +NS_IMETHODIMP +nsIOService::ParseAttributePolicyString(const nsAString& policyString, + uint32_t *outPolicyEnum) +{ + NS_ENSURE_ARG(outPolicyEnum); + *outPolicyEnum = (uint32_t)AttributeReferrerPolicyFromString(policyString); + return NS_OK; +} + +// nsISpeculativeConnect +class IOServiceProxyCallback final : public nsIProtocolProxyCallback +{ + ~IOServiceProxyCallback() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROTOCOLPROXYCALLBACK + + IOServiceProxyCallback(nsIInterfaceRequestor *aCallbacks, + nsIOService *aIOService) + : mCallbacks(aCallbacks) + , mIOService(aIOService) + { } + +private: + RefPtr<nsIInterfaceRequestor> mCallbacks; + RefPtr<nsIOService> mIOService; +}; + +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->GetLoadInfo(); + nsCOMPtr<nsIPrincipal> principal; + if (loadInfo) { + principal = loadInfo->LoadingPrincipal(); + } + + nsLoadFlags loadFlags = 0; + channel->GetLoadFlags(&loadFlags); + if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { + speculativeHandler->SpeculativeAnonymousConnect2(uri, principal, mCallbacks); + } else { + speculativeHandler->SpeculativeConnect2(uri, principal, mCallbacks); + } + + return NS_OK; +} + +nsresult +nsIOService::SpeculativeConnectInternal(nsIURI *aURI, + nsIPrincipal *aPrincipal, + nsIInterfaceRequestor *aCallbacks, + bool aAnonymous) +{ + if (IsNeckoChild()) { + ipc::URIParams params; + SerializeURI(aURI, params); + gNeckoChild->SendSpeculativeConnect(params, + IPC::Principal(aPrincipal), + 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; + + // If the principal is given, we use this prinicpal directly. Otherwise, + // we fallback to use the system principal. + if (!aPrincipal) { + nsCOMPtr<nsIScriptSecurityManager> secMan( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = secMan->GetSystemPrincipal(getter_AddRefs(loadingPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // 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 = NewChannelFromURI2(aURI, + nullptr, // aLoadingNode, + loadingPrincipal, + nullptr, //aTriggeringPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + 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); + nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps); + if (pps2) { + return pps2->AsyncResolve2(channel, 0, callback, getter_AddRefs(cancelable)); + } + return pps->AsyncResolve(channel, 0, callback, getter_AddRefs(cancelable)); +} + +NS_IMETHODIMP +nsIOService::SpeculativeConnect(nsIURI *aURI, + nsIInterfaceRequestor *aCallbacks) +{ + return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, false); +} + +NS_IMETHODIMP +nsIOService::SpeculativeConnect2(nsIURI *aURI, + nsIPrincipal *aPrincipal, + nsIInterfaceRequestor *aCallbacks) +{ + return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false); +} + +NS_IMETHODIMP +nsIOService::SpeculativeAnonymousConnect(nsIURI *aURI, + nsIInterfaceRequestor *aCallbacks) +{ + return SpeculativeConnectInternal(aURI, nullptr, aCallbacks, true); +} + +NS_IMETHODIMP +nsIOService::SpeculativeAnonymousConnect2(nsIURI *aURI, + nsIPrincipal *aPrincipal, + nsIInterfaceRequestor *aCallbacks) +{ + return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, true); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsIOService.h b/netwerk/base/nsIOService.h new file mode 100644 index 000000000..7ac23b791 --- /dev/null +++ b/netwerk/base/nsIOService.h @@ -0,0 +1,203 @@ +/* -*- 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/. */ + +#ifndef nsIOService_h__ +#define nsIOService_h__ + +#include "nsStringFwd.h" +#include "nsIIOService2.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsWeakPtr.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsINetUtil.h" +#include "nsIChannelEventSink.h" +#include "nsCategoryCache.h" +#include "nsISpeculativeConnect.h" +#include "nsDataHashtable.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "prtime.h" +#include "nsICaptivePortalService.h" + +#define NS_N(x) (sizeof(x)/sizeof(*x)) + +// 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" + +static const char gScheme[][sizeof("moz-safe-about")] = + {"chrome", "file", "http", "https", "jar", "data", "about", "moz-safe-about", "resource"}; + +class nsINetworkLinkService; +class nsIPrefBranch; +class nsIProtocolProxyService2; +class nsIProxyInfo; +class nsPIDNSService; +class nsPISocketTransportService; + +namespace mozilla { +namespace net { +class NeckoChild; +class nsAsyncRedirectVerifyHelper; + +class nsIOService final : public nsIIOService2 + , public nsIObserver + , public nsINetUtil + , public nsISpeculativeConnect + , public nsSupportsWeakReference + , public nsIIOServiceInternal +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIOSERVICE + NS_DECL_NSIIOSERVICE2 + NS_DECL_NSIOBSERVER + NS_DECL_NSINETUTIL + NS_DECL_NSISPECULATIVECONNECT + NS_DECL_NSIIOSERVICEINTERNAL + + // Gets the singleton instance of the IO Service, creating it as needed + // Returns nullptr on out of memory or failure to initialize. + // Returns an addrefed pointer. + static 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(); + + // Used to trigger a recheck of the captive portal status + nsresult RecheckCaptivePortal(); +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 GetCachedProtocolHandler(const char *scheme, + nsIProtocolHandler* *hdlrResult, + uint32_t start=0, + uint32_t end=0); + nsresult CacheProtocolHandler(const char *scheme, + nsIProtocolHandler* hdlr); + + nsresult InitializeCaptivePortalService(); + nsresult RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan); + + // Prefs wrangling + void PrefsChanged(nsIPrefBranch *prefs, const char *pref = nullptr); + void GetPrefBranch(nsIPrefBranch **); + void ParsePortList(nsIPrefBranch *prefBranch, const char *pref, bool remove); + + nsresult InitializeSocketTransportService(); + nsresult InitializeNetworkLinkService(); + + // 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, + nsILoadInfo* aLoadInfo, + nsIChannel** result); + + nsresult SpeculativeConnectInternal(nsIURI *aURI, + nsIPrincipal *aPrincipal, + nsIInterfaceRequestor *aCallbacks, + bool aAnonymous); + +private: + bool mOffline; + mozilla::Atomic<bool, mozilla::Relaxed> mOfflineForProfileChange; + bool mManageLinkStatus; + bool mConnectivity; + // If true, the connectivity state will be mirrored by IOService.offline + // meaning if !mConnectivity, GetOffline() will return true + bool mOfflineMirrorsConnectivity; + + // Used to handle SetOffline() reentrancy. See the comment in + // SetOffline() for more details. + bool mSettingOffline; + bool mSetOfflineValue; + + mozilla::Atomic<bool, mozilla::Relaxed> mShutdown; + mozilla::Atomic<bool, mozilla::Relaxed> mHttpHandlerAlreadyShutingDown; + + nsCOMPtr<nsPISocketTransportService> mSocketTransportService; + nsCOMPtr<nsPIDNSService> mDNSService; + nsCOMPtr<nsIProtocolProxyService2> mProxyService; + nsCOMPtr<nsICaptivePortalService> mCaptivePortalService; + nsCOMPtr<nsINetworkLinkService> mNetworkLinkService; + bool mNetworkLinkServiceInitialized; + + // Cached protocol handlers, only accessed on the main thread + nsWeakPtr mWeakHandler[NS_N(gScheme)]; + + // cached categories + nsCategoryCache<nsIChannelEventSink> mChannelEventSinks; + + nsTArray<int32_t> mRestrictedPortList; + + bool mNetworkNotifyChanged; + + static bool sTelemetryEnabled; + + // 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; +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 000000000..2858bb95e --- /dev/null +++ b/netwerk/base/nsIParentChannel.idl @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 nsITabParent; + +%{C++ +namespace mozilla { +namespace net { +class HttpChannelParentListener; +} +} +%} + +[ptr] native HttpChannelParentListener(mozilla::net::HttpChannelParentListener); + +/** + * Implemented by chrome side of IPC protocols. + */ + +[scriptable, uuid(e0fc4801-6030-4653-a59f-1fb282bd1a04)] +interface nsIParentChannel : nsIStreamListener +{ + /** + * Called to set the HttpChannelParentListener object (optional). + */ + [noscript] void setParentListener(in HttpChannelParentListener listener); + + /** + * Called to notify the HttpChannelChild that tracking protection was + * disabled for this load. + */ + [noscript] void notifyTrackingProtectionDisabled(); + + /** + * Called to invoke deletion of the IPC protocol. + */ + void delete(); +}; diff --git a/netwerk/base/nsIParentRedirectingChannel.idl b/netwerk/base/nsIParentRedirectingChannel.idl new file mode 100644 index 000000000..df37a0131 --- /dev/null +++ b/netwerk/base/nsIParentRedirectingChannel.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 "nsIParentChannel.idl" + +interface nsITabParent; +interface nsIChannel; +interface nsIAsyncVerifyRedirectCallback; + +/** + * 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 newChannelId + * id of the redirect channel obtained from nsIRedirectChannelRegistrar. + * @param newURI + * the URI we redirect to + * @param callback + * redirect result callback, usage is compatible with how + * nsIChannelEventSink defines it + */ + void startRedirect(in uint32_t newChannelId, + in nsIChannel newChannel, + in uint32_t redirectFlags, + in nsIAsyncVerifyRedirectCallback 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 HttpChannelParentListener::OnRedirectResult and kept + * as mActiveChannel and mRedirectChannel in that class. + */ + void completeRedirect(in boolean succeeded); +}; diff --git a/netwerk/base/nsIPermission.idl b/netwerk/base/nsIPermission.idl new file mode 100644 index 000000000..c5ddd90fe --- /dev/null +++ b/netwerk/base/nsIPermission.idl @@ -0,0 +1,77 @@ +/* -*- 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; + +[scriptable, uuid(bb409a51-2371-4fea-9dc9-b7286a458b8c)] +/** + * This interface defines a "permission" object, + * used to specify allowed/blocked objects from + * user-specified sites (cookies, images etc). + */ + +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; + + /** + * 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 000000000..b61817d4c --- /dev/null +++ b/netwerk/base/nsIPermissionManager.idl @@ -0,0 +1,271 @@ +/* -*- 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 nsIURI; +interface nsIObserver; +interface nsIPrincipal; +interface mozIDOMWindow; +interface nsIPermission; +interface nsISimpleEnumerator; + +[scriptable, 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. + */ + const uint32_t EXPIRE_NEVER = 0; + const uint32_t EXPIRE_SESSION = 1; + const uint32_t EXPIRE_TIME = 2; + + /** + * Add permission information for a given URI and permission type. This + * operation will cause the type string to be registered if it does not + * currently exist. If a permission already exists for a given type, it + * will be modified. + * + * @param uri the uri to add the permission for + * @param type a case-sensitive ASCII string, identifying the consumer. + * Consumers should choose this string to be unique, with + * respect to other consumers. + * @param permission an integer representing the desired action (e.g. allow + * or deny). The interpretation of this number is up to the + * consumer, and may represent different actions for different + * types. Consumers may use one of the enumerated permission + * actions defined above, for convenience. + * 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. + * @param expiretype a constant defining whether this permission should + * never expire (EXPIRE_NEVER), expire at the end of the + * session (EXPIRE_SESSION), or expire at a specified time + * (EXPIRE_TIME). + * @param expiretime an integer representation of when this permission + * should be forgotten (milliseconds since Jan 1 1970 0:00:00). + */ + void add(in nsIURI uri, + in string type, + in uint32_t permission, + [optional] in uint32_t expireType, + [optional] in int64_t expireTime); + + /** + * Get all custom permissions for a given URI. This will return + * an enumerator of all permissions which are not set to default + * and which belong to the matching prinicpal of the given URI. + * + * @param uri the URI to get all permissions for + */ + nsISimpleEnumerator getAllForURI(in nsIURI uri); + + /** + * 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 string typed, + in uint32_t permission, + [optional] in uint32_t expireType, + [optional] in int64_t expireTime); + + /** + * Remove permission information for a given URI and permission type. This will + * remove the permission for the entire host described by the uri, acting as the + * opposite operation to the add() method. + * + * @param uri the uri to remove the permission for + * @param type a case-sensitive ASCII string, identifying the consumer. + * The type must have been previously registered using the + * add() method. + */ + void remove(in nsIURI uri, + in string type); + + /** + * 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 string 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); + + /** + * Test whether a website has permission to perform the given action. + * @param uri the uri to be tested + * @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 testPermission(in nsIURI uri, + in string type); + + /** + * Test whether the principal has the permission to perform a given action. + * System principals will always have permissions granted. + */ + uint32_t testPermissionFromPrincipal(in nsIPrincipal principal, + in string type); + + /** + * Test whether the principal associated with the window's document has the + * permission to perform a given action. System principals will always + * have permissions granted. + */ + uint32_t testPermissionFromWindow(in mozIDOMWindow window, + in string type); + + /** + * Test whether a website has permission to perform the given action. + * This requires an exact hostname match, subdomains are not a match. + * @param uri the uri to be tested + * @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 testExactPermission(in nsIURI uri, + in string type); + + /** + * See testExactPermission() above. + * System principals will always have permissions granted. + */ + uint32_t testExactPermissionFromPrincipal(in nsIPrincipal principal, + in string type); + + /** + * Test whether a website has permission to perform the given action + * ignoring active sessions. + * System principals will always have permissions granted. + * + * @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 string 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, + * @see testExactPermission. If false, subdomains will + * also be searched, @see testPermission. + * @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 string type, + in boolean exactHost); + + /** + * Allows enumeration of all stored permissions + * @return an nsISimpleEnumerator interface that allows access to + * nsIPermission objects + */ + readonly attribute nsISimpleEnumerator enumerator; + + /** + * Remove all permissions that will match the origin pattern. + */ + void removePermissionsWithAttributes(in DOMString patternAsJSON); + + /** + * If the current permission is set to expire, reset the expiration time. If + * there is no permission or the current permission does not expire, this + * method will silently return. + * + * @param sessionExpiretime an integer representation of when this permission + * should be forgotten (milliseconds since + * Jan 1 1970 0:00:00), if it is currently + * EXPIRE_SESSION. + * @param sessionExpiretime an integer representation of when this permission + * should be forgotten (milliseconds since + * Jan 1 1970 0:00:00), if it is currently + * EXPIRE_TIME. + */ + void updateExpireTime(in nsIPrincipal principal, + in string type, + in boolean exactHost, + in uint64_t sessionExpireTime, + in uint64_t persistentExpireTime); + + /** + * Remove all current permission settings and get permission settings from + * chrome process. + */ + void refreshPermission(); +}; + +%{ 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 000000000..3bc822f01 --- /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 000000000..68f8bf059 --- /dev/null +++ b/netwerk/base/nsIProgressEventSink.idl @@ -0,0 +1,80 @@ +/* -*- 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 aContext + * if aRequest is a channel, then this parameter is the listener + * context passed to nsIChannel::asyncOpen. + * @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 nsISupports aContext, + 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 aContext + * if aRequest is a channel, then this parameter is the listener + * context passed to nsIChannel::asyncOpen. + * @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 nsISupports aContext, + in nsresult aStatus, + in wstring aStatusArg); + +}; diff --git a/netwerk/base/nsIPrompt.idl b/netwerk/base/nsIPrompt.idl new file mode 100644 index 000000000..e68c1a21c --- /dev/null +++ b/netwerk/base/nsIPrompt.idl @@ -0,0 +1,97 @@ +/* -*- 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 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); + + 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, + in wstring checkMsg, + inout boolean checkValue); + + boolean promptUsernameAndPassword(in wstring dialogTitle, + in wstring text, + inout wstring username, + inout wstring password, + in wstring checkMsg, + inout boolean checkValue); + + boolean select(in wstring dialogTitle, + in wstring text, + in uint32_t count, + [array, size_is(count)] in wstring selectList, + out long outSelection); +}; diff --git a/netwerk/base/nsIProtocolHandler.idl b/netwerk/base/nsIProtocolHandler.idl new file mode 100644 index 000000000..95dc00ece --- /dev/null +++ b/netwerk/base/nsIProtocolHandler.idl @@ -0,0 +1,321 @@ +/* -*- 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" +%} + +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. + */ + 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; + + /** + * The default port is 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. + */ + readonly attribute long defaultPort; + + /** + * Returns the protocol specific flags (see flag definitions below). + */ + readonly attribute unsigned long protocolFlags; + +%{C++ + // Helper method to get the protocol flags in the right way. + nsresult DoGetProtocolFlags(nsIURI* aURI, uint32_t* aFlags) + { + nsCOMPtr<nsIProtocolHandlerWithDynamicFlags> dh = do_QueryInterface(this); + return dh ? dh->GetFlagsForURI(aURI, aFlags) : GetProtocolFlags(aFlags); + } +%} + + /** + * Makes a URI object that is suitable for loading by this protocol, + * where the URI string is given as an UTF-8 string. The caller may + * provide the charset from which the URI string originated, so that + * the URI string can be translated back to that charset (if necessary) + * before communicating with, for example, the origin server of the URI + * string. (Many servers do not support UTF-8 IRIs at the present time, + * so we must be careful about tracking the native charset of the origin + * server.) + * + * @param aSpec - the URI string in UTF-8 encoding. depending + * on the protocol implementation, unicode character + * sequences may or may not be %xx escaped. + * @param aOriginCharset - the charset of the document 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 UTF-8 encoding is assumed (i.e., + * no charset transformation from aSpec). + * @param aBaseURI - if null, aSpec must specify an absolute URI. + * otherwise, aSpec may be resolved relative + * to aBaseURI, depending on the protocol. + * If the protocol has no concept of relative + * URI aBaseURI will simply be ignored. + */ + nsIURI newURI(in AUTF8String aSpec, + [optional] in string aOriginCharset, + [optional] in nsIURI aBaseURI); + + /** + * Constructs a new channel from the given URI for this protocol handler and + * sets the loadInfo for the constructed channel. + */ + nsIChannel newChannel2(in nsIURI aURI, in nsILoadInfo aLoadinfo); + + /** + * Constructs a new channel from the given URI for this protocol handler. + */ + nsIChannel newChannel(in nsIURI aURI); + + /** + * 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: Implementation must ignore any flags they do not understand. + */ + + /** + * standard full URI with authority component and concept of relative + * URIs (http, ftp, ...) + */ + 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, + * ftp, 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. | + * | | + * +-------------------------------------------------------------------+ + * + * 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 URI must be treated as if it + * had the URI_LOADABLE_BY_ANYONE flag set, for compatibility with protocol + * handlers written against Gecko 1.8 or earlier. In this case, there may + * be run-time warning messages indicating that a "default insecure" + * assumption is being made. At some point in the futures (Mozilla 2.0, + * most likely), these warnings will become errors. + */ + + /** + * 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); + + /** + * This protocol handler forbids accessing cookies e.g. for mail related + * protocols. + */ + const unsigned long URI_FORBIDS_COOKIE_ACCESS = (1<<15); + + /** + * 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<<16); + + /** + * 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<<17); + + /** + * URI is secure to load in an https page and should not be blocked + * by nsMixedContentBlocker + */ + const unsigned long URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT = (1<<18); + + /** + * 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. + */ + const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 19); + + /** + * If this flag is set, then the origin for this protocol is the full URI + * spec, not just the scheme + host + port. + */ + const unsigned long ORIGIN_IS_FULL_SPEC = (1 << 20); + + /** + * 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 << 21); +}; + +%{C++ +/** + * 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" + */ + +#define IS_ORIGIN_IS_FULL_SPEC_DEFINED 1 +%} diff --git a/netwerk/base/nsIProtocolProxyCallback.idl b/netwerk/base/nsIProtocolProxyCallback.idl new file mode 100644 index 000000000..96c2181ec --- /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 000000000..8798a49b4 --- /dev/null +++ b/netwerk/base/nsIProtocolProxyFilter.idl @@ -0,0 +1,74 @@ +/* -*- 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; + +/** + * 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 aProxyService + * A reference to the Protocol Proxy Service. This is passed so that + * implementations may easily access methods such as newProxyInfo. + * @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. + * + * @return 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 aProxyService.newProxyInfo to + * construct nsIProxyInfo objects. + */ + nsIProxyInfo applyFilter(in nsIProtocolProxyService aProxyService, + in nsIURI aURI, in nsIProxyInfo aProxy); +}; + +/** + * 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 aProxyService + * A reference to the Protocol Proxy Service. This is passed so that + * implementations may easily access methods such as newProxyInfo. + * @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. + * + * @return 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 aProxyService.newProxyInfo to + * construct nsIProxyInfo objects. + */ + nsIProxyInfo applyFilter(in nsIProtocolProxyService aProxyService, + in nsIChannel aChannel, in nsIProxyInfo aProxy); +}; diff --git a/netwerk/base/nsIProtocolProxyService.idl b/netwerk/base/nsIProtocolProxyService.idl new file mode 100644 index 000000000..7bbaa8d85 --- /dev/null +++ b/netwerk/base/nsIProtocolProxyService.idl @@ -0,0 +1,281 @@ +/* -*- 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; + +/** + * nsIProtocolProxyService provides methods to access information about + * various network proxies. + */ +[scriptable, 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. + * + * @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); + + /** + * 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 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 ACString aUsername, in ACString aPassword, + 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); + + /** + * 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; +}; diff --git a/netwerk/base/nsIProtocolProxyService2.idl b/netwerk/base/nsIProtocolProxyService2.idl new file mode 100644 index 000000000..6cd125e58 --- /dev/null +++ b/netwerk/base/nsIProtocolProxyService2.idl @@ -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/. */ + +#include "nsIProtocolProxyService.idl" + +/** + * An extension of nsIProtocolProxyService + */ +[scriptable, 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); +}; diff --git a/netwerk/base/nsIProxiedChannel.idl b/netwerk/base/nsIProxiedChannel.idl new file mode 100644 index 000000000..69fc34650 --- /dev/null +++ b/netwerk/base/nsIProxiedChannel.idl @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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; +}; + + diff --git a/netwerk/base/nsIProxiedProtocolHandler.idl b/netwerk/base/nsIProxiedProtocolHandler.idl new file mode 100644 index 000000000..604177c12 --- /dev/null +++ b/netwerk/base/nsIProxiedProtocolHandler.idl @@ -0,0 +1,55 @@ +/* -*- 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, 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 newProxiedChannel2(in nsIURI uri, in nsIProxyInfo proxyInfo, + in unsigned long proxyResolveFlags, + in nsIURI proxyURI, + in nsILoadInfo aLoadInfo); + + /** 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. + */ + nsIChannel newProxiedChannel(in nsIURI uri, in nsIProxyInfo proxyInfo, + in unsigned long proxyResolveFlags, + in nsIURI proxyURI); +}; diff --git a/netwerk/base/nsIProxyInfo.idl b/netwerk/base/nsIProxyInfo.idl new file mode 100644 index 000000000..46ab438b2 --- /dev/null +++ b/netwerk/base/nsIProxyInfo.idl @@ -0,0 +1,89 @@ +/* -*- 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; + + + /**************************************************************************** + * 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 000000000..29ee25bbe --- /dev/null +++ b/netwerk/base/nsIRandomGenerator.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 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); +}; diff --git a/netwerk/base/nsIRedirectChannelRegistrar.idl b/netwerk/base/nsIRedirectChannelRegistrar.idl new file mode 100644 index 000000000..c5cce0b1c --- /dev/null +++ b/netwerk/base/nsIRedirectChannelRegistrar.idl @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 and obtain a unique ID for that + * channel. + * + * Primarily used in HttpChannelParentListener::AsyncOnChannelRedirect to get + * a channel id sent to the HttpChannelChild being redirected. + */ + uint32_t registerChannel(in nsIChannel channel); + + /** + * 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 uint32_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 uint32_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 HttpChannelParentListener::OnRedirectResult callback + * to grab the created parent side of the channel and forward notifications + * to it. + */ + nsIParentChannel getParentChannel(in uint32_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 HttpChannelParentListener::OnRedirectResult callback. + */ + void deregisterChannels(in uint32_t id); +}; diff --git a/netwerk/base/nsIRedirectResultListener.idl b/netwerk/base/nsIRedirectResultListener.idl new file mode 100644 index 000000000..1afb3e9ec --- /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 boolean proceeding); +}; diff --git a/netwerk/base/nsIRequest.idl b/netwerk/base/nsIRequest.idl new file mode 100644 index 000000000..544612152 --- /dev/null +++ b/netwerk/base/nsIRequest.idl @@ -0,0 +1,217 @@ +/* -*- 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; + +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; + + /************************************************************************** + * The following flags control the flow of data into the cache. + */ + + /** + * This flag prevents loading of the request with an HTTP pipeline. + * Generally this is because the resource is expected to take a + * while to load and may cause head of line blocking problems. + */ + const unsigned long INHIBIT_PIPELINE = 1 << 6; + + /** + * 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. + */ + const unsigned long LOAD_FRESH_CONNECTION = 1 << 15; +}; diff --git a/netwerk/base/nsIRequestContext.idl b/netwerk/base/nsIRequestContext.idl new file mode 100644 index 000000000..b40ba7d18 --- /dev/null +++ b/netwerk/base/nsIRequestContext.idl @@ -0,0 +1,95 @@ +/* -*- 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; +} +} +%} + +[ptr] native SpdyPushCachePtr(mozilla::net::SpdyPushCache); + +/** + * 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. + */ +[scriptable, uuid(658e3e6e-8633-4b1a-8d66-fa9f72293e63)] +interface nsIRequestContext : nsISupports +{ + /** + * A unique identifier for this request context + */ + [noscript] readonly attribute nsID ID; + + /** + * 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. + */ + [noscript] attribute SpdyPushCachePtr spdyPushCache; + + /** + * This holds a cached value of the user agent override. + */ + [noscript] attribute ACString userAgentOverride; +}; + +/** + * 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 nsIDRef id); + + /** + * Create a new request context identifier + */ + nsID newRequestContextID(); + + /** + * Remove an existing request context from its ID + */ + void removeRequestContext(in nsIDRef id); +}; diff --git a/netwerk/base/nsIRequestObserver.idl b/netwerk/base/nsIRequestObserver.idl new file mode 100644 index 000000000..5ab94001f --- /dev/null +++ b/netwerk/base/nsIRequestObserver.idl @@ -0,0 +1,41 @@ +/* -*- 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 + * @param aContext user defined context + * + * An exception thrown from onStartRequest has the side-effect of + * causing the request to be canceled. + */ + void onStartRequest(in nsIRequest aRequest, + in nsISupports aContext); + + /** + * 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 aContext user defined context + * @param aStatusCode reason for stopping (NS_OK if completed successfully) + * + * An exception thrown from onStopRequest is generally ignored. + */ + void onStopRequest(in nsIRequest aRequest, + in nsISupports aContext, + in nsresult aStatusCode); +}; diff --git a/netwerk/base/nsIRequestObserverProxy.idl b/netwerk/base/nsIRequestObserverProxy.idl new file mode 100644 index 000000000..7b79f5342 --- /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 000000000..c58c14cc7 --- /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 000000000..21f4d0c29 --- /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 000000000..396aa42f8 --- /dev/null +++ b/netwerk/base/nsISecureBrowserUI.idl @@ -0,0 +1,24 @@ +/* -*- 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 mozIDOMWindowProxy; +interface nsIDOMElement; +interface nsIDocShell; + +[scriptable, uuid(718c662a-f810-4a80-a6c9-0b1810ecade2)] +interface nsISecureBrowserUI : nsISupports +{ + void init(in mozIDOMWindowProxy window); + void setDocShell(in nsIDocShell docShell); + + readonly attribute unsigned long state; +}; + +%{C++ +#define NS_SECURE_BROWSER_UI_CONTRACTID "@mozilla.org/secure_browser_ui;1" +%} diff --git a/netwerk/base/nsISecurityEventSink.idl b/netwerk/base/nsISecurityEventSink.idl new file mode 100644 index 000000000..568753023 --- /dev/null +++ b/netwerk/base/nsISecurityEventSink.idl @@ -0,0 +1,26 @@ +/* -*- 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; + +[scriptable, uuid(a71aee68-dd38-4736-bd79-035fea1a1ec6)] +interface nsISecurityEventSink : nsISupports +{ + + /** + * Fired when a security change occurs due to page transitions, + * or end document load. This interface should be called by + * a security package (eg Netscape Personal Security Manager) + * to notify nsIWebProgressListeners that security state has + * changed. State flags are in nsIWebProgressListener.idl + */ + + void onSecurityChange(in nsISupports i_Context, in unsigned long state); +}; + + + + diff --git a/netwerk/base/nsISecurityInfoProvider.idl b/netwerk/base/nsISecurityInfoProvider.idl new file mode 100644 index 000000000..0cf83e69c --- /dev/null +++ b/netwerk/base/nsISecurityInfoProvider.idl @@ -0,0 +1,21 @@ +/* -*- 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(b8cc9126-9319-4415-afd9-b82220d453ed)] +interface nsISecurityInfoProvider : nsISupports +{ + /** + * The security info for this provider, if any. + */ + readonly attribute nsISupports securityInfo; + + /** + * Whether this provider has transferred data. If it hasn't, its + * security info should be ignored. + */ + readonly attribute boolean hasTransferredData; +}; diff --git a/netwerk/base/nsISensitiveInfoHiddenURI.idl b/netwerk/base/nsISensitiveInfoHiddenURI.idl new file mode 100644 index 000000000..abb3f082b --- /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 000000000..740927f40 --- /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 000000000..fa54104c3 --- /dev/null +++ b/netwerk/base/nsIServerSocket.idl @@ -0,0 +1,237 @@ +/* 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); + + /** + * 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); + + /** + * 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 000000000..99169481f --- /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/nsISocketFilter.idl b/netwerk/base/nsISocketFilter.idl new file mode 100644 index 000000000..0846fa2ed --- /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 000000000..6395d6b5f --- /dev/null +++ b/netwerk/base/nsISocketTransport.idl @@ -0,0 +1,256 @@ +/* -*- 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" + +interface nsIInterfaceRequestor; +interface nsINetAddr; + +%{ C++ +#include "mozilla/BasePrincipal.h" +namespace mozilla { +namespace net { +union NetAddr; +} +} +%} +native NetAddr(mozilla::net::NetAddr); +[ptr] native NetAddrPtr(mozilla::net::NetAddr); +native NeckoOriginAttributes(mozilla::NeckoOriginAttributes); +[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes); + +/** + * 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, 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)] + NeckoOriginAttributes binaryGetOriginAttributes(); + + [noscript, nostdcall, binaryname(SetOriginAttributes)] + void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs); + + /** + * The platform-specific network interface id that this socket + * associated with. Note that this attribute can be only accessed + * in the socket thread. + */ + attribute ACString networkInterfaceId; + + /** + * 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(); + + /** + * Security info object returned from the secure socket provider. This + * object supports nsISSLSocketControl, nsITransportSecurityInfo, and + * possibly other interfaces. + * + * This attribute is only available once the socket is connected. + */ + readonly attribute nsISupports securityInfo; + + /** + * Security notification callbacks passed to the secure socket provider + * via nsISSLSocketControl 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); + + /** + * 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 = 0x804b0003; + const unsigned long STATUS_RESOLVED = 0x804b000b; + const unsigned long STATUS_CONNECTING_TO = 0x804b0007; + const unsigned long STATUS_CONNECTED_TO = 0x804b0004; + const unsigned long STATUS_SENDING_TO = 0x804b0005; + const unsigned long STATUS_WAITING_FOR = 0x804b000a; + const unsigned long STATUS_RECEIVING_FROM = 0x804b0006; + + /** + * 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); + + /** + * This flag is an explicit opt-in that allows a normally secure socket + * provider to use, at its discretion, an insecure algorithm. e.g. + * a TLS socket without authentication. + */ + const unsigned long MITM_OK = (1 << 6); + + /** + * 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 << 7); + + /** + * 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); +}; diff --git a/netwerk/base/nsISocketTransportService.idl b/netwerk/base/nsISocketTransportService.idl new file mode 100644 index 000000000..06350b532 --- /dev/null +++ b/netwerk/base/nsISocketTransportService.idl @@ -0,0 +1,135 @@ +/* -*- 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 nsIFile; +interface nsISocketTransport; +interface nsIProxyInfo; +interface nsIRunnable; + +%{C++ +class nsASocketHandler; +struct PRFileDesc; +%} + +[ptr] native PRFileDescPtr(PRFileDesc); +[ptr] native nsASocketHandlerPtr(nsASocketHandler); + +[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. null if using default socket type. + * @param aTypeCount + * specifies length of aSocketTypes. + * @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). + * + * @see nsIProxiedProtocolHandler + * @see nsIProtocolProxyService::GetProxyInfo + * + * NOTE: this function can be called from any thread + */ + nsISocketTransport createTransport([array, size_is(aTypeCount)] + in string aSocketTypes, + in unsigned long aTypeCount, + in AUTF8String aHost, + in long aPort, + in nsIProxyInfo aProxyInfo); + + /** + * 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); + + /** + * 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); +}; + +[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([array, size_is(aTypeCount)] + in string aSocketTypes, + in unsigned long aTypeCount, + in AUTF8String aHost, // origin + in long aPort, // origin + in AUTF8String aHostRoute, + in long aPortRoute, + in nsIProxyInfo aProxyInfo); +}; diff --git a/netwerk/base/nsISpeculativeConnect.idl b/netwerk/base/nsISpeculativeConnect.idl new file mode 100644 index 000000000..067edd3f0 --- /dev/null +++ b/netwerk/base/nsISpeculativeConnect.idl @@ -0,0 +1,77 @@ +/* -*- 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; + +[scriptable, 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 + * such as nsIBadCertListener. May be null. + * + */ + void speculativeConnect(in nsIURI aURI, + in nsIInterfaceRequestor aCallbacks); + + void speculativeConnect2(in nsIURI aURI, + in nsIPrincipal aPrincipal, + in nsIInterfaceRequestor aCallbacks); + + void speculativeAnonymousConnect(in nsIURI aURI, + in nsIInterfaceRequestor aCallbacks); + + void speculativeAnonymousConnect2(in nsIURI aURI, + in nsIPrincipal aPrincipal, + in nsIInterfaceRequestor aCallbacks); +}; + +/** + * This is used to override the default values for various values (documented + * inline) to determine whether or not to actually make a speculative + * connection. + */ +[builtinclass, 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 000000000..020555991 --- /dev/null +++ b/netwerk/base/nsIStandardURL.idl @@ -0,0 +1,80 @@ +/* -*- 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 "nsIMutable.idl" + +interface nsIURI; + +/** + * 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, uuid(babd6cca-ebe7-4329-967c-d6b9e33caa81)] +interface nsIStandardURL : nsIMutable +{ + /** + * 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; + + /** + * 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. + */ + void 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. + */ + void setDefaultPort(in long aNewDefaultPort); +}; diff --git a/netwerk/base/nsIStreamListener.idl b/netwerk/base/nsIStreamListener.idl new file mode 100644 index 000000000..09ee408a1 --- /dev/null +++ b/netwerk/base/nsIStreamListener.idl @@ -0,0 +1,40 @@ +/* -*- 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 aContext user defined context + * @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 nsISupports aContext, + 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 000000000..b7c3ae676 --- /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 "nsIStreamListener.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 : nsIStreamListener +{ + /** + * 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 000000000..274a07e9d --- /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 "nsIStreamListener.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 : nsIStreamListener +{ + /** + * 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 000000000..cd39f9cba --- /dev/null +++ b/netwerk/base/nsIStreamTransportService.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 "nsISupports.idl" + +interface nsITransport; +interface nsIInputStream; +interface nsIOutputStream; + +/** + * This service read/writes a stream on a background thread. + * + * 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. + */ +[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 aStartOffset + * The input stream will be read starting from this offset. Pass + * -1 to read from the current stream offset. NOTE: this parameter + * is ignored if the stream does not support nsISeekableStream. + * @param aReadLimit + * This parameter limits the number of bytes that will be read from + * the input stream. Pass -1 to read everything. + * @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 long long aStartOffset, + in long long aReadLimit, + in boolean aCloseWhenDone); + + /** + * CreateOutputTransport + * + * @param aStream + * The output stream that will be written to on a background thread. + * This stream must implement "blocking" stream semantics. + * @param aStartOffset + * The output stream will be written starting at this offset. Pass + * -1 to write to the current stream offset. NOTE: this parameter + * is ignored if the stream does not support nsISeekableStream. + * @param aWriteLimit + * This parameter limits the number of bytes that will be written to + * the output stream. Pass -1 for unlimited writing. + * @param aCloseWhenDone + * Specify this flag to have the output stream closed once its + * contents have been completely written. + * + * @return nsITransport instance. + */ + nsITransport createOutputTransport(in nsIOutputStream aStream, + in long long aStartOffset, + in long long aWriteLimit, + in boolean aCloseWhenDone); +}; diff --git a/netwerk/base/nsIStreamingProtocolController.idl b/netwerk/base/nsIStreamingProtocolController.idl new file mode 100644 index 000000000..249e8e983 --- /dev/null +++ b/netwerk/base/nsIStreamingProtocolController.idl @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set sw=4 ts=4 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/. */ +interface nsIURI; + +#include "nsISupports.idl" + +%{C++ +#define MEDIASTREAM_FRAMETYPE_NORMAL 0x00000001 +#define MEDIASTREAM_FRAMETYPE_DISCONTINUITY 0x00000002 +#define MEDIASTREAM_FRAMETYPE_END_OF_STREAM 0x00000004 +%} + +/** + * Metadata of the media stream. + */ +[uuid(294adb30-856c-11e2-9e96-0800200c9a66)] +interface nsIStreamingProtocolMetaData : nsISupports +{ + /** + * Frame type. + */ + attribute uint32_t frameType; + + /** + * The total tracks for the given media stream session. + */ + attribute uint32_t totalTracks; + + /** + * The mime type of the track. + */ + attribute ACString mimeType; + + /** + * The width of the resolution. + */ + attribute unsigned long width; + + /** + * The height of the resolution. + */ + attribute unsigned long height; + + /** + * The duration of the media stream in units of microseconds. + */ + attribute unsigned long long duration; + + /** + * The sample rate of the media stream. + */ + attribute unsigned long sampleRate; + + /** + * The timestamp indicates the stream absolute position + * relative to the beginning of the presentation. + */ + attribute unsigned long long timeStamp; + + /** + * The total number of audio channels in the media stream. + */ + attribute unsigned long channelCount; + + /** + * The AAC audio codec specific data. + */ + attribute ACString esdsData; + + /** + * The AVCC format extradata of H.264 stream. + */ + attribute ACString avccData; +}; + +/** + * nsIStreamingProtocolListener + */ +[scriptable, uuid(c4f6b660-892e-11e2-9e96-0800200c9a66)] +interface nsIStreamingProtocolListener : nsISupports +{ + /** + * Called when the data may be read without blocking the calling thread. + * @param index The track number of the media stream. + * @param data Raw data of the media stream on given track number. + * @param length The length of the raw data. + * @param offset The offset in the data stream from the start of the media + * presentation in bytes. + * @param meta The meta data of the frame. + */ + void onMediaDataAvailable(in uint8_t index, + in ACString data, + in uint32_t length, + in uint32_t offset, + in nsIStreamingProtocolMetaData meta); + + /** + * Called when the meta data for a given session is available. + * @param index The track number of the media stream. + * @param meta The meta data of the media stream. + */ + void onConnected(in uint8_t index, in nsIStreamingProtocolMetaData meta); + + /** + * Called when the Rtsp session is closed. + * @param index Track number of the media stream. + * @param reason The reason of disconnection. + */ + void onDisconnected(in uint8_t index, in nsresult reason); +}; + +/** + * Media stream controller API: control and retrieve meta data from media stream. + */ +[uuid(4ce040f0-c50d-461f-94e2-af5a77fe13a5)] +interface nsIStreamingProtocolController : nsISupports +{ + /** + * Preprare the URI before we can start the connection. + * @param aUri The URI of the media stream. + */ + void init(in nsIURI aUri); + + /** + * Asynchronously open this controller. Data is fed to the specified + * media stream listener as it becomes available. If asyncOpen returns + * successfully, the controller is responsible for keeping itself alive + * until it has called onStopRequest on aListener. + * + * @param aListener The nsIStreamingProtocolListener implementation + */ + void asyncOpen(in nsIStreamingProtocolListener aListener); + + /* + * Get the metadata of a track. + * @param index Index of a track. + * @return A nsIStreamingProtocolMetaData. + */ + nsIStreamingProtocolMetaData getTrackMetaData(in octet index); + + /* + * Tell the streaming server to start sending media data. + */ + void play(); + + /* + * Tell the streaming server to pause sending media data. + */ + void pause(); + + /* + * Tell the streaming server to resume the suspended media stream. + */ + void resume(); + + /* + * Tell the streaming server to suspend the media stream. + */ + void suspend(); + + /* + * Tell the streaming server to send media data in specific time. + * @param seekTimeUs Start time of the media stream in microseconds. + */ + void seek(in unsigned long long seekTimeUs); + + /* + * Tell the streaming server to stop the + * media stream and close the connection. + */ + void stop(); + + /* + * Notify the streaming controller that the playback has ended. + * The controller might have to perform certain internal state transition. + */ + void playbackEnded(); + + /** + * Total number of audio/video tracks. + */ + readonly attribute octet totalTracks; +}; diff --git a/netwerk/base/nsIStreamingProtocolService.idl b/netwerk/base/nsIStreamingProtocolService.idl new file mode 100644 index 000000000..a0fae164e --- /dev/null +++ b/netwerk/base/nsIStreamingProtocolService.idl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set sw=4 ts=4 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/. */ + +interface nsIStreamingProtocolController; +interface nsIChannel; + +#include "nsISupports.idl" + +%{C++ +#define NS_MEDIASTREAMCONTROLLERSERVICE_CID \ + { 0x94838530, 0x8627, 0x11e2, \ + { \ + 0x9e, 0x96, 0x08, 0x00, \ + 0x20, 0x0c, 0x9a, 0x66 \ + } \ + } +#define MEDIASTREAMCONTROLLERSERVICE_CONTRACTID \ + "@mozilla.org/mediastream/mediastreamcontrollerservice;1" +%} + +/** + * Media stream controller Service API. + */ +[uuid(94838530-8627-11e2-9e96-0800200c9a66)] +interface nsIStreamingProtocolControllerService : nsISupports +{ + /* + * Create a new media stream controller from the given channel. + * @param channel nsIChannel for the given URI. + */ + nsIStreamingProtocolController create(in nsIChannel channel); +}; diff --git a/netwerk/base/nsISyncStreamListener.idl b/netwerk/base/nsISyncStreamListener.idl new file mode 100644 index 000000000..9a46dda78 --- /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 000000000..55be7f313 --- /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 000000000..9a03c2ead --- /dev/null +++ b/netwerk/base/nsITLSServerSocket.idl @@ -0,0 +1,201 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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; + + /** + * setSessionCache + * + * Whether the server should use a session cache. Defaults to true. This + * should be set before calling |asyncListen| if you wish to change the + * default. + */ + void setSessionCache(in boolean aSessionCache); + + /** + * 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); + + /** + * setCipherSuites + * + * The server's cipher suites that is used by the TLS handshake. + * This is required to be set before calling |asyncListen|. + */ + void setCipherSuites([array, size_is(aLength)] in unsigned short aCipherSuites, + in unsigned long aLength); + + /** + * 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 000000000..9a93b70c6 --- /dev/null +++ b/netwerk/base/nsIThreadRetargetableRequest.idl @@ -0,0 +1,35 @@ +/* -*- 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 nsIEventTarget; + +/** + * 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 nsIEventTarget aNewTarget); +}; diff --git a/netwerk/base/nsIThreadRetargetableStreamListener.idl b/netwerk/base/nsIThreadRetargetableStreamListener.idl new file mode 100644 index 000000000..428807a15 --- /dev/null +++ b/netwerk/base/nsIThreadRetargetableStreamListener.idl @@ -0,0 +1,33 @@ +/* -*- 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" + +/** + * nsIThreadRetargetableStreamListener + * + * To be used by classes which implement nsIStreamListener and whose + * OnDataAvailable callback may be retargeted for delivery off the main thread. + */ +[uuid(fb2304b8-f82f-4433-af68-d874a2ebbdc1)] +interface nsIThreadRetargetableStreamListener : nsISupports +{ + /** + * 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(); +}; + diff --git a/netwerk/base/nsIThrottledInputChannel.idl b/netwerk/base/nsIThrottledInputChannel.idl new file mode 100644 index 000000000..76b8cc2a5 --- /dev/null +++ b/netwerk/base/nsIThrottledInputChannel.idl @@ -0,0 +1,80 @@ +/* -*- 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); + + /** + * 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 000000000..6ec2d1ff8 --- /dev/null +++ b/netwerk/base/nsITimedChannel.idl @@ -0,0 +1,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 nsIPrincipal; +%{C++ +namespace mozilla { +class TimeStamp; +} +%} + +native TimeStamp(mozilla::TimeStamp); + +// 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 uint16_t redirectCount; + + [noscript] readonly attribute TimeStamp channelCreation; + [noscript] readonly attribute TimeStamp asyncOpen; + + // 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 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; + + // All following are PRTime versions of the above. + readonly attribute PRTime channelCreationTime; + readonly attribute PRTime asyncOpenTime; + readonly attribute PRTime domainLookupStartTime; + readonly attribute PRTime domainLookupEndTime; + readonly attribute PRTime connectStartTime; + 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; +}; diff --git a/netwerk/base/nsITraceableChannel.idl b/netwerk/base/nsITraceableChannel.idl new file mode 100644 index 000000000..d639d3310 --- /dev/null +++ b/netwerk/base/nsITraceableChannel.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 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. + * + * 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); +}; diff --git a/netwerk/base/nsITransport.idl b/netwerk/base/nsITransport.idl new file mode 100644 index 000000000..2730c3a29 --- /dev/null +++ b/netwerk/base/nsITransport.idl @@ -0,0 +1,163 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 = 0x804b0008; + const unsigned long STATUS_WRITING = 0x804b0009; +}; + +[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 (resolvable to a string using + * nsIErrorService). 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 000000000..9a92bba2b --- /dev/null +++ b/netwerk/base/nsIUDPSocket.idl @@ -0,0 +1,353 @@ +/* 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 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); + + /** + * 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. + * @param dataLength The maximum number of bytes to be written. + * @return number of bytes written. (0 or dataLength) + */ + unsigned long send(in AUTF8String host, in unsigned short port, + [const, array, size_is(dataLength)]in uint8_t data, + in unsigned long dataLength); + + /** + * 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. + * @param dataLength The maximum number of bytes to be written. + * @return number of bytes written. (0 or dataLength) + */ + unsigned long sendWithAddr(in nsINetAddr addr, + [const, array, size_is(dataLength)]in uint8_t data, + in unsigned long dataLength); + + /** + * 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. + * @param dataLength The maximum number of bytes to be written. + * @return number of bytes written. (0 or dataLength) + */ + [noscript] unsigned long sendWithAddress([const] in NetAddrPtr addr, + [const, array, size_is(dataLength)]in uint8_t data, + in unsigned long dataLength); + + /** + * 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; +}; + +/** + * 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, 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(); +}; diff --git a/netwerk/base/nsIURI.idl b/netwerk/base/nsIURI.idl new file mode 100644 index 000000000..2384c5fd9 --- /dev/null +++ b/netwerk/base/nsIURI.idl @@ -0,0 +1,290 @@ +/* -*- 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 set and query the most basic components of an URI. + * Subclasses, including nsIURL, impose greater structure on the URI. + * + * This interface follows Tim Berners-Lee's URI spec (RFC2396) [1], where the + * basic URI components are defined as such: + * <pre> + * ftp://username:password@hostname:portnumber/pathname#ref + * \ / \ / \ / \ /\ \ / + * - --------------- ------ -------- | - + * | | | | | | + * | | | | | 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]. + * + * Note also that the RFC defines #-separated fragment identifiers as being + * "not part of the URI". Despite this, we bundle them as part of the URI, for + * convenience. + * + * [1] http://www.ietf.org/rfc/rfc2396.txt + * [2] http://www.ietf.org/internet-drafts/draft-ietf-idn-idna-06.txt + * [3] http://www.ietf.org/internet-drafts/draft-masinter-url-i18n-08.txt + */ + +%{C++ +#include "nsStringGlue.h" + +#undef GetPort // XXX Windows! +#undef SetPort // XXX Windows! +%} + +/** + * 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 + * nsPrincipal is fixed to serialize its URIs as nsISupports (bug 662693). + */ +[scriptable, 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. Setting the spec causes + * the new spec to be parsed per the rules for the scheme the URI + * currently has. In particular, setting the spec to a URI string with a + * different scheme will generally produce incorrect results; no one + * outside of a protocol handler implementation should be doing that. If + * the URI stores information from the nsIIOService.newURI call used to + * create it other than just the parsed string, then behavior of this + * information on setting the spec attribute is undefined. + * + * Some characters may be escaped. + */ + 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.Assign("[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 RFC2396. Setting this is + * highly discouraged outside of a protocol handler implementation, since + * that will generally lead to incorrect results. + */ + attribute ACString scheme; + + /** + * The username:password (or username only if value doesn't contain a ':') + * + * Some characters may be escaped. + */ + attribute AUTF8String userPass; + + /** + * The optional username and password, assuming the preHost consists of + * username:password. + * + * Some characters may be escaped. + */ + attribute AUTF8String username; + attribute AUTF8String password; + + /** + * The host:port (or simply the host, if port == -1). + * + * If this attribute is set to a value that only has a host part, the port + * will not be reset. To reset the port as well use setHostAndPort. + * + * Characters are NOT escaped. + */ + attribute AUTF8String hostPort; + + /** + * This function will always set a host and a port. If the port part is + * empty, the value of the port will be set to the default value. + */ + void setHostAndPort(in AUTF8String hostport); + + /** + * The host is the internet domain name to which this URI refers. It could + * be an IPv4 (or IPv6) address literal. If supported, it could be a + * non-ASCII internationalized domain name. + * + * Characters are NOT escaped. + */ + attribute AUTF8String host; + + /** + * A port value of -1 corresponds to the protocol's default port (eg. -1 + * implies port 80 for http URIs). + */ + 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. + */ + attribute AUTF8String path; + + + /************************************************************************ + * 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). + */ + boolean schemeIs(in string scheme); + + /** + * Clones the current URI. + */ + nsIURI clone(); + + /** + * 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; + + /** + * The charset of the document from which this URI originated. An empty + * value implies UTF-8. + * + * If this value is something other than UTF-8 then the URI components + * (e.g., spec, prePath, username, etc.) will all be fully URL-escaped. + * Otherwise, the URI components may contain unescaped multibyte UTF-8 + * characters. + */ + readonly attribute ACString originCharset; + + /************************************************************************ + * 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. + */ + 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); + + /** + * Clones the current URI, clearing the 'ref' attribute in the clone. + */ + nsIURI cloneIgnoringRef(); + + /** + * Clones the current URI, replacing the 'ref' attribute in the clone with + * the ref supplied. + */ + nsIURI cloneWithNewRef(in AUTF8String newRef); + + /** + * 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; +}; diff --git a/netwerk/base/nsIURIClassifier.idl b/netwerk/base/nsIURIClassifier.idl new file mode 100644 index 000000000..a8f6098a7 --- /dev/null +++ b/netwerk/base/nsIURIClassifier.idl @@ -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/. */ + +#include "nsISupports.idl" + +interface nsIChannel; +interface nsIPrincipal; +interface nsIURI; + +/** + * Callback function for nsIURIClassifier lookups. + */ +[scriptable, function, uuid(8face46e-0c96-470f-af40-0037dcd797bd)] +interface nsIURIClassifierCallback : nsISupports +{ + /** + * Called by the URI classifier service when it is done checking a URI. + * + * Clients are responsible for associating callback objects with classify() + * calls. + * + * @param aErrorCode + * The error code with which the channel should be cancelled, or + * NS_OK if the load should continue normally. + */ + void onClassifyComplete(in nsresult aErrorCode); +}; + +/** + * The URI classifier service checks a URI against lists of phishing + * and malware sites. + */ +[scriptable, uuid(596620cc-76e3-4133-9d90-360e59a794cf)] +interface nsIURIClassifier : nsISupports +{ + /** + * Classify a Principal using its URI. + * + * @param aPrincipal + * The principal that should be checked by the URI classifier. + * @param aTrackingProtectionEnabled + * Whether or not to classify the given URI against tracking + * protection lists + * + * @param aCallback + * The URI classifier will call this callback when the URI has been + * classified. + * + * @return <code>false</code> if classification is not necessary. The + * callback will not be called. + * <code>true</code> if classification will be performed. The + * callback will be called. + */ + boolean classify(in nsIPrincipal aPrincipal, + in boolean aTrackingProtectionEnabled, + in nsIURIClassifierCallback aCallback); + + /** + * Synchronously classify a URI with a comma-separated string + * containing the given tables. This does not make network requests. + * The result is a comma-separated string of tables that match. + */ + ACString classifyLocalWithTables(in nsIURI aURI, in ACString aTables); +}; diff --git a/netwerk/base/nsIURIWithBlobImpl.idl b/netwerk/base/nsIURIWithBlobImpl.idl new file mode 100644 index 000000000..d055746cc --- /dev/null +++ b/netwerk/base/nsIURIWithBlobImpl.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; + +/** + * nsIURIWithBlobImpl is implemented by URIs which are associated with a + * specific BlobImpl. + */ +[builtinclass, uuid(331b41d3-3506-4ab5-bef9-aab41e3202a3)] +interface nsIURIWithBlobImpl : nsISupports +{ + /** + * The BlobImpl associated with the resource returned when loading this uri. + */ + readonly attribute nsISupports blobImpl; +}; diff --git a/netwerk/base/nsIURIWithPrincipal.idl b/netwerk/base/nsIURIWithPrincipal.idl new file mode 100644 index 000000000..6c63aaad9 --- /dev/null +++ b/netwerk/base/nsIURIWithPrincipal.idl @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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; + +/** + * nsIURIWithPrincipal is implemented by URIs which are associated with a + * specific principal. + */ +[scriptable, uuid(626a5c0c-bfd8-4531-8b47-a8451b0daa33)] +interface nsIURIWithPrincipal : nsISupports +{ + /** + * The principal associated with the resource returned when loading this + * uri. + */ + readonly attribute nsIPrincipal principal; + + /** + * The uri for the principal. + */ + readonly attribute nsIURI principalUri; +}; diff --git a/netwerk/base/nsIURIWithQuery.idl b/netwerk/base/nsIURIWithQuery.idl new file mode 100644 index 000000000..749b2773d --- /dev/null +++ b/netwerk/base/nsIURIWithQuery.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 "nsIURI.idl" + +/** + * nsIURIWithQuery is implemented by URIs which have a query parameter. + * This is useful for the URL API. + */ +[scriptable, uuid(367510ee-8556-435a-8f99-b5fd357e08cc)] +interface nsIURIWithQuery : nsIURI +{ + /** + * 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. + */ + 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. + */ + attribute AUTF8String query; +}; diff --git a/netwerk/base/nsIURL.idl b/netwerk/base/nsIURL.idl new file mode 100644 index 000000000..aeaa3f694 --- /dev/null +++ b/netwerk/base/nsIURL.idl @@ -0,0 +1,122 @@ +/* -*- 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 "nsIURIWithQuery.idl" + +/** + * 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, uuid(86adcd89-0b70-47a2-b0fe-5bb2c5f37e31)] +interface nsIURL : nsIURIWithQuery +{ + /************************************************************************* + * The URL path is broken down into the following principal components: + * + * attribute AUTF8String filePath; + * attribute AUTF8String query; + * + * These are inherited from nsIURIWithQuery. + */ + + /************************************************************************* + * 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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); + +}; diff --git a/netwerk/base/nsIURLParser.idl b/netwerk/base/nsIURLParser.idl new file mode 100644 index 000000000..3d6ac19b8 --- /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/nsIUnicharStreamLoader.idl b/netwerk/base/nsIUnicharStreamLoader.idl new file mode 100644 index 000000000..d4cdc3fb3 --- /dev/null +++ b/netwerk/base/nsIUnicharStreamLoader.idl @@ -0,0 +1,88 @@ +/* -*- 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 "nsIStreamListener.idl" + +interface nsIUnicharInputStream; +interface nsIUnicharStreamLoader; +interface nsIChannel; + +[scriptable, uuid(c2982b39-2e48-429e-92b7-99348a1633c5)] +interface nsIUnicharStreamLoaderObserver : nsISupports +{ + /** + * Called as soon as at least 512 octets of data have arrived. + * If the stream receives fewer than 512 octets of data in total, + * called upon stream completion but before calling OnStreamComplete. + * Will not be called if the stream receives no data at all. + * + * @param aLoader the unichar stream loader + * @param aContext the context parameter of the underlying channel + * @param aSegment up to 512 octets of raw data from the stream + * + * @return the name of the character set to be used to decode this stream + */ + ACString onDetermineCharset(in nsIUnicharStreamLoader aLoader, + in nsISupports aContext, + in ACString aSegment); + + /** + * Called when the entire stream has been loaded and decoded. + * + * @param aLoader the unichar stream loader + * @param aContext the context parameter of the underlying channel + * @param aStatus the status of the underlying channel + * @param aBuffer the contents of the stream, decoded to UTF-16. + * + * This method will always be called asynchronously by the + * nsUnicharIStreamLoader involved, on the thread that called the + * loader's init() method. If onDetermineCharset fails, + * onStreamComplete will still be called, but aStatus will be an + * error code. + */ + void onStreamComplete(in nsIUnicharStreamLoader aLoader, + in nsISupports aContext, + in nsresult aStatus, + in AString aBuffer); +}; + +/** + * Asynchronously load a channel, converting the data to UTF-16. + * + * To use this interface, first call init() with a + * nsIUnicharStreamLoaderObserver that will be notified when the data has been + * loaded. Then call asyncOpen() on the channel with the nsIUnicharStreamLoader + * as the listener. The context argument in the asyncOpen() call will be + * passed to the onStreamComplete() callback. + */ +[scriptable, uuid(afb62060-37c7-4713-8a84-4a0c1199ba5c)] +interface nsIUnicharStreamLoader : nsIStreamListener +{ + /** + * Initializes the unichar stream loader + * + * @param aObserver the observer to notify when a charset is needed and when + * the load is complete + */ + void init(in nsIUnicharStreamLoaderObserver aObserver); + + /** + * The channel attribute is only valid inside the onDetermineCharset + * and onStreamComplete callbacks. Otherwise it will be null. + */ + readonly attribute nsIChannel channel; + + /** + * The charset that onDetermineCharset returned, if that's been + * called. + */ + readonly attribute ACString charset; + + /** + * Get the raw bytes as seen on the wire prior to character converstion. + * Used by Subresource Integrity checker to generate the correct hash. + */ + readonly attribute ACString rawBuffer; +}; diff --git a/netwerk/base/nsIUploadChannel.idl b/netwerk/base/nsIUploadChannel.idl new file mode 100644 index 000000000..3ec81444a --- /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 000000000..2ea74ac35 --- /dev/null +++ b/netwerk/base/nsIUploadChannel2.idl @@ -0,0 +1,67 @@ +/* -*- 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 + * contians request headers. + */ + readonly attribute boolean uploadStreamHasHeaders; + + /** + * Ensure the upload stream, if any, is cloneable. This may involve + * async copying, so a callback runnable must be provided. It will + * invoked on the current thread when the upload stream is ready + * for cloning. If the stream is already cloneable, then the callback + * will be invoked synchronously. + */ + [noscript] + void ensureUploadStreamIsCloneable(in nsIRunnable aCallback); + + /** + * Clones the upload stream. May return failure if the upload stream + * is not cloneable. If this is not acceptable, use the + * ensureUploadStreamIsCloneable() method first. + */ + [noscript] + nsIInputStream cloneUploadStream(); +}; diff --git a/netwerk/base/nsIncrementalDownload.cpp b/netwerk/base/nsIncrementalDownload.cpp new file mode 100644 index 000000000..42cd6faa5 --- /dev/null +++ b/netwerk/base/nsIncrementalDownload.cpp @@ -0,0 +1,936 @@ +/* -*- 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 "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/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(500 * PR_USEC_PER_MSEC) // 500ms + +// Number of times to retry a failed byte-range request. +#define MAX_RETRY_COUNT 20 + +using namespace mozilla; + +//----------------------------------------------------------------------------- + +static nsresult +WriteToFile(nsIFile *lf, const char *data, uint32_t len, int32_t flags) +{ + PRFileDesc *fd; + int32_t mode = 0600; + nsresult rv; +#if defined(MOZ_WIDGET_GONK) + // The sdcard on a B2G phone looks like: + // d---rwx--- system sdcard_rw 1970-01-01 01:00:00 sdcard + // On the emulator, xpcshell fails when using 0600 mode to open the file, + // and 0660 works. + nsCOMPtr<nsIFile> parent; + rv = lf->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) { + return rv; + } + uint32_t parentPerm; + rv = parent->GetPermissions(&parentPerm); + if (NS_FAILED(rv)) { + return rv; + } + if ((parentPerm & 0700) == 0) { + // Parent directory has no owner-write, so try to use group permissions + // instead of owner permissions. + mode = 0660; + } +#endif + 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(); + +private: + ~nsIncrementalDownload() {} + 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<nsISupports> mObserverContext; + 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; + int32_t mChunkSize; + int32_t mInterval; + int64_t mTotalSize; + int64_t mCurrentSize; + uint32_t mLoadFlags; + int32_t mNonPartialCount; + nsresult mStatus; + bool mIsPending; + bool mDidOnStartRequest; + PRTime mLastProgressUpdate; + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; + nsCOMPtr<nsIChannel> mNewRedirectChannel; + nsCString mPartialValidator; + bool mCacheBust; +}; + +nsIncrementalDownload::nsIncrementalDownload() + : mChunkLen(0) + , mChunkSize(DEFAULT_CHUNK_SIZE) + , mInterval(DEFAULT_INTERVAL) + , mTotalSize(-1) + , mCurrentSize(-1) + , mLoadFlags(LOAD_NORMAL) + , mNonPartialCount(0) + , mStatus(NS_OK) + , mIsPending(false) + , mDidOnStartRequest(false) + , mLastProgressUpdate(0) + , mRedirectCallback(nullptr) + , mNewRedirectChannel(nullptr) + , mCacheBust(false) +{ +} + +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, mObserverContext, + mCurrentSize + mChunkLen, + mTotalSize); +} + +nsresult +nsIncrementalDownload::CallOnStartRequest() +{ + if (!mObserver || mDidOnStartRequest) + return NS_OK; + + mDidOnStartRequest = true; + return mObserver->OnStartRequest(this, mObserverContext); +} + +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, mObserverContext, mStatus); + mObserver = nullptr; + mObserverContext = nullptr; +} + +nsresult +nsIncrementalDownload::StartTimer(int32_t interval) +{ + nsresult rv; + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + return mTimer->Init(this, 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_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + 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(NS_LITERAL_CSTRING("Range"), range, false); + if (NS_FAILED(rv)) + return rv; + + if (!mPartialValidator.IsEmpty()) + http->SetRequestHeader(NS_LITERAL_CSTRING("If-Range"), + mPartialValidator, false); + + if (mCacheBust) { + http->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"), + NS_LITERAL_CSTRING("no-cache"), false); + http->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"), + NS_LITERAL_CSTRING("no-cache"), false); + } + } + + rv = channel->AsyncOpen2(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 || + rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + 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::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::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 = do_QueryInterface(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) +{ + NS_IF_ADDREF(*result = mURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalDownload::GetFinalURI(nsIURI **result) +{ + NS_IF_ADDREF(*result = mFinalURI); + 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; + mObserverContext = context; + mProgressSink = do_QueryInterface(observer); // ok if null + + mIsPending = true; + return NS_OK; +} + +// nsIRequestObserver + +NS_IMETHODIMP +nsIncrementalDownload::OnStartRequest(nsIRequest *request, + nsISupports *context) +{ + 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(NS_LITERAL_CSTRING("Content-Range"), 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; + http->GetResponseHeader(NS_LITERAL_CSTRING("Etag"), mPartialValidator); + if (StringBeginsWith(mPartialValidator, NS_LITERAL_CSTRING("W/"))) + mPartialValidator.Truncate(); // don't use weak validators + if (mPartialValidator.IsEmpty()) + http->GetResponseHeader(NS_LITERAL_CSTRING("Last-Modified"), mPartialValidator); + + if (code == 206) { + // OK, read the Content-Range header to determine the total size of this + // download file. + nsAutoCString buf; + rv = http->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"), 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, + nsISupports *context, + 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, + nsISupports *context, + 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(); + } + else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) { + mTimer = nullptr; + nsresult rv = ProcessTimeout(); + if (NS_FAILED(rv)) + Cancel(rv); + } + 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(NS_LITERAL_CSTRING("Accept-Encoding"), + NS_LITERAL_CSTRING(""), 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); + + NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range"); + + 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; + 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) { + newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"), + NS_LITERAL_CSTRING("no-cache"), false); + newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Pragma"), + NS_LITERAL_CSTRING("no-cache"), false); + } + + // 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(nsISupports *outer, const nsIID &iid, void **result) +{ + if (outer) + return NS_ERROR_NO_AGGREGATION; + + nsIncrementalDownload *d = new nsIncrementalDownload(); + if (!d) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(d); + nsresult rv = d->QueryInterface(iid, result); + NS_RELEASE(d); + return rv; +} diff --git a/netwerk/base/nsIncrementalStreamLoader.cpp b/netwerk/base/nsIncrementalStreamLoader.cpp new file mode 100644 index 000000000..a7298be3f --- /dev/null +++ b/netwerk/base/nsIncrementalStreamLoader.cpp @@ -0,0 +1,214 @@ +/* -*- 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 "GeckoProfiler.h" + +#include <limits> + +nsIncrementalStreamLoader::nsIncrementalStreamLoader() + : mData(), mBytesConsumed(0) +{ +} + +nsIncrementalStreamLoader::~nsIncrementalStreamLoader() +{ +} + +NS_IMETHODIMP +nsIncrementalStreamLoader::Init(nsIIncrementalStreamLoaderObserver* observer) +{ + NS_ENSURE_ARG_POINTER(observer); + mObserver = observer; + return NS_OK; +} + +nsresult +nsIncrementalStreamLoader::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + if (aOuter) return NS_ERROR_NO_AGGREGATION; + + nsIncrementalStreamLoader* it = new nsIncrementalStreamLoader(); + if (it == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(it); + nsresult rv = it->QueryInterface(aIID, aResult); + NS_RELEASE(it); + return rv; +} + +NS_IMPL_ISUPPORTS(nsIncrementalStreamLoader, nsIIncrementalStreamLoader, + nsIRequestObserver, nsIStreamListener, + nsIThreadRetargetableStreamListener) + +NS_IMETHODIMP +nsIncrementalStreamLoader::GetNumBytesRead(uint32_t* aNumBytes) +{ + *aNumBytes = mBytesConsumed + mData.length(); + return NS_OK; +} + +/* readonly attribute nsIRequest request; */ +NS_IMETHODIMP +nsIncrementalStreamLoader::GetRequest(nsIRequest **aRequest) +{ + NS_IF_ADDREF(*aRequest = mRequest); + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalStreamLoader::OnStartRequest(nsIRequest* request, nsISupports *ctxt) +{ + nsCOMPtr<nsIChannel> chan( do_QueryInterface(request) ); + if (chan) { + int64_t contentLength = -1; + chan->GetContentLength(&contentLength); + if (contentLength >= 0) { + if (uint64_t(contentLength) > std::numeric_limits<size_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; + } + } + } + mContext = ctxt; + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalStreamLoader::OnStopRequest(nsIRequest* request, nsISupports *ctxt, + nsresult aStatus) +{ + PROFILER_LABEL("nsIncrementalStreamLoader", "OnStopRequest", + js::ProfileEntry::Category::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; + mContext = 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); + } + } + } + + self->mBytesConsumed += consumedCount; + *writeCount = count; + + return NS_OK; +} + +NS_IMETHODIMP +nsIncrementalStreamLoader::OnDataAvailable(nsIRequest* request, nsISupports *ctxt, + 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; + return rv; +} + +void +nsIncrementalStreamLoader::ReleaseData() +{ + mData.clearAndFree(); +} + +NS_IMETHODIMP +nsIncrementalStreamLoader::CheckListenerChain() +{ + return NS_OK; +} diff --git a/netwerk/base/nsIncrementalStreamLoader.h b/netwerk/base/nsIncrementalStreamLoader.h new file mode 100644 index 000000000..f04d4a958 --- /dev/null +++ b/netwerk/base/nsIncrementalStreamLoader.h @@ -0,0 +1,54 @@ +/* -*- 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 nsIThreadRetargetableStreamListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINCREMENTALSTREAMLOADER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + + nsIncrementalStreamLoader(); + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + ~nsIncrementalStreamLoader(); + + 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 consumed bytes from the mData. + size_t mBytesConsumed; +}; + +#endif // nsIncrementalStreamLoader_h__ diff --git a/netwerk/base/nsInputStreamChannel.cpp b/netwerk/base/nsInputStreamChannel.cpp new file mode 100644 index 000000000..99877eebf --- /dev/null +++ b/netwerk/base/nsInputStreamChannel.cpp @@ -0,0 +1,112 @@ +/* -*- 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 "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); + + NS_ADDREF(*result = mContentStream); + 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) +{ + NS_IF_ADDREF(*stream = mContentStream); + 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 = mBaseURI; + NS_IF_ADDREF(*aBaseURI); + 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 000000000..3c1c6e83b --- /dev/null +++ b/netwerk/base/nsInputStreamChannel.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +#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() : + mIsSrcdocChannel(false) {} + +protected: + virtual ~nsInputStreamChannel() {} + + 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; +}; + +} // namespace net +} // namespace mozilla + +#endif // !nsInputStreamChannel_h__ diff --git a/netwerk/base/nsInputStreamPump.cpp b/netwerk/base/nsInputStreamPump.cpp new file mode 100644 index 000000000..19c2a790a --- /dev/null +++ b/netwerk/base/nsInputStreamPump.cpp @@ -0,0 +1,766 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sts=4 sw=4 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 "nsISeekableStream.h" +#include "nsITransport.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsThreadUtils.h" +#include "nsCOMPtr.h" +#include "mozilla/Logging.h" +#include "GeckoProfiler.h" +#include "nsIStreamListener.h" +#include "nsILoadGroup.h" +#include "nsNetCID.h" +#include <algorithm> + +static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID); + +// +// 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() + : mState(STATE_IDLE) + , mStreamOffset(0) + , mStreamLength(UINT64_MAX) + , mStatus(NS_OK) + , mSuspendCount(0) + , mLoadFlags(LOAD_NORMAL) + , mProcessingCallbacks(false) + , mWaitingForInputStreamReady(false) + , mCloseWhenDone(false) + , mRetargeting(false) + , mMonitor("nsInputStreamPump") +{ +} + +nsInputStreamPump::~nsInputStreamPump() +{ +} + +nsresult +nsInputStreamPump::Create(nsInputStreamPump **result, + nsIInputStream *stream, + int64_t streamPos, + int64_t streamLen, + uint32_t segsize, + uint32_t segcount, + bool closeWhenDone) +{ + nsresult rv = NS_ERROR_OUT_OF_MEMORY; + RefPtr<nsInputStreamPump> pump = new nsInputStreamPump(); + if (pump) { + rv = pump->Init(stream, streamPos, streamLen, + segsize, segcount, closeWhenDone); + 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) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + NS_ASSERTION(mAsyncStream, "PeekStream called without stream"); + + // See if the pipe is closed by checking the return of Available. + uint64_t dummy64; + nsresult 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, + nsIOService::gDefaultSegmentSize, + &dummy); +} + +nsresult +nsInputStreamPump::EnsureWaiting() +{ + mMonitor.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. + if (mState == STATE_STOP) { + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + if (mTargetThread != mainThread) { + mTargetThread = do_QueryInterface(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 wil 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_ISUPPORTS(nsInputStreamPump, + nsIRequest, + nsIThreadRetargetableRequest, + nsIInputStreamCallback, + nsIInputStreamPump) + +//----------------------------------------------------------------------------- +// nsInputStreamPump::nsIRequest +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsInputStreamPump::GetName(nsACString &result) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + result.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::IsPending(bool *result) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + *result = (mState != STATE_IDLE); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::GetStatus(nsresult *status) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + *status = mStatus; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::Cancel(nsresult status) +{ + MOZ_ASSERT(NS_IsMainThread()); + + ReentrantMonitorAutoEnter mon(mMonitor); + + LOG(("nsInputStreamPump::Cancel [this=%p status=%x]\n", + this, 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) { + mAsyncStream->CloseWithStatus(status); + if (mSuspendCount == 0) + EnsureWaiting(); + // Otherwise, 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. + } + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::Suspend() +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + LOG(("nsInputStreamPump::Suspend [this=%p]\n", this)); + NS_ENSURE_TRUE(mState != STATE_IDLE, NS_ERROR_UNEXPECTED); + ++mSuspendCount; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::Resume() +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + LOG(("nsInputStreamPump::Resume [this=%p]\n", this)); + NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED); + NS_ENSURE_TRUE(mState != STATE_IDLE, NS_ERROR_UNEXPECTED); + + if (--mSuspendCount == 0) + EnsureWaiting(); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::GetLoadFlags(nsLoadFlags *aLoadFlags) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::SetLoadFlags(nsLoadFlags aLoadFlags) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + mLoadFlags = aLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::GetLoadGroup(nsILoadGroup **aLoadGroup) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + NS_IF_ADDREF(*aLoadGroup = mLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::SetLoadGroup(nsILoadGroup *aLoadGroup) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + mLoadGroup = aLoadGroup; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsInputStreamPump::nsIInputStreamPump implementation +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsInputStreamPump::Init(nsIInputStream *stream, + int64_t streamPos, int64_t streamLen, + uint32_t segsize, uint32_t segcount, + bool closeWhenDone) +{ + NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS); + + mStreamOffset = uint64_t(streamPos); + if (int64_t(streamLen) >= int64_t(0)) + mStreamLength = uint64_t(streamLen); + mStream = stream; + mSegSize = segsize; + mSegCount = segcount; + mCloseWhenDone = closeWhenDone; + + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamPump::AsyncRead(nsIStreamListener *listener, nsISupports *ctxt) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS); + NS_ENSURE_ARG_POINTER(listener); + MOZ_ASSERT(NS_IsMainThread(), "nsInputStreamPump should be read from the " + "main thread only."); + + // + // OK, we need to use the stream transport service if + // + // (1) the stream is blocking + // (2) the stream does not support nsIAsyncInputStream + // + + bool nonBlocking; + nsresult rv = mStream->IsNonBlocking(&nonBlocking); + if (NS_FAILED(rv)) return rv; + + if (nonBlocking) { + mAsyncStream = do_QueryInterface(mStream); + // + // if the stream supports nsIAsyncInputStream, and if we need to seek + // to a starting offset, then we must do so here. in the non-async + // stream case, the stream transport service will take care of seeking + // for us. + // + if (mAsyncStream && (mStreamOffset != UINT64_MAX)) { + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream); + if (seekable) + seekable->Seek(nsISeekableStream::NS_SEEK_SET, mStreamOffset); + } + } + + if (!mAsyncStream) { + // ok, let's use the stream transport service to read this stream. + nsCOMPtr<nsIStreamTransportService> sts = + do_GetService(kStreamTransportServiceCID, &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsITransport> transport; + rv = sts->CreateInputTransport(mStream, mStreamOffset, mStreamLength, + mCloseWhenDone, getter_AddRefs(transport)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIInputStream> wrapper; + rv = transport->OpenInputStream(0, mSegSize, mSegCount, getter_AddRefs(wrapper)); + if (NS_FAILED(rv)) return rv; + + mAsyncStream = do_QueryInterface(wrapper, &rv); + if (NS_FAILED(rv)) return rv; + } + + // release our reference to the original stream. from this point forward, + // we only reference the "stream" via mAsyncStream. + mStream = nullptr; + + // mStreamOffset now holds the number of bytes currently read. we use this + // to enforce the mStreamLength restriction. + mStreamOffset = 0; + + // grab event queue (we must do this here by contract, since all notifications + // must go to the thread which called AsyncRead) + mTargetThread = do_GetCurrentThread(); + NS_ENSURE_STATE(mTargetThread); + + rv = EnsureWaiting(); + if (NS_FAILED(rv)) return rv; + + if (mLoadGroup) + mLoadGroup->AddRequest(this, nullptr); + + mState = STATE_START; + mListener = listener; + mListenerContext = ctxt; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsInputStreamPump::nsIInputStreamCallback implementation +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream *stream) +{ + LOG(("nsInputStreamPump::OnInputStreamReady [this=%p]\n", this)); + + PROFILER_LABEL("nsInputStreamPump", "OnInputStreamReady", + js::ProfileEntry::Category::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 monitor; and + // -- a boolean mProcessingCallbacks to detect parallel loops + // when exiting the monitor for callbacks. + ReentrantMonitorAutoEnter lock(mMonitor); + + // Prevent parallel execution during callbacks, while out of monitor. + if (mProcessingCallbacks) { + MOZ_ASSERT(!mProcessingCallbacks); + break; + } + mProcessingCallbacks = true; + if (mSuspendCount || mState == STATE_IDLE) { + 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; + NS_NOTREACHED("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()) { + 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 when we're switching event delivery to another + // thread and the input stream pump is suspended, otherwise + // OnStateStop() might be called off the main thread. See bug 1026951 + // comment #107 for the exact scenario. + if (mSuspendCount && mRetargeting) { + mState = nextState; + mWaitingForInputStreamReady = false; + break; + } + + // Wait asynchronously if there is still data to transfer, or we're + // switching event delivery to another thread. + if (!mSuspendCount && (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() +{ + mMonitor.AssertCurrentThreadIn(); + + PROFILER_LABEL("nsInputStreamPump", "OnStateStart", + js::ProfileEntry::Category::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; + } + + { + // Note: Must exit monitor for call to OnStartRequest to avoid + // deadlocks when calls to RetargetDeliveryTo for multiple + // nsInputStreamPumps are needed (e.g. nsHttpChannel). + mMonitor.Exit(); + rv = mListener->OnStartRequest(this, mListenerContext); + mMonitor.Enter(); + } + + // 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() +{ + mMonitor.AssertCurrentThreadIn(); + + PROFILER_LABEL("nsInputStreamPump", "OnStateTransfer", + js::ProfileEntry::Category::NETWORK); + + LOG((" OnStateTransfer [this=%p]\n", this)); + + // if canceled, go directly to STATE_STOP... + if (NS_FAILED(mStatus)) + return STATE_STOP; + + nsresult rv; + + uint64_t avail; + rv = mAsyncStream->Available(&avail); + LOG((" Available returned [stream=%x rv=%x avail=%llu]\n", mAsyncStream.get(), rv, avail)); + + if (rv == NS_BASE_STREAM_CLOSED) { + rv = NS_OK; + avail = 0; + } + else if (NS_SUCCEEDED(rv) && avail) { + // figure out how much data to report (XXX detect overflow??) + if (avail > mStreamLength - mStreamOffset) + avail = mStreamLength - mStreamOffset; + + if (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 nsISeekableStream::Tell). + int64_t offsetBefore; + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mAsyncStream); + if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) { + NS_NOTREACHED("Tell failed on readable stream"); + offsetBefore = 0; + } + + uint32_t odaAvail = + avail > UINT32_MAX ? + UINT32_MAX : uint32_t(avail); + + LOG((" calling OnDataAvailable [offset=%llu count=%llu(%u)]\n", + mStreamOffset, avail, odaAvail)); + + { + // Note: Must exit monitor for call to OnStartRequest to avoid + // deadlocks when calls to RetargetDeliveryTo for multiple + // nsInputStreamPumps are needed (e.g. nsHttpChannel). + mMonitor.Exit(); + rv = mListener->OnDataAvailable(this, mListenerContext, + mAsyncStream, mStreamOffset, + odaAvail); + mMonitor.Enter(); + } + + // 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 (seekable) { + // 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(seekable->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 stomp 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() +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + MOZ_ASSERT(NS_IsMainThread(), + "CallOnStateStop should only be called on the main thread."); + + mState = OnStateStop(); + return NS_OK; +} + +uint32_t +nsInputStreamPump::OnStateStop() +{ + mMonitor.AssertCurrentThreadIn(); + + if (!NS_IsMainThread()) { + // Hopefully temporary hack: OnStateStop should only run on the main + // thread, but we're seeing some rare off-main-thread calls. For now + // just redispatch to the main thread in release builds, and crash in + // debug builds. + MOZ_ASSERT(NS_IsMainThread(), + "OnStateStop should only be called on the main thread."); + nsresult rv = NS_DispatchToMainThread( + NewRunnableMethod(this, &nsInputStreamPump::CallOnStateStop)); + NS_ENSURE_SUCCESS(rv, STATE_IDLE); + return STATE_IDLE; + } + + PROFILER_LABEL("nsInputStreamPump", "OnStateStop", + js::ProfileEntry::Category::NETWORK); + + LOG((" OnStateStop [this=%p status=%x]\n", this, 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_IDLE; + } + + if (NS_FAILED(mStatus)) + mAsyncStream->CloseWithStatus(mStatus); + else if (mCloseWhenDone) + mAsyncStream->Close(); + + mAsyncStream = nullptr; + mTargetThread = nullptr; + mIsPending = false; + { + // Note: Must exit monitor for call to OnStartRequest to avoid + // deadlocks when calls to RetargetDeliveryTo for multiple + // nsInputStreamPumps are needed (e.g. nsHttpChannel). + mMonitor.Exit(); + mListener->OnStopRequest(this, mListenerContext, mStatus); + mMonitor.Enter(); + } + mListener = nullptr; + mListenerContext = nullptr; + + if (mLoadGroup) + mLoadGroup->RemoveRequest(this, nullptr, mStatus); + + return STATE_IDLE; +} + +//----------------------------------------------------------------------------- +// nsIThreadRetargetableRequest +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsInputStreamPump::RetargetDeliveryTo(nsIEventTarget* aNewTarget) +{ + ReentrantMonitorAutoEnter mon(mMonitor); + + 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; + } + + // 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=%x aNewTarget=%p] " + "%s listener [%p] rv[%x]", + this, aNewTarget, (mTargetThread == aNewTarget ? "success" : "failure"), + (nsIStreamListener*)mListener, rv)); + return rv; +} diff --git a/netwerk/base/nsInputStreamPump.h b/netwerk/base/nsInputStreamPump.h new file mode 100644 index 000000000..c9b7bd64c --- /dev/null +++ b/netwerk/base/nsInputStreamPump.h @@ -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/. */ + +#ifndef nsInputStreamPump_h__ +#define nsInputStreamPump_h__ + +#include "nsIInputStreamPump.h" +#include "nsIAsyncInputStream.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" + +class nsIInputStream; +class nsILoadGroup; +class nsIStreamListener; + +class nsInputStreamPump final : public nsIInputStreamPump + , public nsIInputStreamCallback + , public nsIThreadRetargetableRequest +{ + ~nsInputStreamPump(); + +public: + typedef mozilla::ReentrantMonitorAutoEnter ReentrantMonitorAutoEnter; + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSIINPUTSTREAMPUMP + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSITHREADRETARGETABLEREQUEST + + nsInputStreamPump(); + + static nsresult + Create(nsInputStreamPump **result, + nsIInputStream *stream, + int64_t streamPos = -1, + int64_t streamLen = -1, + uint32_t segsize = 0, + uint32_t segcount = 0, + bool closeWhenDone = false); + + typedef void (*PeekSegmentFun)(void *closure, const uint8_t *buf, + uint32_t bufLen); + /** + * 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 + }; + + nsresult EnsureWaiting(); + uint32_t OnStateStart(); + uint32_t OnStateTransfer(); + uint32_t OnStateStop(); + + uint32_t mState; + nsCOMPtr<nsILoadGroup> mLoadGroup; + nsCOMPtr<nsIStreamListener> mListener; + nsCOMPtr<nsISupports> mListenerContext; + nsCOMPtr<nsIEventTarget> mTargetThread; + nsCOMPtr<nsIInputStream> mStream; + nsCOMPtr<nsIAsyncInputStream> mAsyncStream; + uint64_t mStreamOffset; + uint64_t mStreamLength; + uint32_t mSegSize; + uint32_t mSegCount; + nsresult mStatus; + uint32_t mSuspendCount; + uint32_t mLoadFlags; + bool mIsPending; + // True while in OnInputStreamReady, calling OnStateStart, OnStateTransfer + // and OnStateStop. Used to prevent calls to AsyncWait during callbacks. + bool mProcessingCallbacks; + // True if waiting on the "input stream ready" callback. + bool mWaitingForInputStreamReady; + bool mCloseWhenDone; + bool mRetargeting; + // Protects state/member var accesses across multiple threads. + mozilla::ReentrantMonitor mMonitor; +}; + +#endif // !nsInputStreamChannel_h__ diff --git a/netwerk/base/nsLoadGroup.cpp b/netwerk/base/nsLoadGroup.cpp new file mode 100644 index 000000000..3b8cf4434 --- /dev/null +++ b/netwerk/base/nsLoadGroup.cpp @@ -0,0 +1,1087 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set sw=4 ts=4 sts=4 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 "mozilla/Logging.h" +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/Telemetry.h" +#include "nsITimedChannel.h" +#include "nsIInterfaceRequestor.h" +#include "nsIRequestObserver.h" +#include "nsIRequestContext.h" +#include "CacheObserver.h" +#include "MainThreadUtils.h" + +#include "mozilla/net/NeckoChild.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(nsISupports* outer) + : mForegroundCount(0) + , mLoadFlags(LOAD_NORMAL) + , mDefaultLoadFlags(0) + , mRequests(&sRequestHashOps, sizeof(RequestMapEntry)) + , mStatus(NS_OK) + , mPriority(PRIORITY_NORMAL) + , mIsCanceling(false) + , mDefaultLoadIsTimed(false) + , mTimedRequests(0) + , mCachedRequests(0) + , mTimedNonCachedRequestsUntilOnEndPageLoad(0) +{ + NS_INIT_AGGREGATED(outer); + LOG(("LOADGROUP [%x]: Created.\n", this)); +} + +nsLoadGroup::~nsLoadGroup() +{ + DebugOnly<nsresult> rv = Cancel(NS_BINDING_ABORTED); + NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed"); + + mDefaultLoadRequest = nullptr; + + if (mRequestContext) { + nsID rcid; + mRequestContext->GetID(&rcid); + + if (IsNeckoChild() && gNeckoChild) { + char rcid_str[NSID_LENGTH]; + rcid.ToProvidedString(rcid_str); + + nsCString rcid_nscs; + rcid_nscs.AssignASCII(rcid_str); + + gNeckoChild->SendRemoveRequestContext(rcid_nscs); + } else { + mRequestContextService->RemoveRequestContext(rcid); + } + } + + LOG(("LOADGROUP [%x]: Destroyed.\n", this)); +} + + +//////////////////////////////////////////////////////////////////////////////// +// nsISupports methods: + +NS_IMPL_AGGREGATED(nsLoadGroup) +NS_INTERFACE_MAP_BEGIN_AGGREGATED(nsLoadGroup) + NS_INTERFACE_MAP_ENTRY(nsILoadGroup) + NS_INTERFACE_MAP_ENTRY(nsPILoadGroupInternal) + NS_INTERFACE_MAP_ENTRY(nsILoadGroupChild) + NS_INTERFACE_MAP_ENTRY(nsIRequest) + NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +//////////////////////////////////////////////////////////////////////////////// +// 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) ? true : false; + 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; + NS_ASSERTION(request, "What? Null key in PLDHashTable entry?"); + + bool ok = !!aArray->AppendElement(request); + if (!ok) { + break; + } + 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::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) { + nsIRequest* request = requests.ElementAt(--count); + + NS_ASSERTION(request, "NULL request found in list."); + + if (!mRequests.Search(request)) { + // |request| was removed already + NS_RELEASE(request); + continue; + } + + if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { + nsAutoCString nameStr; + request->GetName(nameStr); + LOG(("LOADGROUP [%x]: Canceling request %x %s.\n", + this, request, nameStr.get())); + } + + // + // Remove the request from the load group... This may cause + // the OnStopRequest notification to fire... + // + // XXX: What should the context be? + // + (void)RemoveRequest(request, nullptr, status); + + // Cancel the request... + rv = request->Cancel(status); + + // Remember the first failure and return it... + if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) + firstError = rv; + + NS_RELEASE(request); + } + +#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) { + nsIRequest* request = 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 [%x]: Suspending request %x %s.\n", + this, request, nameStr.get())); + } + + // Suspend the request... + rv = request->Suspend(); + + // Remember the first failure and return it... + if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) + firstError = rv; + + NS_RELEASE(request); + } + + 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) { + nsIRequest* request = 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 [%x]: Resuming request %x %s.\n", + this, request, nameStr.get())); + } + + // Resume the request... + rv = request->Resume(); + + // Remember the first failure and return it... + if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) + firstError = rv; + + NS_RELEASE(request); + } + + 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::GetLoadGroup(nsILoadGroup **loadGroup) +{ + *loadGroup = mLoadGroup; + NS_IF_ADDREF(*loadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetLoadGroup(nsILoadGroup *loadGroup) +{ + mLoadGroup = loadGroup; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsILoadGroup methods: + +NS_IMETHODIMP +nsLoadGroup::GetDefaultLoadRequest(nsIRequest * *aRequest) +{ + *aRequest = mDefaultLoadRequest; + NS_IF_ADDREF(*aRequest); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetDefaultLoadRequest(nsIRequest *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 [%x]: Adding request %x %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 [%x]: 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); + + if (!(flags & nsIRequest::LOAD_BACKGROUND)) { + // Update the count of foreground URIs.. + mForegroundCount += 1; + + // + // 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 [%x]: Firing OnStartRequest for request %x." + "(foreground count=%d).\n", this, request, mForegroundCount)); + + rv = observer->OnStartRequest(request, ctxt); + if (NS_FAILED(rv)) { + LOG(("LOADGROUP [%x]: OnStartRequest for request %x FAILED.\n", + this, request)); + // + // The URI load has been canceled by the observer. Clean up + // the damage... + // + + mRequests.Remove(request); + + rv = NS_OK; + + mForegroundCount -= 1; + } + } + + // Ensure that we're part of our loadgroup while pending + if (mForegroundCount == 1 && mLoadGroup) { + mLoadGroup->AddRequest(this, nullptr); + } + + } + + return rv; +} + +NS_IMETHODIMP +nsLoadGroup::RemoveRequest(nsIRequest *request, nsISupports* ctxt, + nsresult aStatus) +{ + NS_ENSURE_ARG_POINTER(request); + nsresult rv; + + if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) { + nsAutoCString nameStr; + request->GetName(nameStr); + LOG(("LOADGROUP [%x]: Removing request %x %s status %x (count=%d).\n", + this, request, nameStr.get(), aStatus, mRequests.EntryCount() - 1)); + } + + // Make sure we have a owning reference to the request we're about + // to remove. + + nsCOMPtr<nsIRequest> kungFuDeathGrip(request); + + // + // 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 [%x]: Unable to remove request %x. 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; + } + else { + mTimedNonCachedRequestsUntilOnEndPageLoad++; + } + + 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(); + } + + // Undo any group priority delta... + if (mPriority != 0) + RescheduleRequest(request, -mPriority); + + nsLoadFlags flags; + rv = request->GetLoadFlags(&flags); + if (NS_FAILED(rv)) return rv; + + if (!(flags & nsIRequest::LOAD_BACKGROUND)) { + NS_ASSERTION(mForegroundCount > 0, "ForegroundCount messed up"); + mForegroundCount -= 1; + + // Fire the OnStopRequest out to the observer... + nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver); + if (observer) { + LOG(("LOADGROUP [%x]: Firing OnStopRequest for request %x." + "(foreground count=%d).\n", this, request, mForegroundCount)); + + rv = observer->OnStopRequest(request, ctxt, aStatus); + + if (NS_FAILED(rv)) { + LOG(("LOADGROUP [%x]: OnStopRequest for request %x FAILED.\n", + this, request)); + } + } + + // If that was the last request -> remove ourselves from loadgroup + if (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_IMETHODIMP +nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver) +{ + mObserver = do_GetWeakReference(aObserver); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetGroupObserver(nsIRequestObserver* *aResult) +{ + nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver); + *aResult = observer; + NS_IF_ADDREF(*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); + *aCallbacks = mCallbacks; + NS_IF_ADDREF(*aCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) +{ + mCallbacks = aCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::GetRequestContextID(nsID *aRCID) +{ + if (!mRequestContext) { + return NS_ERROR_NOT_AVAILABLE; + } + return mRequestContext->GetID(aRCID); +} + +//////////////////////////////////////////////////////////////////////////////// +// 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) +{ + NS_ADDREF(*aChildLoadGroup = this); + 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 + NS_ADDREF(*aRootLoadGroup = this); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsPILoadGroupInternal methods: + +NS_IMETHODIMP +nsLoadGroup::OnEndPageLoad(nsIChannel *aDefaultChannel) +{ + // for the moment, nothing to do here. + 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; +} + +NS_IMETHODIMP +nsLoadGroup::GetUserAgentOverrideCache(nsACString & aUserAgentOverrideCache) +{ + aUserAgentOverrideCache = mUserAgentOverrideCache; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroup::SetUserAgentOverrideCache(const nsACString & aUserAgentOverrideCache) +{ + mUserAgentOverrideCache = aUserAgentOverrideCache; + return NS_OK; +} + + +//////////////////////////////////////////////////////////////////////////////// + +void +nsLoadGroup::TelemetryReport() +{ + if (mDefaultLoadIsTimed) { + 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 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; + +#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 (!connectStart.IsNull() && !connectEnd.IsNull()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_TCP_CONNECTION, \ + 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()) { \ + if (!CacheObserver::UseNewCache()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE, \ + asyncOpen, cacheReadStart); \ + } else { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE_V2, \ + asyncOpen, cacheReadStart); \ + } \ + \ + if (!CacheObserver::UseNewCache()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_CACHE_READ_TIME, \ + cacheReadStart, cacheReadEnd); \ + } else { \ + 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, \ + asyncOpen, cacheReadEnd); \ + \ + if (!CacheObserver::UseNewCache()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED, \ + asyncOpen, cacheReadEnd); \ + } else { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED_V2, \ + asyncOpen, cacheReadEnd); \ + } \ + } \ + else if (!responseEnd.IsNull()) { \ + if (!CacheObserver::UseNewCache()) { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_COMPLETE_LOAD, \ + asyncOpen, responseEnd); \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET, \ + asyncOpen, responseEnd); \ + } else { \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, \ + asyncOpen, responseEnd); \ + Telemetry::AccumulateTimeDelta( \ + Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET_V2, \ + asyncOpen, responseEnd); \ + } \ + } + + if (aDefaultRequest) { + HTTP_REQUEST_HISTOGRAMS(PAGE) + } else { + HTTP_REQUEST_HISTOGRAMS(SUB) + } +#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 = do_GetService("@mozilla.org/network/request-context-service;1"); + if (mRequestContextService) { + nsID requestContextID; + if (NS_SUCCEEDED(mRequestContextService->NewRequestContextID(&requestContextID))) { + mRequestContextService->GetRequestContext(requestContextID, + getter_AddRefs(mRequestContext)); + } + } + + 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 000000000..da89ca1b3 --- /dev/null +++ b/netwerk/base/nsLoadGroup.h @@ -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/. */ + +#ifndef nsLoadGroup_h__ +#define nsLoadGroup_h__ + +#include "nsILoadGroup.h" +#include "nsILoadGroupChild.h" +#include "nsPILoadGroupInternal.h" +#include "nsAgg.h" +#include "nsCOMPtr.h" +#include "nsWeakPtr.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 nsISupportsPriority, + public nsSupportsWeakReference, + public nsPILoadGroupInternal +{ +public: + NS_DECL_AGGREGATED + + //////////////////////////////////////////////////////////////////////////// + // nsIRequest methods: + NS_DECL_NSIREQUEST + + //////////////////////////////////////////////////////////////////////////// + // nsILoadGroup methods: + NS_DECL_NSILOADGROUP + NS_DECL_NSPILOADGROUPINTERNAL + + //////////////////////////////////////////////////////////////////////////// + // nsILoadGroupChild methods: + NS_DECL_NSILOADGROUPCHILD + + //////////////////////////////////////////////////////////////////////////// + // nsISupportsPriority methods: + NS_DECL_NSISUPPORTSPRIORITY + + //////////////////////////////////////////////////////////////////////////// + // nsLoadGroup methods: + + explicit nsLoadGroup(nsISupports* outer); + virtual ~nsLoadGroup(); + + nsresult Init(); + +protected: + nsresult MergeLoadFlags(nsIRequest *aRequest, nsLoadFlags& flags); + nsresult MergeDefaultLoadFlags(nsIRequest *aRequest, nsLoadFlags& flags); + +private: + void TelemetryReport(); + void TelemetryReportChannel(nsITimedChannel *timedChannel, + bool defaultRequest); + +protected: + uint32_t mForegroundCount; + uint32_t mLoadFlags; + uint32_t mDefaultLoadFlags; + + 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; + int32_t mPriority; + bool mIsCanceling; + + /* Telemetry */ + mozilla::TimeStamp mDefaultRequestCreationTime; + bool mDefaultLoadIsTimed; + uint32_t mTimedRequests; + uint32_t mCachedRequests; + + /* For nsPILoadGroupInternal */ + uint32_t mTimedNonCachedRequestsUntilOnEndPageLoad; + + nsCString mUserAgentOverrideCache; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsLoadGroup_h__ diff --git a/netwerk/base/nsMIMEInputStream.cpp b/netwerk/base/nsMIMEInputStream.cpp new file mode 100644 index 000000000..ce1188ea0 --- /dev/null +++ b/netwerk/base/nsMIMEInputStream.cpp @@ -0,0 +1,390 @@ +/* -*- 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/. */ + +/** + * The MIME stream separates headers and a datastream. It also allows + * automatic creation of the content-length header. + */ + +#include "ipc/IPCMessageUtils.h" + +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIMultiplexInputStream.h" +#include "nsIMIMEInputStream.h" +#include "nsISeekableStream.h" +#include "nsIStringStream.h" +#include "nsString.h" +#include "nsMIMEInputStream.h" +#include "nsIClassInfoImpl.h" +#include "nsIIPCSerializableInputStream.h" +#include "mozilla/ipc/InputStreamUtils.h" + +using namespace mozilla::ipc; +using mozilla::Maybe; + +class nsMIMEInputStream : public nsIMIMEInputStream, + public nsISeekableStream, + public nsIIPCSerializableInputStream +{ + virtual ~nsMIMEInputStream(); + +public: + nsMIMEInputStream(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIMIMEINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + + nsresult Init(); + +private: + + void InitStreams(); + + struct MOZ_STACK_CLASS ReadSegmentsState { + nsCOMPtr<nsIInputStream> mThisStream; + nsWriteSegmentFun mWriter; + void* mClosure; + }; + static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t *aWriteCount); + + nsCString mHeaders; + nsCOMPtr<nsIStringInputStream> mHeaderStream; + + nsCString mContentLength; + nsCOMPtr<nsIStringInputStream> mCLStream; + + nsCOMPtr<nsIInputStream> mData; + nsCOMPtr<nsIMultiplexInputStream> mStream; + bool mAddContentLength; + bool mStartedReading; +}; + +NS_IMPL_ADDREF(nsMIMEInputStream) +NS_IMPL_RELEASE(nsMIMEInputStream) + +NS_IMPL_CLASSINFO(nsMIMEInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_MIMEINPUTSTREAM_CID) + +NS_IMPL_QUERY_INTERFACE_CI(nsMIMEInputStream, + nsIMIMEInputStream, + nsIInputStream, + nsISeekableStream, + nsIIPCSerializableInputStream) +NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream, + nsIMIMEInputStream, + nsIInputStream, + nsISeekableStream) + +nsMIMEInputStream::nsMIMEInputStream() : mAddContentLength(false), + mStartedReading(false) +{ +} + +nsMIMEInputStream::~nsMIMEInputStream() +{ +} + +nsresult nsMIMEInputStream::Init() +{ + nsresult rv = NS_OK; + mStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", + &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mHeaderStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1", + &rv); + NS_ENSURE_SUCCESS(rv, rv); + mCLStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mStream->AppendStream(mHeaderStream); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mStream->AppendStream(mCLStream); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsMIMEInputStream::GetAddContentLength(bool *aAddContentLength) +{ + *aAddContentLength = mAddContentLength; + return NS_OK; +} +NS_IMETHODIMP +nsMIMEInputStream::SetAddContentLength(bool aAddContentLength) +{ + NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE); + mAddContentLength = aAddContentLength; + return NS_OK; +} + +NS_IMETHODIMP +nsMIMEInputStream::AddHeader(const char *aName, const char *aValue) +{ + NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE); + mHeaders.Append(aName); + mHeaders.AppendLiteral(": "); + mHeaders.Append(aValue); + mHeaders.AppendLiteral("\r\n"); + + // Just in case someone somehow uses our stream, lets at least + // let the stream have a valid pointer. The stream will be properly + // initialized in nsMIMEInputStream::InitStreams + mHeaderStream->ShareData(mHeaders.get(), 0); + + return NS_OK; +} + +NS_IMETHODIMP +nsMIMEInputStream::SetData(nsIInputStream *aStream) +{ + NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE); + // Remove the old stream if there is one + if (mData) + mStream->RemoveStream(2); + + mData = aStream; + if (aStream) + mStream->AppendStream(mData); + return NS_OK; +} + +NS_IMETHODIMP +nsMIMEInputStream::GetData(nsIInputStream **aStream) +{ + NS_ENSURE_ARG_POINTER(aStream); + *aStream = mData; + NS_IF_ADDREF(*aStream); + return NS_OK; +} + +// set up the internal streams +void nsMIMEInputStream::InitStreams() +{ + NS_ASSERTION(!mStartedReading, + "Don't call initStreams twice without rewinding"); + + mStartedReading = true; + + // We'll use the content-length stream to add the final \r\n + if (mAddContentLength) { + uint64_t cl = 0; + if (mData) { + mData->Available(&cl); + } + mContentLength.AssignLiteral("Content-Length: "); + mContentLength.AppendInt(cl); + mContentLength.AppendLiteral("\r\n\r\n"); + } + else { + mContentLength.AssignLiteral("\r\n"); + } + mCLStream->ShareData(mContentLength.get(), -1); + mHeaderStream->ShareData(mHeaders.get(), -1); +} + + + +#define INITSTREAMS \ +if (!mStartedReading) { \ + InitStreams(); \ +} + +// Reset mStartedReading when Seek-ing to start +NS_IMETHODIMP +nsMIMEInputStream::Seek(int32_t whence, int64_t offset) +{ + 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; + state.mThisStream = 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::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); } + +// nsISeekableStream +NS_IMETHODIMP nsMIMEInputStream::Tell(int64_t *_retval) +{ + INITSTREAMS; + nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream); + return stream->Tell(_retval); +} +NS_IMETHODIMP nsMIMEInputStream::SetEOF(void) { + INITSTREAMS; + nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream); + return stream->SetEOF(); +} + + +/** + * Factory method used by do_CreateInstance + */ + +nsresult +nsMIMEInputStreamConstructor(nsISupports *outer, REFNSIID iid, void **result) +{ + *result = nullptr; + + if (outer) + return NS_ERROR_NO_AGGREGATION; + + nsMIMEInputStream *inst = new nsMIMEInputStream(); + if (!inst) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(inst); + + nsresult rv = inst->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(inst); + return rv; + } + + rv = inst->QueryInterface(iid, result); + NS_RELEASE(inst); + + return rv; +} + +void +nsMIMEInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + MIMEInputStreamParams params; + + if (mData) { + nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mData); + MOZ_ASSERT(stream); + + InputStreamParams wrappedParams; + SerializeInputStream(stream, wrappedParams, aFileDescriptors); + + NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None, + "Wrapped stream failed to serialize!"); + + params.optionalStream() = wrappedParams; + } + else { + params.optionalStream() = mozilla::void_t(); + } + + params.headers() = mHeaders; + params.contentLength() = mContentLength; + params.startedReading() = mStartedReading; + params.addContentLength() = mAddContentLength; + + aParams = params; +} + +bool +nsMIMEInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) +{ + if (aParams.type() != InputStreamParams::TMIMEInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const MIMEInputStreamParams& params = + aParams.get_MIMEInputStreamParams(); + const OptionalInputStreamParams& wrappedParams = params.optionalStream(); + + mHeaders = params.headers(); + mContentLength = params.contentLength(); + mStartedReading = params.startedReading(); + + // nsMIMEInputStream::Init() already appended mHeaderStream & mCLStream + mHeaderStream->ShareData(mHeaders.get(), + mStartedReading ? mHeaders.Length() : 0); + mCLStream->ShareData(mContentLength.get(), + mStartedReading ? mContentLength.Length() : 0); + + nsCOMPtr<nsIInputStream> stream; + if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) { + stream = DeserializeInputStream(wrappedParams.get_InputStreamParams(), + aFileDescriptors); + if (!stream) { + NS_WARNING("Failed to deserialize wrapped stream!"); + return false; + } + + mData = stream; + + if (NS_FAILED(mStream->AppendStream(mData))) { + NS_WARNING("Failed to append stream!"); + return false; + } + } + else { + NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t, + "Unknown type for OptionalInputStreamParams!"); + } + + mAddContentLength = params.addContentLength(); + + return true; +} + +Maybe<uint64_t> +nsMIMEInputStream::ExpectedSerializedLength() +{ + nsCOMPtr<nsIIPCSerializableInputStream> serializable = do_QueryInterface(mStream); + return serializable ? serializable->ExpectedSerializedLength() : Nothing(); +} + diff --git a/netwerk/base/nsMIMEInputStream.h b/netwerk/base/nsMIMEInputStream.h new file mode 100644 index 000000000..38823d7ae --- /dev/null +++ b/netwerk/base/nsMIMEInputStream.h @@ -0,0 +1,29 @@ +/* -*- 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/. */ + +/** + * 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 "nsIMIMEInputStream.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(nsISupports *outer, + REFNSIID iid, + void **result); + +#endif // _nsMIMEInputStream_h_ diff --git a/netwerk/base/nsMediaFragmentURIParser.cpp b/netwerk/base/nsMediaFragmentURIParser.cpp new file mode 100644 index 000000000..af73fe52c --- /dev/null +++ b/netwerk/base/nsMediaFragmentURIParser.cpp @@ -0,0 +1,390 @@ +/* -*- 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::pair; +using std::make_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 = PromiseFlatString(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 = PromiseFlatString(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 = PromiseFlatString(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 = PromiseFlatString(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, NS_LITERAL_STRING("pixel:"))) { + clipUnit = eClipUnit_Pixel; + aString.Rebind(aString, 6); + } else if (StringBeginsWith(aString, NS_LITERAL_STRING("percent:"))) { + 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; +} + +bool nsMediaFragmentURIParser::ParseMozSampleSize(nsDependentSubstring aString) +{ + int32_t sampleSize; + + // Read and validate coordinates. + if (ParseInteger(aString, sampleSize) && sampleSize > 0) { + mSampleSize.emplace(sampleSize); + return true; + } + + return false; +} + +void nsMediaFragmentURIParser::Parse(nsACString& aRef) +{ + // Create an array of possibly-invalid media fragments. + nsTArray< std::pair<nsCString, nsCString> > fragments; + nsCCharSeparatedTokenizer tokenizer(aRef, '&'); + + while (tokenizer.hasMoreTokens()) { + const nsCSubstring& nv = tokenizer.nextToken(); + 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, gotSampleSize = false; + for (int i = fragments.Length() - 1 ; i >= 0 ; --i) { + if (gotTemporal && gotSpatial && gotSampleSize) { + // We've got one of each possible type. No need to look at the rest. + break; + } else 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)); + } else if (!gotSampleSize && fragments[i].first.EqualsLiteral("-moz-samplesize")) { + nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second); + gotSampleSize = ParseMozSampleSize(nsDependentSubstring(value, 0)); + } + } +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsMediaFragmentURIParser.h b/netwerk/base/nsMediaFragmentURIParser.h new file mode 100644 index 000000000..acfa1d5fe --- /dev/null +++ b/netwerk/base/nsMediaFragmentURIParser.h @@ -0,0 +1,106 @@ +/* -*- 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; } + + bool HasSampleSize() const { return mSampleSize.isSome(); } + + int GetSampleSize() const { return *mSampleSize; } + +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); + bool ParseMozSampleSize(nsDependentSubstring aString); + + // Media fragment information. + Maybe<double> mStart; + Maybe<double> mEnd; + Maybe<nsIntRect> mClip; + ClipUnit mClipUnit; + Maybe<int> mSampleSize; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/nsNetAddr.cpp b/netwerk/base/nsNetAddr.cpp new file mode 100644 index 000000000..8d6f245cf --- /dev/null +++ b/netwerk/base/nsNetAddr.cpp @@ -0,0 +1,152 @@ +/* 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) + +/* Makes a copy of |addr| */ +nsNetAddr::nsNetAddr(NetAddr* addr) +{ + NS_ASSERTION(addr, "null addr"); + mAddr = *addr; +} + +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.SetCapacity(kIPv4CStrBufSize); + NetAddrToString(&mAddr, aAddress.BeginWriting(), kIPv4CStrBufSize); + aAddress.SetLength(strlen(aAddress.BeginReading())); + break; + case AF_INET6: + aAddress.SetCapacity(kIPv6CStrBufSize); + NetAddrToString(&mAddr, 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 000000000..2717596f6 --- /dev/null +++ b/netwerk/base/nsNetAddr.h @@ -0,0 +1,31 @@ +/* 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() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSINETADDR + + explicit nsNetAddr(mozilla::net::NetAddr* 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 000000000..41808eb50 --- /dev/null +++ b/netwerk/base/nsNetSegmentUtils.h @@ -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/. */ + +#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 000000000..8ff3e788f --- /dev/null +++ b/netwerk/base/nsNetUtil.cpp @@ -0,0 +1,2459 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 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 "HttpLog.h" + +#include "mozilla/LoadContext.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Telemetry.h" +#include "nsNetUtil.h" +#include "nsNetUtilInlines.h" +#include "mozIApplicationClearPrivateDataParams.h" +#include "nsCategoryCache.h" +#include "nsContentUtils.h" +#include "nsHashKeys.h" +#include "nsHttp.h" +#include "nsIAsyncStreamCopier.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsIAuthPromptAdapterFactory.h" +#include "nsIBufferedStreams.h" +#include "nsIChannelEventSink.h" +#include "nsIContentSniffer.h" +#include "nsIDocument.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 "nsIMutable.h" +#include "nsINode.h" +#include "nsIOfflineCacheUpdate.h" +#include "nsIPersistentProperties2.h" +#include "nsIPrivateBrowsingChannel.h" +#include "nsIPropertyBag2.h" +#include "nsIProtocolProxyService.h" +#include "nsIRedirectChannelRegistrar.h" +#include "nsIRequestObserverProxy.h" +#include "nsIScriptSecurityManager.h" +#include "nsISimpleStreamListener.h" +#include "nsISocketProvider.h" +#include "nsISocketProviderService.h" +#include "nsIStandardURL.h" +#include "nsIStreamLoader.h" +#include "nsIIncrementalStreamLoader.h" +#include "nsIStreamTransportService.h" +#include "nsStringStream.h" +#include "nsISyncStreamListener.h" +#include "nsITransport.h" +#include "nsIUnicharStreamLoader.h" +#include "nsIURIWithPrincipal.h" +#include "nsIURLParser.h" +#include "nsIUUIDGenerator.h" +#include "nsIViewSourceChannel.h" +#include "nsInterfaceRequestorAgg.h" +#include "plstr.h" +#include "nsINestedURI.h" +#include "mozilla/dom/nsCSPUtils.h" +#include "mozilla/net/HttpBaseChannel.h" +#include "nsIScriptError.h" +#include "nsISiteSecurityService.h" +#include "nsHttpHandler.h" +#include "nsNSSComponent.h" + +#ifdef MOZ_WIDGET_GONK +#include "nsINetworkManager.h" +#include "nsThreadUtils.h" // for NS_IsMainThread +#endif + +#include <limits> + +using namespace mozilla; +using namespace mozilla::net; + +nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */ +NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel, + nsIURI *aUri, + nsINode *aLoadingNode, + nsIPrincipal *aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + 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, + aSecurityFlags, + aContentPolicyType, + aLoadGroup, + aCallbacks, + aLoadFlags, + aIoService); +} + +// See NS_NewChannelInternal for usage and argument description +nsresult /*NS_NewChannelWithPrincipalAndTriggeringPrincipal */ +NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel, + nsIURI *aUri, + nsIPrincipal *aLoadingPrincipal, + nsIPrincipal *aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + 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, + aSecurityFlags, + aContentPolicyType, + aLoadGroup, + aCallbacks, + aLoadFlags, + aIoService); +} + +nsresult /* NS_NewChannelNode */ +NS_NewChannel(nsIChannel **outChannel, + nsIURI *aUri, + nsINode *aLoadingNode, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsILoadGroup *aLoadGroup /* = nullptr */, + nsIInterfaceRequestor *aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService *aIoService /* = nullptr */) +{ + NS_ASSERTION(aLoadingNode, "Can not create channel without a loading Node!"); + return NS_NewChannelInternal(outChannel, + aUri, + aLoadingNode, + aLoadingNode->NodePrincipal(), + nullptr, // aTriggeringPrincipal + aSecurityFlags, + aContentPolicyType, + aLoadGroup, + aCallbacks, + aLoadFlags, + aIoService); +} + +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); + 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; + + nsCOMPtr<nsIIOService> grip; + net_EnsureIOService(&ioService, grip); + if (!ioService) + return -1; + + nsCOMPtr<nsIProtocolHandler> handler; + rv = ioService->GetProtocolHandler(scheme, getter_AddRefs(handler)); + if (NS_FAILED(rv)) + return -1; + int32_t port; + rv = handler->GetDefaultPort(&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); + if (NS_FAILED(rv)) + return false; + + return true; +} + +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_NewInputStreamChannelWithLoadInfo */ +NS_NewInputStreamChannelInternal(nsIChannel **outChannel, + nsIURI *aUri, + 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); + rv = isc->SetContentStream(aStream); + 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); + } + + 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, + nsIInputStream *aStream, + const nsACString &aContentType, + const nsACString &aContentCharset, + nsINode *aLoadingNode, + nsIPrincipal *aLoadingPrincipal, + nsIPrincipal *aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType) +{ + nsCOMPtr<nsILoadInfo> loadInfo = + new mozilla::LoadInfo(aLoadingPrincipal, + aTriggeringPrincipal, + aLoadingNode, + aSecurityFlags, + aContentPolicyType); + if (!loadInfo) { + return NS_ERROR_UNEXPECTED; + } + return NS_NewInputStreamChannelInternal(outChannel, + aUri, + aStream, + aContentType, + aContentCharset, + loadInfo); +} + +nsresult /* NS_NewInputStreamChannelPrincipal */ +NS_NewInputStreamChannel(nsIChannel **outChannel, + nsIURI *aUri, + nsIInputStream *aStream, + nsIPrincipal *aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + const nsACString &aContentType /* = EmptyCString() */, + const nsACString &aContentCharset /* = EmptyCString() */) +{ + return NS_NewInputStreamChannelInternal(outChannel, + aUri, + aStream, + 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); + +#ifdef MOZILLA_INTERNAL_API + uint32_t len; + char* utf8Bytes = ToNewUTF8String(aData, &len); + rv = stream->AdoptData(utf8Bytes, len); +#else + char* utf8Bytes = ToNewUTF8String(aData); + rv = stream->AdoptData(utf8Bytes, strlen(utf8Bytes)); +#endif + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), + aUri, + stream, + aContentType, + NS_LITERAL_CSTRING("UTF-8"), + 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::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 **result, + nsIInputStream *stream, + int64_t streamPos /* = int64_t(-1) */, + int64_t streamLen /* = int64_t(-1) */, + uint32_t segsize /* = 0 */, + uint32_t segcount /* = 0 */, + bool closeWhenDone /* = false */) +{ + nsresult rv; + nsCOMPtr<nsIInputStreamPump> pump = + do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = pump->Init(stream, streamPos, streamLen, + segsize, segcount, closeWhenDone); + if (NS_SUCCEEDED(rv)) { + *result = nullptr; + pump.swap(*result); + } + } + return rv; +} + +nsresult +NS_NewAsyncStreamCopier(nsIAsyncStreamCopier **result, + nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + bool sourceBuffered /* = true */, + bool sinkBuffered /* = true */, + uint32_t chunkSize /* = 0 */, + bool closeSource /* = true */, + bool closeSink /* = true */) +{ + nsresult rv; + nsCOMPtr<nsIAsyncStreamCopier> copier = + do_CreateInstance(NS_ASYNCSTREAMCOPIER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = copier->Init(source, sink, target, sourceBuffered, sinkBuffered, + chunkSize, closeSource, closeSink); + if (NS_SUCCEEDED(rv)) { + *result = nullptr; + copier.swap(*result); + } + } + 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); +} + +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); + + // Verify load context appId and browser flag match the principal + uint32_t contextAppId; + bool contextInIsolatedBrowser; + nsresult rv = loadContext->GetAppId(&contextAppId); + NS_ENSURE_SUCCESS(rv, false); + rv = loadContext->GetIsInIsolatedMozBrowserElement(&contextInIsolatedBrowser); + NS_ENSURE_SUCCESS(rv, false); + + return contextAppId == aPrincipal->GetAppId() && + contextInIsolatedBrowser == aPrincipal->GetIsInIsolatedMozBrowserElement(); +} + +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_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 */, + nsIURI *aReferrer /* = nullptr */) +{ + nsCOMPtr<nsIChannel> channel; + nsresult rv = NS_NewChannelInternal(getter_AddRefs(channel), + aUri, + aLoadingNode, + aLoadingPrincipal, + nullptr, // aTriggeringPrincipal + aSecurityFlags, + aContentPolicyType, + aLoadGroup, + aCallbacks, + aLoadFlags); + + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + if (httpChannel) { + httpChannel->SetReferrer(aReferrer); + } + rv = NS_NewStreamLoader(outStream, aObserver); + NS_ENSURE_SUCCESS(rv, rv); + return channel->AsyncOpen2(*outStream); +} + + +nsresult /* NS_NewStreamLoaderNode */ +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 */, + nsIURI *aReferrer /* = nullptr */) +{ + 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, + aReferrer); +} + +nsresult /* NS_NewStreamLoaderPrincipal */ +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 */, + nsIURI *aReferrer /* = nullptr */) +{ + return NS_NewStreamLoaderInternal(outStream, + aUri, + aObserver, + nullptr, // aLoadingNode + aLoadingPrincipal, + aSecurityFlags, + aContentPolicyType, + aLoadGroup, + aCallbacks, + aLoadFlags, + aReferrer); +} + +nsresult +NS_NewUnicharStreamLoader(nsIUnicharStreamLoader **result, + nsIUnicharStreamLoaderObserver *observer) +{ + nsresult rv; + nsCOMPtr<nsIUnicharStreamLoader> loader = + do_CreateInstance(NS_UNICHARSTREAMLOADER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = loader->Init(observer); + if (NS_SUCCEEDED(rv)) { + *result = nullptr; + loader.swap(*result); + } + } + return rv; +} + +nsresult +NS_NewSyncStreamListener(nsIStreamListener **result, + nsIInputStream **stream) +{ + nsresult rv; + nsCOMPtr<nsISyncStreamListener> listener = + do_CreateInstance(NS_SYNCSTREAMLISTENER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + 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 = NS_MaybeOpenChannelUsingAsyncOpen2(channel, 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) +{ + nsresult rv; + nsCOMPtr<nsIRequestObserverProxy> proxy = + do_CreateInstance(NS_REQUESTOBSERVERPROXY_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + 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, 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; +} + +nsresult +NS_GetReferrerFromChannel(nsIChannel *channel, + nsIURI **referrer) +{ + nsresult rv = NS_ERROR_NOT_AVAILABLE; + *referrer = nullptr; + + nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(channel)); + if (props) { + // 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). + rv = props->GetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"), + NS_GET_IID(nsIURI), + reinterpret_cast<void **>(referrer)); + if (NS_FAILED(rv)) + *referrer = nullptr; + } + + // if that didn't work, we can still try to get the referrer from the + // nsIHttpChannel (if we can QI to it) + if (!(*referrer)) { + nsCOMPtr<nsIHttpChannel> chan(do_QueryInterface(channel)); + if (chan) { + rv = chan->GetReferrer(referrer); + if (NS_FAILED(rv)) + *referrer = nullptr; + } + } + return rv; +} + +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_NewPartialLocalFileInputStream(nsIInputStream **result, + nsIFile *file, + uint64_t offset, + uint64_t length, + int32_t ioFlags /* = -1 */, + int32_t perm /* = -1 */, + int32_t behaviorFlags /* = 0 */) +{ + nsresult rv; + nsCOMPtr<nsIPartialFileInputStream> in = + do_CreateInstance(NS_PARTIALLOCALFILEINPUTSTREAM_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = in->Init(file, offset, length, ioFlags, perm, behaviorFlags); + if (NS_SUCCEEDED(rv)) + rv = CallQueryInterface(in, result); + } + return rv; +} + +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_NewLocalFileStream(nsIFileStream **result, + nsIFile *file, + int32_t ioFlags /* = -1 */, + int32_t perm /* = -1 */, + int32_t behaviorFlags /* = 0 */) +{ + nsresult rv; + nsCOMPtr<nsIFileStream> stream = + do_CreateInstance(NS_LOCALFILESTREAM_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = stream->Init(file, ioFlags, perm, behaviorFlags); + if (NS_SUCCEEDED(rv)) + stream.forget(result); + } + return rv; +} + +nsresult +NS_BackgroundInputStream(nsIInputStream **result, + nsIInputStream *stream, + uint32_t segmentSize /* = 0 */, + uint32_t segmentCount /* = 0 */) +{ + nsresult rv; + nsCOMPtr<nsIStreamTransportService> sts = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsITransport> inTransport; + rv = sts->CreateInputTransport(stream, int64_t(-1), int64_t(-1), + true, getter_AddRefs(inTransport)); + if (NS_SUCCEEDED(rv)) + rv = inTransport->OpenInputStream(nsITransport::OPEN_BLOCKING, + segmentSize, segmentCount, + result); + } + return rv; +} + +nsresult +NS_BackgroundOutputStream(nsIOutputStream **result, + nsIOutputStream *stream, + uint32_t segmentSize /* = 0 */, + uint32_t segmentCount /* = 0 */) +{ + nsresult rv; + nsCOMPtr<nsIStreamTransportService> sts = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsITransport> inTransport; + rv = sts->CreateOutputTransport(stream, int64_t(-1), int64_t(-1), + true, getter_AddRefs(inTransport)); + if (NS_SUCCEEDED(rv)) + rv = inTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING, + segmentSize, segmentCount, + result); + } + return rv; +} + +nsresult +NS_NewBufferedOutputStream(nsIOutputStream **result, + nsIOutputStream *str, + uint32_t bufferSize) +{ + nsresult rv; + nsCOMPtr<nsIBufferedOutputStream> out = + do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = out->Init(str, bufferSize); + if (NS_SUCCEEDED(rv)) { + out.forget(result); + } + } + return rv; +} + +already_AddRefed<nsIOutputStream> +NS_BufferOutputStream(nsIOutputStream *aOutputStream, + uint32_t aBufferSize) +{ + NS_ASSERTION(aOutputStream, "No output stream given!"); + + nsCOMPtr<nsIOutputStream> bos; + nsresult rv = NS_NewBufferedOutputStream(getter_AddRefs(bos), aOutputStream, + aBufferSize); + if (NS_SUCCEEDED(rv)) + return bos.forget(); + + bos = aOutputStream; + return bos.forget(); +} + +already_AddRefed<nsIInputStream> +NS_BufferInputStream(nsIInputStream *aInputStream, + uint32_t aBufferSize) +{ + NS_ASSERTION(aInputStream, "No input stream given!"); + + nsCOMPtr<nsIInputStream> bis; + nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bis), aInputStream, + aBufferSize); + if (NS_SUCCEEDED(rv)) + return bis.forget(); + + bis = aInputStream; + return bis.forget(); +} + +nsresult +NS_ReadInputStreamToBuffer(nsIInputStream *aInputStream, + void **aDest, + uint32_t aCount) +{ + nsresult rv; + + if (!*aDest) { + *aDest = malloc(aCount); + if (!*aDest) + return NS_ERROR_OUT_OF_MEMORY; + } + + char * p = reinterpret_cast<char*>(*aDest); + uint32_t bytesRead; + uint32_t totalRead = 0; + while (1) { + rv = aInputStream->Read(p + totalRead, aCount - totalRead, &bytesRead); + if (!NS_SUCCEEDED(rv)) + return rv; + totalRead += bytesRead; + if (totalRead == aCount) + break; + // if Read reads 0 bytes, we've hit EOF + if (bytesRead == 0) + return NS_ERROR_UNEXPECTED; + } + return rv; +} + +#ifdef MOZILLA_INTERNAL_API + +nsresult +NS_ReadInputStreamToString(nsIInputStream *aInputStream, + nsACString &aDest, + uint32_t aCount) +{ + if (!aDest.SetLength(aCount, mozilla::fallible)) + return NS_ERROR_OUT_OF_MEMORY; + void* dest = aDest.BeginWriting(); + return NS_ReadInputStreamToBuffer(aInputStream, &dest, aCount); +} + +#endif + +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_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIInputStream> in; + rv = channel->Open2(getter_AddRefs(in)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPersistentProperties> properties = + do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = properties->Load(in); + NS_ENSURE_SUCCESS(rv, rv); + + properties.swap(*outResult); + return NS_OK; +} + +bool +NS_UsePrivateBrowsing(nsIChannel *channel) +{ + bool isPrivate = false; + nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel); + if (pbChannel && NS_SUCCEEDED(pbChannel->GetIsChannelPrivate(&isPrivate))) { + return isPrivate; + } + + // Some channels may not implement nsIPrivateBrowsingChannel + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(channel, loadContext); + return loadContext && loadContext->UsePrivateBrowsing(); +} + +bool +NS_GetOriginAttributes(nsIChannel *aChannel, + mozilla::NeckoOriginAttributes &aAttributes) +{ + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + if (!loadInfo) { + return false; + } + + loadInfo->GetOriginAttributes(&aAttributes); + aAttributes.SyncAttributesWithPrivateBrowsing(NS_UsePrivateBrowsing(aChannel)); + return true; +} + +bool +NS_GetAppInfo(nsIChannel *aChannel, + uint32_t *aAppID, + bool *aIsInIsolatedMozBrowserElement) +{ + NeckoOriginAttributes attrs; + + if (!NS_GetOriginAttributes(aChannel, attrs)) { + return false; + } + + *aAppID = attrs.mAppId; + *aIsInIsolatedMozBrowserElement = attrs.mInIsolatedMozBrowser; + + return true; +} + +bool +NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport) +{ + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + MOZ_RELEASE_ASSERT(loadInfo, "Origin tracking only works for channels created with a loadinfo"); + +#ifdef DEBUG + // Don't enforce TYPE_DOCUMENT assertions for loads + // initiated by javascript tests. + bool skipContentTypeCheck = false; + skipContentTypeCheck = Preferences::GetBool("network.loadinfo.skip_type_assertion"); +#endif + + MOZ_ASSERT(skipContentTypeCheck || + loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT, + "calling NS_HasBeenCrossOrigin on a top level load"); + + // Always treat tainted channels as cross-origin. + if (loadInfo->GetTainting() != LoadTainting::Basic) { + return true; + } + + nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->LoadingPrincipal(); + uint32_t mode = loadInfo->GetSecurityMode(); + bool dataInherits = + mode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS || + mode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS || + mode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS; + + bool aboutBlankInherits = dataInherits && loadInfo->GetAboutBlankInherits(); + + for (nsIPrincipal* principal : loadInfo->RedirectChain()) { + nsCOMPtr<nsIURI> uri; + principal->GetURI(getter_AddRefs(uri)); + if (!uri) { + return true; + } + + if (aboutBlankInherits && NS_IsAboutBlank(uri)) { + continue; + } + + if (NS_FAILED(loadingPrincipal->CheckMayLoad(uri, aReport, dataInherits))) { + return true; + } + } + + nsCOMPtr<nsIURI> uri; + NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + if (!uri) { + return true; + } + + if (aboutBlankInherits && NS_IsAboutBlank(uri)) { + return false; + } + + return NS_FAILED(loadingPrincipal->CheckMayLoad(uri, aReport, dataInherits)); +} + +nsresult +NS_GetAppInfoFromClearDataNotification(nsISupports *aSubject, + uint32_t *aAppID, + bool *aBrowserOnly) +{ + nsresult rv; + + nsCOMPtr<mozIApplicationClearPrivateDataParams> + clearParams(do_QueryInterface(aSubject)); + MOZ_ASSERT(clearParams); + if (!clearParams) { + return NS_ERROR_UNEXPECTED; + } + + uint32_t appId; + rv = clearParams->GetAppId(&appId); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(appId != NECKO_UNKNOWN_APP_ID); + NS_ENSURE_SUCCESS(rv, rv); + if (appId == NECKO_UNKNOWN_APP_ID) { + return NS_ERROR_UNEXPECTED; + } + + bool browserOnly = false; + rv = clearParams->GetBrowserOnly(&browserOnly); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + NS_ENSURE_SUCCESS(rv, rv); + + *aAppID = appId; + *aBrowserOnly = browserOnly; + return NS_OK; +} + +bool +NS_ShouldCheckAppCache(nsIURI *aURI, bool usePrivateBrowsing) +{ + if (usePrivateBrowsing) { + return false; + } + + nsCOMPtr<nsIOfflineCacheUpdateService> offlineService = + do_GetService("@mozilla.org/offlinecacheupdate-service;1"); + if (!offlineService) { + return false; + } + + bool allowed; + nsresult rv = offlineService->OfflineAppAllowedForURI(aURI, + nullptr, + &allowed); + return NS_SUCCEEDED(rv) && allowed; +} + +bool +NS_ShouldCheckAppCache(nsIPrincipal *aPrincipal, bool usePrivateBrowsing) +{ + if (usePrivateBrowsing) { + return false; + } + + nsCOMPtr<nsIOfflineCacheUpdateService> offlineService = + do_GetService("@mozilla.org/offlinecacheupdate-service;1"); + if (!offlineService) { + return false; + } + + bool allowed; + nsresult rv = offlineService->OfflineAppAllowed(aPrincipal, + nullptr, + &allowed); + return NS_SUCCEEDED(rv) && allowed; +} + +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) +{ + NS_PRECONDITION(nestedURI, "Must have a nested URI!"); + NS_PRECONDITION(!*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); +} + +nsresult +NS_EnsureSafeToReturn(nsIURI *uri, nsIURI **result) +{ + NS_PRECONDITION(uri, "Must have a URI"); + + // Assume mutable until told otherwise + bool isMutable = true; + nsCOMPtr<nsIMutable> mutableObj(do_QueryInterface(uri)); + if (mutableObj) { + nsresult rv = mutableObj->GetMutable(&isMutable); + isMutable = NS_FAILED(rv) || isMutable; + } + + if (!isMutable) { + NS_ADDREF(*result = uri); + return NS_OK; + } + + nsresult rv = uri->Clone(result); + if (NS_SUCCEEDED(rv) && !*result) { + NS_ERROR("nsIURI.clone contract was violated"); + return NS_ERROR_UNEXPECTED; + } + + return rv; +} + +void +NS_TryToSetImmutable(nsIURI *uri) +{ + nsCOMPtr<nsIMutable> mutableObj(do_QueryInterface(uri)); + if (mutableObj) { + mutableObj->SetMutable(false); + } +} + +already_AddRefed<nsIURI> +NS_TryToMakeImmutable(nsIURI *uri, + nsresult *outRv /* = nullptr */) +{ + nsresult rv; + nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv); + + nsCOMPtr<nsIURI> result; + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(util, "do_GetNetUtil lied"); + rv = util->ToImmutableURI(uri, getter_AddRefs(result)); + } + + if (NS_FAILED(rv)) { + result = uri; + } + + if (outRv) { + *outRv = rv; + } + + return result.forget(); +} + +already_AddRefed<nsIURI> +NS_GetInnermostURI(nsIURI *aURI) +{ + NS_PRECONDITION(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; + nsLoadFlags loadFlags = 0; + nsresult rv = channel->GetLoadFlags(&loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + + if (loadFlags & nsIChannel::LOAD_REPLACE) { + return channel->GetURI(uri); + } + + return channel->GetOriginalURI(uri); +} + +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 + + 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; + } + + 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) +{ + // 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 either uri is an nsIURIWithPrincipal + nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(sourceBaseURI); + if (uriPrinc) { + uriPrinc->GetPrincipalUri(getter_AddRefs(sourceBaseURI)); + } + + uriPrinc = do_QueryInterface(targetBaseURI); + if (uriPrinc) { + uriPrinc->GetPrincipalUri(getter_AddRefs(targetBaseURI)); + } + + 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; + nsresult rv = sourceFile->Equals(targetFile, &filesAreEqual); + return NS_SUCCEEDED(rv) && filesAreEqual; + } + + 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) ); + } + + // 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; + } + +#ifdef MOZILLA_INTERNAL_API + if (!targetHost.Equals(sourceHost, nsCaseInsensitiveCStringComparator() )) +#else + if (!targetHost.Equals(sourceHost, CaseInsensitiveCompare)) +#endif + { + 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 + NS_NOTREACHED("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; + } + + // + // If the file to be loaded is in a subdirectory of the source + // (or same-dir if source is not a directory) then it will + // inherit its source principal and be scriptable by that source. + // + bool sourceIsDir; + bool allowed = false; + nsresult rv = sourceFile->IsDirectory(&sourceIsDir); + if (NS_SUCCEEDED(rv) && sourceIsDir) { + rv = sourceFile->Contains(targetFile, &allowed); + } else { + nsCOMPtr<nsIFile> sourceParent; + rv = sourceFile->GetParent(getter_AddRefs(sourceParent)); + if (NS_SUCCEEDED(rv) && sourceParent) { + rv = sourceParent->Equals(targetFile, &allowed); + if (NS_FAILED(rv) || !allowed) { + rv = sourceParent->Contains(targetFile, &allowed); + } else { + MOZ_ASSERT(aAllowDirectoryTarget, + "sourceFile->Parent == targetFile, but targetFile " + "should've been disallowed if it is a directory"); + } + } + } + + if (NS_SUCCEEDED(rv) && allowed) { + return true; + } + + 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; + } + + bool isHttp; + if (NS_FAILED(oldURI->SchemeIs("http", &isHttp)) || !isHttp) { + 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; +} + +nsresult +NS_LinkRedirectChannels(uint32_t channelId, + nsIParentChannel *parentChannel, + nsIChannel **_result) +{ + nsresult rv; + + nsCOMPtr<nsIRedirectChannelRegistrar> registrar = + do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return registrar->LinkChannels(channelId, + parentChannel, + _result); +} + +#define NS_FAKE_SCHEME "http://" +#define NS_FAKE_TLD ".invalid" +nsresult NS_MakeRandomInvalidURLString(nsCString &result) +{ + nsresult rv; + nsCOMPtr<nsIUUIDGenerator> uuidgen = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsID idee; + rv = uuidgen->GenerateUUIDInPlace(&idee); + NS_ENSURE_SUCCESS(rv, rv); + + char chars[NSID_LENGTH]; + idee.ToProvidedString(chars); + + result.AssignLiteral(NS_FAKE_SCHEME); + // Strip off the '{' and '}' at the beginning and end of the UUID + result.Append(chars + 1, NSID_LENGTH - 3); + result.AppendLiteral(NS_FAKE_TLD); + + return NS_OK; +} +#undef NS_FAKE_SCHEME +#undef NS_FAKE_TLD + +nsresult NS_MaybeOpenChannelUsingOpen2(nsIChannel* aChannel, + nsIInputStream **aStream) +{ + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + if (loadInfo && loadInfo->GetSecurityMode() != 0) { + return aChannel->Open2(aStream); + } + return aChannel->Open(aStream); +} + +nsresult NS_MaybeOpenChannelUsingAsyncOpen2(nsIChannel* aChannel, + nsIStreamListener *aListener) +{ + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + if (loadInfo && loadInfo->GetSecurityMode() != 0) { + return aChannel->AsyncOpen2(aListener); + } + return aChannel->AsyncOpen(aListener, nullptr); +} + +nsresult +NS_CheckIsJavaCompatibleURLString(nsCString &urlString, bool *result) +{ + *result = false; // Default to "no" + + nsresult rv = NS_OK; + nsCOMPtr<nsIURLParser> urlParser = + do_GetService(NS_STDURLPARSER_CONTRACTID, &rv); + if (NS_FAILED(rv) || !urlParser) + return NS_ERROR_FAILURE; + + bool compatible = true; + uint32_t schemePos = 0; + int32_t schemeLen = 0; + urlParser->ParseURL(urlString.get(), -1, &schemePos, &schemeLen, + nullptr, nullptr, nullptr, nullptr); + if (schemeLen != -1) { + nsCString scheme; + scheme.Assign(urlString.get() + schemePos, schemeLen); + // By default Java only understands a small number of URL schemes, and of + // these only some can legitimately represent a browser page's "origin" + // (and be something we can legitimately expect Java to handle ... or not + // to mishandle). + // + // Besides those listed below, the OJI plugin understands the "jar", + // "mailto", "netdoc", "javascript" and "rmi" schemes, and Java Plugin2 + // also understands the "about" scheme. We actually pass "about" URLs + // to Java ("about:blank" when processing a javascript: URL (one that + // calls Java) from the location bar of a blank page, and (in FF4 and up) + // "about:home" when processing a javascript: URL from the home page). + // And Java doesn't appear to mishandle them (for example it doesn't allow + // connections to "about" URLs). But it doesn't make any sense to do + // same-origin checks on "about" URLs, so we don't include them in our + // scheme whitelist. + // + // The OJI plugin doesn't understand "chrome" URLs (only Java Plugin2 + // does) -- so we mustn't pass them to the OJI plugin. But we do need to + // pass "chrome" URLs to Java Plugin2: Java Plugin2 grants additional + // privileges to chrome "origins", and some extensions take advantage of + // this. For more information see bug 620773. + // + // As of FF4, we no longer support the OJI plugin. + if (PL_strcasecmp(scheme.get(), "http") && + PL_strcasecmp(scheme.get(), "https") && + PL_strcasecmp(scheme.get(), "file") && + PL_strcasecmp(scheme.get(), "ftp") && + PL_strcasecmp(scheme.get(), "gopher") && + PL_strcasecmp(scheme.get(), "chrome")) + compatible = false; + } else { + compatible = false; + } + + *result = compatible; + + return NS_OK; +} + +/** 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; + + nsAutoCString fallbackCharset; + if (aChan) { + nsCOMPtr<nsIURI> uri; + aChan->GetURI(getter_AddRefs(uri)); + if (uri) + uri->GetOriginCharset(fallbackCharset); + } + + nsAutoString dispToken; + rv = mimehdrpar->GetParameterHTTP(aHeader, "", fallbackCharset, 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, + nsIURI *aURI /* = nullptr */) +{ + aFilename.Truncate(); + + nsresult rv; + nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = + do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIURL> url = do_QueryInterface(aURI); + + nsAutoCString fallbackCharset; + if (url) + url->GetOriginCharset(fallbackCharset); + // Get the value of 'filename' parameter + rv = mimehdrpar->GetParameterHTTP(aDisposition, "filename", + fallbackCharset, true, nullptr, + aFilename); + + if (NS_FAILED(rv)) { + aFilename.Truncate(); + return rv; + } + + if (aFilename.IsEmpty()) + return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +void net_EnsurePSMInit() +{ + nsresult rv; + nsCOMPtr<nsISupports> psm = do_GetService(PSM_COMPONENT_CONTRACTID, &rv); + MOZ_ASSERT(NS_SUCCEEDED(rv)); +} + +bool NS_IsAboutBlank(nsIURI *uri) +{ + // GetSpec can be expensive for some URIs, so check the scheme first. + bool isAbout = false; + if (NS_FAILED(uri->SchemeIs("about", &isAbout)) || !isAbout) { + return false; + } + + return uri->GetSpecOrDefault().EqualsLiteral("about:blank"); +} + +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) +{ + typedef nsCategoryCache<nsIContentSniffer> ContentSnifferCache; + extern ContentSnifferCache* gNetSniffers; + extern ContentSnifferCache* gDataSniffers; + 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 { + // Invalid content sniffer type was requested + MOZ_ASSERT(false); + 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) { + vsc->GetIsSrcdocChannel(&isSrcdoc); + return isSrcdoc; + } + return false; +} + +nsresult +NS_ShouldSecureUpgrade(nsIURI* aURI, + nsILoadInfo* aLoadInfo, + nsIPrincipal* aChannelResultPrincipal, + bool aPrivateBrowsing, + bool aAllowSTS, + bool& aShouldUpgrade) +{ + // 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 = false; + nsresult rv = aURI->SchemeIs("https", &isHttps); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isHttps) { + // If any of the documents up the chain to the root doucment makes use of + // the CSP directive 'upgrade-insecure-requests', then it's time to fulfill + // the promise to CSP and mixed content blocking to upgrade the channel + // from http to https. + if (aLoadInfo) { + // Please note that cross origin top level navigations are not subject + // to upgrade-insecure-requests, see: + // http://www.w3.org/TR/upgrade-insecure-requests/#examples + // Compare the principal we are navigating to (aChannelResultPrincipal) + // with the referring/triggering Principal. + bool crossOriginNavigation = + (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT) && + (!aChannelResultPrincipal->Equals(aLoadInfo->TriggeringPrincipal())); + + if (aLoadInfo->GetUpgradeInsecureRequests() && !crossOriginNavigation) { + // 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.AppendASCII("s"); + NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault()); + NS_ConvertUTF8toUTF16 reportScheme(scheme); + + const char16_t* params[] = { reportSpec.get(), reportScheme.get() }; + uint32_t innerWindowId = aLoadInfo->GetInnerWindowID(); + CSP_LogLocalizedStr(u"upgradeInsecureRequest", + params, ArrayLength(params), + EmptyString(), // aSourceFile + EmptyString(), // aScriptSample + 0, // aLineNumber + 0, // aColumnNumber + nsIScriptError::warningFlag, "CSP", + innerWindowId); + + Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 4); + aShouldUpgrade = true; + return NS_OK; + } + } + + // enforce Strict-Transport-Security + nsISiteSecurityService* sss = gHttpHandler->GetSSService(); + NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY); + + bool isStsHost = false; + uint32_t flags = aPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; + rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags, + nullptr, &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); + + if (isStsHost) { + LOG(("nsHttpChannel::Connect() STS permissions found\n")); + if (aAllowSTS) { + Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3); + aShouldUpgrade = true; + return NS_OK; + } else { + Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 2); + } + } else { + Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 1); + } + } else { + Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0); + } + aShouldUpgrade = false; + return NS_OK; +} + +nsresult +NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI) +{ + nsCOMPtr<nsIURI> upgradedURI; + + nsresult rv = aURI->Clone(getter_AddRefs(upgradedURI)); + NS_ENSURE_SUCCESS(rv,rv); + + // Change the scheme to HTTPS: + upgradedURI->SetScheme(NS_LITERAL_CSTRING("https")); + + // Change the default port to 443: + nsCOMPtr<nsIStandardURL> upgradedStandardURL = do_QueryInterface(upgradedURI); + if (upgradedStandardURL) { + upgradedStandardURL->SetDefaultPort(443); + } 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? + int32_t oldPort = -1; + 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) { + upgradedURI->SetPort(-1); + } else { + upgradedURI->SetPort(oldPort); + } + } + + upgradedURI.forget(aUpgradedURI); + return NS_OK; +} + +nsresult +NS_CompareLoadInfoAndLoadContext(nsIChannel *aChannel) +{ + nsCOMPtr<nsILoadInfo> loadInfo; + aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); + + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(aChannel, loadContext); + if (!loadInfo || !loadContext) { + return NS_OK; + } + + // We try to skip about:newtab and about:sync-tabs. + // about:newtab will use SystemPrincipal to download thumbnails through + // https:// and blob URLs. + // about:sync-tabs will fetch icons through moz-icon://. + bool isAboutPage = false; + nsINode* node = loadInfo->LoadingNode(); + if (node) { + nsIDocument* doc = node->OwnerDoc(); + if (doc) { + nsIURI* uri = doc->GetDocumentURI(); + nsresult rv = uri->SchemeIs("about", &isAboutPage); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + 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 (nsContentUtils::IsSystemPrincipal(loadInfo->LoadingPrincipal()) && + loadInfo->InternalContentPolicyType() == + nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) { + return NS_OK; + } + + uint32_t loadContextAppId = 0; + nsresult rv = loadContext->GetAppId(&loadContextAppId); + if (NS_FAILED(rv)) { + return NS_ERROR_UNEXPECTED; + } + + bool loadContextIsInBE = false; + rv = loadContext->GetIsInIsolatedMozBrowserElement(&loadContextIsInBE); + if (NS_FAILED(rv)) { + return NS_ERROR_UNEXPECTED; + } + + OriginAttributes originAttrsLoadInfo = loadInfo->GetOriginAttributes(); + DocShellOriginAttributes originAttrsLoadContext; + loadContext->GetOriginAttributes(originAttrsLoadContext); + + LOG(("NS_CompareLoadInfoAndLoadContext - loadInfo: %d, %d, %d, %d; " + "loadContext: %d %d, %d, %d. [channel=%p]", + originAttrsLoadInfo.mAppId, originAttrsLoadInfo.mInIsolatedMozBrowser, + originAttrsLoadInfo.mUserContextId, originAttrsLoadInfo.mPrivateBrowsingId, + loadContextAppId, loadContextIsInBE, + originAttrsLoadContext.mUserContextId, originAttrsLoadContext.mPrivateBrowsingId, + aChannel)); + + MOZ_ASSERT(originAttrsLoadInfo.mAppId == loadContextAppId, + "AppId in the loadContext and in the loadInfo are not the " + "same!"); + + MOZ_ASSERT(originAttrsLoadInfo.mInIsolatedMozBrowser == + loadContextIsInBE, + "The value of InIsolatedMozBrowser in the loadContext and in " + "the loadInfo are not the same!"); + + 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; +} + +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; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h new file mode 100644 index 000000000..bd89ca8ae --- /dev/null +++ b/netwerk/base/nsNetUtil.h @@ -0,0 +1,1000 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 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 "nsCOMPtr.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILoadGroup.h" +#include "nsINetUtil.h" +#include "nsIRequest.h" +#include "nsILoadInfo.h" +#include "nsIIOService.h" +#include "mozilla/Services.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" + +class nsIURI; +class nsIPrincipal; +class nsIAsyncStreamCopier; +class nsIAuthPrompt; +class nsIAuthPrompt2; +class nsIChannel; +class nsIChannelPolicy; +class nsIDownloadObserver; +class nsIEventTarget; +class nsIFileProtocolHandler; +class nsIFileStream; +class nsIInputStream; +class nsIInputStreamPump; +class nsIInterfaceRequestor; +class nsINestedURI; +class nsINetworkInterface; +class nsIOutputStream; +class nsIParentChannel; +class nsIPersistentProperties; +class nsIProxyInfo; +class nsIRequestObserver; +class nsIStreamListener; +class nsIStreamLoader; +class nsIStreamLoaderObserver; +class nsIIncrementalStreamLoader; +class nsIIncrementalStreamLoaderObserver; +class nsIUnicharStreamLoader; +class nsIUnicharStreamLoaderObserver; + +namespace mozilla { class NeckoOriginAttributes; } + +template <class> class nsCOMPtr; +template <typename> struct already_AddRefed; + +#ifdef MOZILLA_INTERNAL_API +#include "nsReadableUtils.h" +#include "nsString.h" +#else +#include "nsStringAPI.h" +#endif + +#ifdef MOZILLA_INTERNAL_API +already_AddRefed<nsIIOService> do_GetIOService(nsresult *error = 0); + +already_AddRefed<nsINetUtil> do_GetNetUtil(nsresult *error = 0); + +#else +// Helper, to simplify getting the I/O service. +const nsGetServiceByContractIDWithError do_GetIOService(nsresult *error = 0); + +// An alias to do_GetIOService +const nsGetServiceByContractIDWithError do_GetNetUtil(nsresult *error = 0); + +#endif + +// private little helper function... don't call this directly! +nsresult net_EnsureIOService(nsIIOService **ios, nsCOMPtr<nsIIOService> &grip); + +nsresult NS_NewURI(nsIURI **result, + const nsACString &spec, + const char *charset = nullptr, + nsIURI *baseURI = nullptr, + nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers + +nsresult NS_NewURI(nsIURI **result, + const nsAString &spec, + const char *charset = nullptr, + nsIURI *baseURI = nullptr, + nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers + +nsresult NS_NewURI(nsIURI **result, + const char *spec, + nsIURI *baseURI = nullptr, + nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers + +nsresult NS_NewFileURI(nsIURI **result, + nsIFile *spec, + nsIIOService *ioService = nullptr); // pass in nsIIOService to optimize callers + +/* +* 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 avaialable, 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. +* +* 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, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsILoadGroup *aLoadGroup = nullptr, + nsIInterfaceRequestor *aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService *aIoService = nullptr); + +// See NS_NewChannelInternal for usage and argument description +nsresult NS_NewChannelInternal(nsIChannel **outChannel, + nsIURI *aUri, + nsILoadInfo *aLoadInfo, + 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, + nsILoadGroup *aLoadGroup = nullptr, + nsIInterfaceRequestor *aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService *aIoService = nullptr); + + +// See NS_NewChannelInternal for usage and argument description +nsresult /*NS_NewChannelWithPrincipalAndTriggeringPrincipal */ +NS_NewChannelWithTriggeringPrincipal(nsIChannel **outChannel, + nsIURI *aUri, + nsIPrincipal *aLoadingPrincipal, + nsIPrincipal *aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsILoadGroup *aLoadGroup = nullptr, + nsIInterfaceRequestor *aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService *aIoService = nullptr); + +// See NS_NewChannelInternal for usage and argument description +nsresult /* NS_NewChannelNode */ +NS_NewChannel(nsIChannel **outChannel, + nsIURI *aUri, + nsINode *aLoadingNode, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsILoadGroup *aLoadGroup = nullptr, + nsIInterfaceRequestor *aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService *aIoService = nullptr); + +// See NS_NewChannelInternal for usage and argument description +nsresult /* NS_NewChannelPrincipal */ +NS_NewChannel(nsIChannel **outChannel, + nsIURI *aUri, + nsIPrincipal *aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsILoadGroup *aLoadGroup = nullptr, + nsIInterfaceRequestor *aCallbacks = nullptr, + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIIOService *aIoService = nullptr); + +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_NewInputStreamChannelWithLoadInfo */ +NS_NewInputStreamChannelInternal(nsIChannel **outChannel, + nsIURI *aUri, + nsIInputStream *aStream, + const nsACString &aContentType, + const nsACString &aContentCharset, + nsILoadInfo *aLoadInfo); + +nsresult NS_NewInputStreamChannelInternal(nsIChannel **outChannel, + nsIURI *aUri, + nsIInputStream *aStream, + const nsACString &aContentType, + const nsACString &aContentCharset, + nsINode *aLoadingNode, + nsIPrincipal *aLoadingPrincipal, + nsIPrincipal *aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType); + + +nsresult /* NS_NewInputStreamChannelPrincipal */ +NS_NewInputStreamChannel(nsIChannel **outChannel, + nsIURI *aUri, + nsIInputStream *aStream, + nsIPrincipal *aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + const nsACString &aContentType = EmptyCString(), + const nsACString &aContentCharset = EmptyCString()); + +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 **result, + nsIInputStream *stream, + int64_t streamPos = int64_t(-1), + int64_t streamLen = int64_t(-1), + uint32_t segsize = 0, + uint32_t segcount = 0, + bool closeWhenDone = false); + +// NOTE: you will need to specify whether or not your streams are buffered +// (i.e., do they implement ReadSegments/WriteSegments). the default +// assumption of TRUE for both streams might not be right for you! +nsresult NS_NewAsyncStreamCopier(nsIAsyncStreamCopier **result, + nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + bool sourceBuffered = true, + bool sinkBuffered = true, + uint32_t chunkSize = 0, + bool closeSource = true, + bool closeSink = true); + +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 appId and browser element flags match. 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, + nsIURI *aReferrer = nullptr); + +nsresult /* NS_NewStreamLoaderNode */ +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, + nsIURI *aReferrer = nullptr); + +nsresult /* NS_NewStreamLoaderPrincipal */ +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, + nsIURI *aReferrer = nullptr); + +nsresult NS_NewUnicharStreamLoader(nsIUnicharStreamLoader **result, + nsIUnicharStreamLoaderObserver *observer); + +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. + * + * @returns NS_ERROR_NOT_AVAILABLE if no referrer is available. + */ +nsresult 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); + +nsresult NS_NewPartialLocalFileInputStream(nsIInputStream **result, + nsIFile *file, + uint64_t offset, + uint64_t length, + 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); + +// 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_NewLocalFileStream(nsIFileStream **result, + nsIFile *file, + int32_t ioFlags = -1, + int32_t perm = -1, + int32_t behaviorFlags = 0); + +// returns the input end of a pipe. the output end of the pipe +// is attached to the original stream. data from the original +// stream is read into the pipe on a background thread. +nsresult NS_BackgroundInputStream(nsIInputStream **result, + nsIInputStream *stream, + uint32_t segmentSize = 0, + uint32_t segmentCount = 0); + +// returns the output end of a pipe. the input end of the pipe +// is attached to the original stream. data written to the pipe +// is copied to the original stream on a background thread. +nsresult NS_BackgroundOutputStream(nsIOutputStream **result, + nsIOutputStream *stream, + uint32_t segmentSize = 0, + uint32_t segmentCount = 0); + +MOZ_MUST_USE nsresult +NS_NewBufferedInputStream(nsIInputStream **result, + nsIInputStream *str, + uint32_t bufferSize); + +// note: the resulting stream can be QI'ed to nsISafeOutputStream iff the +// provided stream supports it. +nsresult NS_NewBufferedOutputStream(nsIOutputStream **result, + nsIOutputStream *str, + uint32_t bufferSize); + +/** + * Attempts to buffer a given stream. If this fails, it returns the + * passed-in stream. + * + * @param aOutputStream + * The output stream we want to buffer. This cannot be null. + * @param aBufferSize + * The size of the buffer for the buffered output stream. + * @returns an nsIOutputStream that is buffered with the specified buffer size, + * or is aOutputStream if creating the new buffered stream failed. + */ +already_AddRefed<nsIOutputStream> +NS_BufferOutputStream(nsIOutputStream *aOutputStream, + uint32_t aBufferSize); +already_AddRefed<nsIInputStream> +NS_BufferInputStream(nsIInputStream *aInputStream, + uint32_t aBufferSize); + +// returns an input stream compatible with nsIUploadChannel::SetUploadStream() +nsresult NS_NewPostDataStream(nsIInputStream **result, + bool isFile, + const nsACString &data); + +nsresult NS_ReadInputStreamToBuffer(nsIInputStream *aInputStream, + void **aDest, + uint32_t aCount); + +// external code can't see fallible_t +#ifdef MOZILLA_INTERNAL_API + +nsresult NS_ReadInputStreamToString(nsIInputStream *aInputStream, + nsACString &aDest, + uint32_t aCount); + +#endif + +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) +{ + NS_PRECONDITION(channel, "null channel"); + *result = nullptr; + + nsCOMPtr<nsIInterfaceRequestor> cbs; + channel->GetNotificationCallbacks(getter_AddRefs(cbs)); + if (cbs) + cbs->GetInterface(iid, result); + if (!*result) { + // try load group's notification callbacks... + nsCOMPtr<nsILoadGroup> loadGroup; + 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); + +/** + * Extract the NeckoOriginAttributes from the channel's triggering principal. + */ +bool NS_GetOriginAttributes(nsIChannel *aChannel, + mozilla::NeckoOriginAttributes &aAttributes); + +/** + * 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); + +// Constants duplicated from nsIScriptSecurityManager so we avoid having necko +// know about script security manager. +#define NECKO_NO_APP_ID 0 +#define NECKO_UNKNOWN_APP_ID UINT32_MAX +// special app id reserved for separating the safebrowsing cookie +#define NECKO_SAFEBROWSING_APP_ID UINT32_MAX - 1 + +/** + * Gets AppId and isInIsolatedMozBrowserElement from channel's nsILoadContext. + * Returns false if error or channel's callbacks don't implement nsILoadContext. + */ +bool NS_GetAppInfo(nsIChannel *aChannel, + uint32_t *aAppID, + bool *aIsInIsolatedMozBrowserElement); + +/** + * Gets appId and browserOnly parameters from the TOPIC_WEB_APP_CLEAR_DATA + * nsIObserverService notification. Used when clearing user data or + * uninstalling web apps. + */ +nsresult NS_GetAppInfoFromClearDataNotification(nsISupports *aSubject, + uint32_t *aAppID, + bool *aBrowserOnly); + +/** + * Determines whether appcache should be checked for a given URI. + */ +bool NS_ShouldCheckAppCache(nsIURI *aURI, bool usePrivateBrowsing); + +bool NS_ShouldCheckAppCache(nsIPrincipal *aPrincipal, bool usePrivateBrowsing); + +/** + * 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 that ensures that |result| is a URI that's safe to + * return. If |uri| is immutable, just returns it, otherwise returns + * a clone. |uri| must not be null. + */ +nsresult NS_EnsureSafeToReturn(nsIURI *uri, nsIURI **result); + +/** + * Helper function that tries to set the argument URI to be immutable + */ +void NS_TryToSetImmutable(nsIURI *uri); + +/** + * Helper function for calling ToImmutableURI. If all else fails, returns + * the input URI. The optional second arg indicates whether we had to fall + * back to the input URI. Passing in a null URI is ok. + */ +already_AddRefed<nsIURI> NS_TryToMakeImmutable(nsIURI *uri, + nsresult *outRv = nullptr); + +/** + * 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); + +/** + * Get the "final" URI for a channel. This is either the same as GetURI or + * GetOriginalURI, depending on whether this channel has + * nsIChanel::LOAD_REPLACE set. For channels without that flag set, the final + * URI is the original URI, while for ones with the flag the final URI is the + * channel URI. + */ +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); + +nsresult NS_LinkRedirectChannels(uint32_t channelId, + nsIParentChannel *parentChannel, + nsIChannel **_result); + +/** + * Helper function to create a random URL string that's properly formed + * but guaranteed to be invalid. + */ +nsresult NS_MakeRandomInvalidURLString(nsCString &result); + +/** + * Helper function which checks whether the channel can be + * openend using Open2() or has to fall back to opening + * the channel using Open(). + */ +nsresult NS_MaybeOpenChannelUsingOpen2(nsIChannel* aChannel, + nsIInputStream **aStream); + +/** + * Helper function which checks whether the channel can be + * openend using AsyncOpen2() or has to fall back to opening + * the channel using AsyncOpen(). + */ +nsresult NS_MaybeOpenChannelUsingAsyncOpen2(nsIChannel* aChannel, + nsIStreamListener *aListener); + +/** + * Helper function to determine whether urlString is Java-compatible -- + * whether it can be passed to the Java URL(String) constructor without the + * latter throwing a MalformedURLException, or without Java otherwise + * mishandling it. This function (in effect) implements a scheme whitelist + * for Java. + */ +nsresult NS_CheckIsJavaCompatibleURLString(nsCString& urlString, bool *result); + +/** 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 + * @param aURI Optional. Will be used to get a fallback charset for the + * filename, if it is QI'able to nsIURL + */ +nsresult NS_GetFilenameFromDisposition(nsAString &aFilename, + const nsACString &aDisposition, + nsIURI *aURI = nullptr); + +/** + * 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); + +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); + +/** + * Return true if the given request must be upgraded to HTTPS. + */ +nsresult NS_ShouldSecureUpgrade(nsIURI* aURI, + nsILoadInfo* aLoadInfo, + nsIPrincipal* aChannelResultPrincipal, + bool aPrivateBrowsing, + bool aAllowSTS, + bool& aShouldUpgrade); + +/** + * 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); + +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); + +} // namespace net +} // namespace mozilla + +// Include some function bodies for callers with external linkage +#ifndef MOZILLA_INTERNAL_API +#include "nsNetUtilInlines.h" +#endif + +#endif // !nsNetUtil_h__ diff --git a/netwerk/base/nsNetUtilInlines.h b/netwerk/base/nsNetUtilInlines.h new file mode 100644 index 000000000..7003814d5 --- /dev/null +++ b/netwerk/base/nsNetUtilInlines.h @@ -0,0 +1,395 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 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_inl +#define nsNetUtil_inl + +#include "mozilla/Services.h" + +#include "nsComponentManagerUtils.h" +#include "nsIBufferedStreams.h" +#include "nsIChannel.h" +#include "nsIFile.h" +#include "nsIFileStreams.h" +#include "nsIFileURL.h" +#include "nsIHttpChannel.h" +#include "nsIInputStreamChannel.h" +#include "nsIIOService.h" +#include "nsINestedURI.h" +#include "nsINode.h" +#include "nsIProtocolHandler.h" +#include "nsIStandardURL.h" +#include "nsIStreamLoader.h" +#include "nsIIncrementalStreamLoader.h" +#include "nsIURI.h" +#include "nsIURIWithPrincipal.h" +#include "nsIWritablePropertyBag2.h" +#include "nsNetCID.h" +#include "nsStringStream.h" + +#ifdef MOZILLA_INTERNAL_API +// Don't allow functions that end up in nsNetUtil.cpp to be inlined out. +#define INLINE_IF_EXTERN MOZ_NEVER_INLINE +#else +// Make sure that functions included via nsNetUtil.h don't get multiply defined. +#define INLINE_IF_EXTERN MOZ_ALWAYS_INLINE +#endif + +#ifdef MOZILLA_INTERNAL_API + +INLINE_IF_EXTERN already_AddRefed<nsIIOService> +do_GetIOService(nsresult *error /* = 0 */) +{ + nsCOMPtr<nsIIOService> io = mozilla::services::GetIOService(); + if (error) + *error = io ? NS_OK : NS_ERROR_FAILURE; + return io.forget(); +} + +INLINE_IF_EXTERN already_AddRefed<nsINetUtil> +do_GetNetUtil(nsresult *error /* = 0 */) +{ + nsCOMPtr<nsIIOService> io = mozilla::services::GetIOService(); + nsCOMPtr<nsINetUtil> util; + if (io) + util = do_QueryInterface(io); + + if (error) + *error = !!util ? NS_OK : NS_ERROR_FAILURE; + return util.forget(); +} + +#else + +INLINE_IF_EXTERN const nsGetServiceByContractIDWithError +do_GetIOService(nsresult *error /* = 0 */) +{ + return nsGetServiceByContractIDWithError(NS_IOSERVICE_CONTRACTID, error); +} + +INLINE_IF_EXTERN const nsGetServiceByContractIDWithError +do_GetNetUtil(nsresult *error /* = 0 */) +{ + return do_GetIOService(error); +} +#endif + +// private little helper function... don't call this directly! +MOZ_ALWAYS_INLINE nsresult +net_EnsureIOService(nsIIOService **ios, nsCOMPtr<nsIIOService> &grip) +{ + nsresult rv = NS_OK; + if (!*ios) { + grip = do_GetIOService(&rv); + *ios = grip; + } + return rv; +} + +INLINE_IF_EXTERN 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); +} + +INLINE_IF_EXTERN nsresult +NS_NewURI(nsIURI **result, + const nsACString &spec, + const char *charset /* = nullptr */, + nsIURI *baseURI /* = nullptr */, + nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers +{ + nsresult rv; + nsCOMPtr<nsIIOService> grip; + rv = net_EnsureIOService(&ioService, grip); + if (ioService) + rv = ioService->NewURI(spec, charset, baseURI, result); + return rv; +} + +INLINE_IF_EXTERN nsresult +NS_NewURI(nsIURI **result, + const nsAString &spec, + const char *charset /* = nullptr */, + nsIURI *baseURI /* = nullptr */, + nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers +{ + return NS_NewURI(result, NS_ConvertUTF16toUTF8(spec), charset, baseURI, ioService); +} + +INLINE_IF_EXTERN nsresult +NS_NewURI(nsIURI **result, + const char *spec, + nsIURI *baseURI /* = nullptr */, + nsIIOService *ioService /* = nullptr */) // pass in nsIIOService to optimize callers +{ + return NS_NewURI(result, nsDependentCString(spec), nullptr, baseURI, ioService); +} + +INLINE_IF_EXTERN 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; +} + +INLINE_IF_EXTERN nsresult +NS_NewChannelInternal(nsIChannel **outChannel, + nsIURI *aUri, + nsINode *aLoadingNode, + nsIPrincipal *aLoadingPrincipal, + nsIPrincipal *aTriggeringPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsILoadGroup *aLoadGroup /* = nullptr */, + nsIInterfaceRequestor *aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService *aIoService /* = nullptr */) +{ + NS_ENSURE_ARG_POINTER(outChannel); + + nsCOMPtr<nsIIOService> grip; + nsresult rv = net_EnsureIOService(&aIoService, grip); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel; + rv = aIoService->NewChannelFromURI2( + aUri, + aLoadingNode ? + aLoadingNode->AsDOMNode() : nullptr, + aLoadingPrincipal, + aTriggeringPrincipal, + aSecurityFlags, + aContentPolicyType, + 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); + } + + if (aLoadFlags != nsIRequest::LOAD_NORMAL) { + // Retain the LOAD_REPLACE load flag if set. + nsLoadFlags normalLoadFlags = 0; + channel->GetLoadFlags(&normalLoadFlags); + rv = channel->SetLoadFlags(aLoadFlags | (normalLoadFlags & nsIChannel::LOAD_REPLACE)); + NS_ENSURE_SUCCESS(rv, rv); + } + + channel.forget(outChannel); + return NS_OK; +} + +INLINE_IF_EXTERN nsresult +NS_NewChannelInternal(nsIChannel **outChannel, + nsIURI *aUri, + nsILoadInfo *aLoadInfo, + 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); + } + + if (aLoadFlags != nsIRequest::LOAD_NORMAL) { + // Retain the LOAD_REPLACE load flag if set. + nsLoadFlags normalLoadFlags = 0; + channel->GetLoadFlags(&normalLoadFlags); + rv = channel->SetLoadFlags(aLoadFlags | (normalLoadFlags & nsIChannel::LOAD_REPLACE)); + NS_ENSURE_SUCCESS(rv, rv); + } + + channel.forget(outChannel); + return NS_OK; +} + +INLINE_IF_EXTERN nsresult /* NS_NewChannelPrincipal */ +NS_NewChannel(nsIChannel **outChannel, + nsIURI *aUri, + nsIPrincipal *aLoadingPrincipal, + nsSecurityFlags aSecurityFlags, + nsContentPolicyType aContentPolicyType, + nsILoadGroup *aLoadGroup /* = nullptr */, + nsIInterfaceRequestor *aCallbacks /* = nullptr */, + nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */, + nsIIOService *aIoService /* = nullptr */) +{ + return NS_NewChannelInternal(outChannel, + aUri, + nullptr, // aLoadingNode, + aLoadingPrincipal, + nullptr, // aTriggeringPrincipal + aSecurityFlags, + aContentPolicyType, + aLoadGroup, + aCallbacks, + aLoadFlags, + aIoService); +} + +INLINE_IF_EXTERN 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; +} + +INLINE_IF_EXTERN 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; +} + +INLINE_IF_EXTERN 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; +} + +INLINE_IF_EXTERN MOZ_MUST_USE nsresult +NS_NewBufferedInputStream(nsIInputStream **result, + nsIInputStream *str, + uint32_t bufferSize) +{ + nsresult rv; + nsCOMPtr<nsIBufferedInputStream> in = + do_CreateInstance(NS_BUFFEREDINPUTSTREAM_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = in->Init(str, bufferSize); + if (NS_SUCCEEDED(rv)) { + in.forget(result); + } + } + return rv; +} + +INLINE_IF_EXTERN nsresult +NS_NewPostDataStream(nsIInputStream **result, + bool isFile, + const nsACString &data) +{ + nsresult rv; + + if (isFile) { + nsCOMPtr<nsIFile> file; + nsCOMPtr<nsIInputStream> fileStream; + + rv = NS_NewNativeLocalFile(data, false, getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) { + rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file); + if (NS_SUCCEEDED(rv)) { + // wrap the file stream with a buffered input stream + rv = NS_NewBufferedInputStream(result, fileStream, 8192); + } + } + return rv; + } + + // otherwise, create a string stream for the data (copies) + nsCOMPtr<nsIStringInputStream> stream + (do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv)); + if (NS_FAILED(rv)) + return rv; + + rv = stream->SetData(data.BeginReading(), data.Length()); + if (NS_FAILED(rv)) + return rv; + + stream.forget(result); + return NS_OK; +} + +INLINE_IF_EXTERN 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; +} + +#endif // nsNetUtil_inl diff --git a/netwerk/base/nsNetworkInfoService.cpp b/netwerk/base/nsNetworkInfoService.cpp new file mode 100644 index 000000000..5b188c7f1 --- /dev/null +++ b/netwerk/base/nsNetworkInfoService.cpp @@ -0,0 +1,113 @@ +/* -*- 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/. */ + +#if defined(XP_MACOSX) || defined(XP_LINUX) +#include <unistd.h> +#elif defined(XP_WIN) +#include <winsock2.h> +#endif + +#include "nsNetworkInfoService.h" +#include "mozilla/ScopeExit.h" + +#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX) +#include "NetworkInfoServiceImpl.h" +#else +#error "Unsupported platform for nsNetworkInfoService! Check moz.build" +#endif + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(nsNetworkInfoService, + nsINetworkInfoService) + +nsNetworkInfoService::nsNetworkInfoService() +{ +} + +nsresult +nsNetworkInfoService::Init() +{ + return NS_OK; +} + +nsresult +nsNetworkInfoService::ListNetworkAddresses(nsIListNetworkAddressesListener* aListener) +{ + nsresult rv; + + AddrMapType addrMap; + rv = DoListAddresses(addrMap); + if (NS_WARN_IF(NS_FAILED(rv))) { + aListener->OnListNetworkAddressesFailed(); + return NS_OK; + } + + uint32_t addrCount = addrMap.Count(); + const char** addrStrings = (const char**) malloc(sizeof(*addrStrings) * addrCount); + if (!addrStrings) { + aListener->OnListNetworkAddressesFailed(); + return NS_OK; + } + auto autoFreeAddrStrings = MakeScopeExit([&] { + free(addrStrings); + }); + + uint32_t idx = 0; + for (auto iter = addrMap.Iter(); !iter.Done(); iter.Next()) { + addrStrings[idx++] = iter.Data().get(); + } + aListener->OnListedNetworkAddresses(addrStrings, addrCount); + return NS_OK; +} + +// TODO: Bug 1275373: https://bugzilla.mozilla.org/show_bug.cgi?id=1275373 +// Use platform-specific implementation of DoGetHostname on Cocoa and Windows. +static nsresult +DoGetHostname(nsACString& aHostname) +{ + char hostnameBuf[256]; + int result = gethostname(hostnameBuf, 256); + if (result == -1) { + return NS_ERROR_FAILURE; + } + + // Ensure that there is always a terminating NUL byte. + hostnameBuf[255] = '\0'; + + // Find the first '.', terminate string there. + char* dotLocation = strchr(hostnameBuf, '.'); + if (dotLocation) { + *dotLocation = '\0'; + } + + if (strlen(hostnameBuf) == 0) { + return NS_ERROR_FAILURE; + } + + aHostname.AssignASCII(hostnameBuf); + return NS_OK; +} + +nsresult +nsNetworkInfoService::GetHostname(nsIGetHostnameListener* aListener) +{ + nsresult rv; + nsCString hostnameStr; + rv = DoGetHostname(hostnameStr); + if (NS_FAILED(rv)) { + aListener->OnGetHostnameFailed(); + return NS_OK; + } + + aListener->OnGotHostname(hostnameStr); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsNetworkInfoService.h b/netwerk/base/nsNetworkInfoService.h new file mode 100644 index 000000000..be6f686bd --- /dev/null +++ b/netwerk/base/nsNetworkInfoService.h @@ -0,0 +1,40 @@ +/* -*- 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_nsNetworkInfoService_h +#define mozilla_net_nsNetworkInfoService_h + +#include "nsISupportsImpl.h" +#include "mozilla/ErrorResult.h" + +#include "nsINetworkInfoService.h" + +#define NETWORKINFOSERVICE_CID \ +{ 0x296d0900, 0xf8ef, 0x4df0, \ + { 0x9c, 0x35, 0xdb, 0x58, 0x62, 0xab, 0xc5, 0x8d } } + +namespace mozilla { +namespace net { + +class nsNetworkInfoService final + : public nsINetworkInfoService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSINETWORKINFOSERVICE + + nsresult Init(); + + explicit nsNetworkInfoService(); + +private: + virtual ~nsNetworkInfoService() = default; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_dom_nsNetworkInfoService_h diff --git a/netwerk/base/nsPACMan.cpp b/netwerk/base/nsPACMan.cpp new file mode 100644 index 000000000..37d3e8b6b --- /dev/null +++ b/netwerk/base/nsPACMan.cpp @@ -0,0 +1,769 @@ +/* -*- 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 "nsThreadUtils.h" +#include "nsIAuthPrompt.h" +#include "nsIPromptFactory.h" +#include "nsIHttpChannel.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsNetUtil.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsISystemProxySettings.h" +#include "nsContentUtils.h" +#include "mozilla/Preferences.h" + +//----------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +LazyLogModule gProxyLog("proxy"); + +#undef LOG +#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args) + +// 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) + httpChannel->GetRequestSucceeded(&result); + + return result; +} + +//----------------------------------------------------------------------------- + +// 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) + : mCallback(aCallback) + , mStatus(status) + { + } + + void SetPACString(const nsCString &pacString) + { + mPACString = pacString; + } + + void SetPACURL(const nsCString &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) + : 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) + : 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) + : mPACMan(aPACMan) + { + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + mPACMan->mLoader = nullptr; + mPACMan->PostProcessPendingQ(); + return NS_OK; + } + +private: + RefPtr<nsPACMan> mPACMan; +}; + +//----------------------------------------------------------------------------- + +// ExecutePACThreadAction is used to proxy actions from the main +// thread onto the PAC thread. There are 3 options: process the queue, +// cancel the queue, 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) + : mPACMan(aPACMan) + , mCancel(false) + , mCancelStatus(NS_OK) + , mSetupPAC(false) + { } + + void CancelQueue (nsresult status) + { + mCancel = true; + mCancelStatus = status; + } + + void SetupPAC (const char *text, uint32_t datalen, nsCString &pacURI) + { + mSetupPAC = true; + mSetupPACData.Assign(text, datalen); + mSetupPACURI = pacURI; + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + if (mCancel) { + mPACMan->CancelPendingQ(mCancelStatus); + mCancel = false; + return NS_OK; + } + + if (mSetupPAC) { + mSetupPAC = false; + + mPACMan->mPAC.Init(mSetupPACURI, + mSetupPACData, + mPACMan->mIncludePath); + + RefPtr<PACLoadComplete> runnable = new PACLoadComplete(mPACMan); + NS_DispatchToMainThread(runnable); + return NS_OK; + } + + mPACMan->ProcessPendingQ(); + return NS_OK; + } + +private: + RefPtr<nsPACMan> mPACMan; + + bool mCancel; + nsresult mCancelStatus; + + bool mSetupPAC; + nsCString mSetupPACData; + nsCString mSetupPACURI; +}; + +//----------------------------------------------------------------------------- + +PendingPACQuery::PendingPACQuery(nsPACMan *pacMan, nsIURI *uri, + nsPACManCallback *callback, + bool mainThreadResponse) + : mPACMan(pacMan) + , mCallback(callback) + , mOnMainThreadOnly(mainThreadResponse) +{ + uri->GetAsciiSpec(mSpec); + uri->GetAsciiHost(mHost); + uri->GetScheme(mScheme); + uri->GetPort(&mPort); +} + +void +PendingPACQuery::Complete(nsresult status, const nsCString &pacString) +{ + if (!mCallback) + return; + RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, status); + runnable->SetPACString(pacString); + if (mOnMainThreadOnly) + NS_DispatchToMainThread(runnable); + else + runnable->Run(); +} + +void +PendingPACQuery::UseAlternatePACFile(const nsCString &pacURL) +{ + if (!mCallback) + return; + + RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, NS_OK); + runnable->SetPACURL(pacURL); + if (mOnMainThreadOnly) + NS_DispatchToMainThread(runnable); + 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() + : mLoadPending(false) + , mShutdown(false) + , mLoadFailureCount(0) + , mInProgress(false) +{ + MOZ_ASSERT(NS_IsMainThread(), "pacman must be created on main thread"); + if (!sThreadLocalSetup){ + sThreadLocalSetup = true; + PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr); + } + mPAC.SetThreadLocalIndex(sThreadLocalIndex); + mIncludePath = Preferences::GetBool(kPACIncludePath, false); +} + +nsPACMan::~nsPACMan() +{ + if (mPACThread) { + if (NS_IsMainThread()) { + mPACThread->Shutdown(); + } + else { + RefPtr<ShutdownThread> runnable = new ShutdownThread(mPACThread); + NS_DispatchToMainThread(runnable); + } + } + + NS_ASSERTION(mLoader == nullptr, "pac man not shutdown properly"); + 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; + } + mShutdown = true; + CancelExistingLoad(); + PostCancelPendingQ(NS_ERROR_ABORT); + + RefPtr<WaitForThreadShutdown> runnable = new WaitForThreadShutdown(this); + NS_DispatchToMainThread(runnable); +} + +nsresult +nsPACMan::AsyncGetProxyForURI(nsIURI *uri, + nsPACManCallback *callback, + 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(EmptyCString()); + } + + RefPtr<PendingPACQuery> query = + new PendingPACQuery(this, uri, callback, mainThreadResponse); + + if (IsPACURI(uri)) { + // deal with this directly instead of queueing it + query->Complete(NS_OK, EmptyCString()); + return NS_OK; + } + + return mPACThread->Dispatch(query, nsIEventTarget::DISPATCH_NORMAL); +} + +nsresult +nsPACMan::PostQuery(PendingPACQuery *query) +{ + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + + if (mShutdown) { + query->Complete(NS_ERROR_NOT_AVAILABLE, EmptyCString()); + 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 nsCString &spec) +{ + NS_ENSURE_STATE(!mShutdown); + NS_ENSURE_ARG(!spec.IsEmpty() || !mPACURISpec.IsEmpty()); + + nsCOMPtr<nsIStreamLoader> loader = + do_CreateInstance(NS_STREAMLOADER_CONTRACTID); + NS_ENSURE_STATE(loader); + + LOG(("nsPACMan::LoadPACFromURI %s\n", spec.get())); + // 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) { + nsresult rv; + if (NS_FAILED(rv = NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsPACMan::StartLoading)))) + return rv; + mLoadPending = true; + } + + CancelExistingLoad(); + + mLoader = loader; + if (!spec.IsEmpty()) { + mPACURISpec = spec; + mPACURIRedirectSpec.Truncate(); + mNormalPACURISpec.Truncate(); // set at load time + mLoadFailureCount = 0; // reset + } + + // reset to Null + mScheduledReload = TimeStamp(); + return NS_OK; +} + +void +nsPACMan::StartLoading() +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + mLoadPending = false; + + // CancelExistingLoad was called... + if (!mLoader) { + PostCancelPendingQ(NS_ERROR_ABORT); + return; + } + + if (NS_SUCCEEDED(mLoader->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_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // aLoadGroup + nullptr, // aCallbacks + nsIRequest::LOAD_NORMAL, + ios); + } + else { + LOG(("nsPACMan::StartLoading Failed pacspec uri conversion %s\n", + mPACURISpec.get())); + } + + if (channel) { + channel->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE); + channel->SetNotificationCallbacks(this); + if (NS_SUCCEEDED(channel->AsyncOpen2(mLoader))) + 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, 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() +{ + if (mLoader) { + nsCOMPtr<nsIRequest> request; + mLoader->GetRequest(getter_AddRefs(request)); + if (request) + request->Cancel(NS_ERROR_ABORT); + mLoader = nullptr; + } +} + +void +nsPACMan::PostProcessPendingQ() +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + RefPtr<ExecutePACThreadAction> pending = + new ExecutePACThreadAction(this); + if (mPACThread) + mPACThread->Dispatch(pending, nsIEventTarget::DISPATCH_NORMAL); +} + +void +nsPACMan::PostCancelPendingQ(nsresult status) +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + RefPtr<ExecutePACThreadAction> pending = + new ExecutePACThreadAction(this); + pending->CancelQueue(status); + if (mPACThread) + mPACThread->Dispatch(pending, nsIEventTarget::DISPATCH_NORMAL); +} + +void +nsPACMan::CancelPendingQ(nsresult status) +{ + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + RefPtr<PendingPACQuery> query; + + while (!mPendingQ.isEmpty()) { + query = dont_AddRef(mPendingQ.popLast()); + query->Complete(status, EmptyCString()); + } + + if (mShutdown) + 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())); + + if (mShutdown || IsLoading()) { + query->Complete(NS_ERROR_NOT_AVAILABLE, EmptyCString()); + 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))) { + 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) { + nsresult status = mPAC.GetProxyForURI(query->mSpec, query->mHost, + pacString); + LOG(("Use proxy from PAC: %s\n", pacString.get())); + query->Complete(status, pacString); + } + + 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"); + if (mLoader != 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; + } + + LOG(("OnStreamComplete: entry\n")); + + if (NS_SUCCEEDED(status) && HttpRequestSucceeded(loader)) { + // 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); + } + } + + // We assume that the PAC text is ASCII (or ISO-Latin-1). We've had this + // assumption forever, and some real-world PAC scripts actually have some + // non-ASCII text in comment blocks (see bug 296163). + const char *text = (const char *) data; + + // we have 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 is where it will be used. + + RefPtr<ExecutePACThreadAction> pending = + new ExecutePACThreadAction(this); + pending->SetupPAC(text, dataLen, pacURI); + if (mPACThread) + mPACThread->Dispatch(pending, nsIEventTarget::DISPATCH_NORMAL); + + 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_FAILURE); + return promptFac->GetPrompt(nullptr, iid, reinterpret_cast<void**>(result)); + } + + // 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; +} + +void +nsPACMan::NamePACThread() +{ + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); + PR_SetCurrentThreadName("Proxy Resolution"); +} + +nsresult +nsPACMan::Init(nsISystemProxySettings *systemProxySettings) +{ + mSystemProxySettings = systemProxySettings; + + nsresult rv = NS_NewThread(getter_AddRefs(mPACThread), nullptr); + if (NS_FAILED(rv)) + return rv; + + // don't check return value as it is not a big deal for this to fail. + mPACThread->Dispatch(NewRunnableMethod(this, &nsPACMan::NamePACThread), + nsIEventTarget::DISPATCH_NORMAL); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsPACMan.h b/netwerk/base/nsPACMan.h new file mode 100644 index 000000000..def0843cb --- /dev/null +++ b/netwerk/base/nsPACMan.h @@ -0,0 +1,248 @@ +/* -*- 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 "nsIStreamLoader.h" +#include "nsIInterfaceRequestor.h" +#include "nsIChannelEventSink.h" +#include "ProxyAutoConfig.h" +#include "nsThreadUtils.h" +#include "nsIURI.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "mozilla/Attributes.h" +#include "mozilla/LinkedList.h" +#include "nsAutoPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Logging.h" +#include "mozilla/Atomics.h" + +class nsISystemProxySettings; +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 nsCString &pacString, + const nsCString &newPACURL) = 0; +}; + +class PendingPACQuery final : public Runnable, + public LinkedListElement<PendingPACQuery> +{ +public: + PendingPACQuery(nsPACMan *pacMan, nsIURI *uri, + nsPACManCallback *callback, + bool mainThreadResponse); + + // can be called from either thread + void Complete(nsresult status, const nsCString &pacString); + void UseAlternatePACFile(const nsCString &pacURL); + + nsCString mSpec; + nsCString mScheme; + nsCString mHost; + int32_t mPort; + + NS_IMETHOD Run(void); /* 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: + NS_DECL_THREADSAFE_ISUPPORTS + + nsPACMan(); + + /** + * 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 mustCallbackOnMainThread + * If set to false the callback can be made from the PAC thread + */ + nsresult AsyncGetProxyForURI(nsIURI *uri, + nsPACManCallback *callback, + bool mustCallbackOnMainThread); + + /** + * 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 pacSpec + * 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 nsCString &pacSpec); + + /** + * Returns true if we are currently loading the PAC file. + */ + bool IsLoading() { return mLoader != 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); + } + + nsresult Init(nsISystemProxySettings *); + static nsPACMan *sInstance; + + // PAC thread operations only + void ProcessPendingQ(); + void CancelPendingQ(nsresult); + +private: + NS_DECL_NSISTREAMLOADEROBSERVER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + + friend class PendingPACQuery; + friend class PACLoadComplete; + friend class ExecutePACThreadAction; + friend class WaitForThreadShutdown; + + ~nsPACMan(); + + /** + * Cancel any existing load if any. + */ + void CancelExistingLoad(); + + /** + * Start loading the PAC file. + */ + void StartLoading(); + + /** + * 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); + + // PAC thread operations only + void PostProcessPendingQ(); + void PostCancelPendingQ(nsresult); + bool ProcessPending(); + void NamePACThread(); + +private: + ProxyAutoConfig mPAC; + nsCOMPtr<nsIThread> mPACThread; + nsCOMPtr<nsISystemProxySettings> mSystemProxySettings; + + 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; + + nsCOMPtr<nsIStreamLoader> mLoader; + bool mLoadPending; + Atomic<bool, Relaxed> mShutdown; + TimeStamp mScheduledReload; + uint32_t mLoadFailureCount; + + bool mInProgress; + bool mIncludePath; +}; + +extern LazyLogModule gProxyLog; + +} // namespace net +} // namespace mozilla + +#endif // nsPACMan_h__ diff --git a/netwerk/base/nsPILoadGroupInternal.idl b/netwerk/base/nsPILoadGroupInternal.idl new file mode 100644 index 000000000..6b12304a4 --- /dev/null +++ b/netwerk/base/nsPILoadGroupInternal.idl @@ -0,0 +1,28 @@ +/* -*- 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; + +/** + * Dumping ground for load group experimental work. + * This interface will never be frozen. If you are + * using any feature exposed by this interface, be aware that this interface + * will change and you will be broken. You have been warned. + */ +[scriptable, uuid(6ef2f8ac-9584-48f3-957a-0c94fff0c8c7)] +interface nsPILoadGroupInternal : nsISupports +{ + + /** + * Called when the load group has loaded main page and + * subresources. (i.e.essentially DOMComplete) + * + * @param aDefaultChanel + * The request channel for the base apge + */ + void OnEndPageLoad(in nsIChannel aDefaultChannel); +}; diff --git a/netwerk/base/nsPISocketTransportService.idl b/netwerk/base/nsPISocketTransportService.idl new file mode 100644 index 000000000..d49745fac --- /dev/null +++ b/netwerk/base/nsPISocketTransportService.idl @@ -0,0 +1,61 @@ +/* -*- 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. + */ +[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++ +/* + * Network activity indicator: we send out these topics no more than every + * blipIntervalMilliseconds (as set by the + * "network.activity.blipIntervalMilliseconds" preference: if 0 no notifications + * are sent) if the network is currently active (i.e. we're sending/receiving + * data to/from the socket). + */ +#define NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC "network-activity-blip-upload" +#define NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC "network-activity-blip-download" + +%} diff --git a/netwerk/base/nsPreloadedStream.cpp b/netwerk/base/nsPreloadedStream.cpp new file mode 100644 index 000000000..d203f5505 --- /dev/null +++ b/netwerk/base/nsPreloadedStream.cpp @@ -0,0 +1,153 @@ +/* -*- 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 "nsPreloadedStream.h" +#include "nsIRunnable.h" + +#include "nsThreadUtils.h" +#include <algorithm> + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(nsPreloadedStream, + nsIInputStream, + nsIAsyncInputStream) + +nsPreloadedStream::nsPreloadedStream(nsIAsyncInputStream *aStream, + const char *data, uint32_t datalen) + : mStream(aStream), + mOffset(0), + mLen(datalen) +{ + 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::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) + : mStream(aStream), + mCallback(aCallback) {} + + virtual ~RunOnThread() {} + + 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) + return mStream->AsyncWait(aCallback, 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); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsPreloadedStream.h b/netwerk/base/nsPreloadedStream.h new file mode 100644 index 000000000..afdc960e7 --- /dev/null +++ b/netwerk/base/nsPreloadedStream.h @@ -0,0 +1,52 @@ +/* -*- 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/. */ + +/** + * 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" + +namespace mozilla { +namespace net { + +class nsPreloadedStream final : public nsIAsyncInputStream +{ + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + nsPreloadedStream(nsIAsyncInputStream *aStream, + const char *data, uint32_t datalen); +private: + ~nsPreloadedStream(); + + nsCOMPtr<nsIAsyncInputStream> mStream; + + char *mBuf; + uint32_t mOffset; + uint32_t mLen; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/nsProtocolProxyService.cpp b/netwerk/base/nsProtocolProxyService.cpp new file mode 100644 index 000000000..26eca0e88 --- /dev/null +++ b/netwerk/base/nsProtocolProxyService.cpp @@ -0,0 +1,2146 @@ +/* -*- Mode: C++; 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 "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.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 "nsIDNSService.h" +#include "nsPIDNSService.h" +#include "nsIScriptSecurityManager.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsThreadUtils.h" +#include "nsSOCKSIOLayer.h" +#include "nsString.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "plstr.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/Logging.h" +#include "mozilla/Tokenizer.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[]; + +#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 + +#define WPAD_URL "http://wpad/wpad.dat" + +//---------------------------------------------------------------------------- + +// This structure is intended to be allocated on the stack +struct nsProtocolInfo { + nsAutoCString scheme; + uint32_t flags; + int32_t defaultPort; +}; + +//---------------------------------------------------------------------------- + +// 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; +} + +//----------------------------------------------------------------------------- + +// 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) + : mStatus(NS_OK) + , mDispatched(false) + , 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(mChannel.forget()); + } + + if (mCallback) { + NS_ReleaseOnMainThread(mCallback.forget()); + } + + if (mProxyInfo) { + NS_ReleaseOnMainThread(mProxyInfo.forget()); + } + + if (mXPComPPS) { + NS_ReleaseOnMainThread(mXPComPPS.forget()); + } + } + } + +public: + 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 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 nsCString &pacString, + const nsCString &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 = NS_LITERAL_CSTRING("DIRECT;"); + 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); + if (NS_SUCCEEDED(mStatus)) + mPPS->ApplyFilters(mChannel, info, mProxyInfo); + else + mProxyInfo = nullptr; + + if(pacAvailable) { + // if !pacAvailable, it was already logged above + LOG(("pac thread callback %s\n", mPACString.get())); + } + if (NS_SUCCEEDED(mStatus)) + mPPS->MaybeDisableDNSPrefetch(mProxyInfo); + mCallback->OnProxyAvailable(this, mChannel, mProxyInfo, mStatus); + } + 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, + 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 %X\n", mStatus)); + if (NS_SUCCEEDED(mStatus)) + mPPS->MaybeDisableDNSPrefetch(mProxyInfo); + 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; + nsCString mPACString; + nsCString mPACURL; + bool mDispatched; + uint32_t mResolveFlags; + + nsProtocolProxyService *mPPS; + nsCOMPtr<nsIProtocolProxyService> mXPComPPS; + nsCOMPtr<nsIChannel> mChannel; + nsCOMPtr<nsIProtocolProxyCallback> mCallback; + nsCOMPtr<nsIProxyInfo> mProxyInfo; +}; + +NS_IMPL_ISUPPORTS(nsAsyncResolveRequest, nsICancelable, nsIRunnable) + +//---------------------------------------------------------------------------- + +#define IS_ASCII_SPACE(_c) ((_c) == ' ' || (_c) == '\t') + +// +// 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]) & (~0L << (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]) & (~0L << (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]) & (~0L << (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]) & (~0L << (32 - mask_len))); + } +} + +static void +proxy_GetStringPref(nsIPrefBranch *aPrefBranch, + const char *aPref, + nsCString &aResult) +{ + nsXPIDLCString temp; + nsresult rv = aPrefBranch->GetCharPref(aPref, getter_Copies(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) +if ( aIID.Equals(NS_GET_IID(nsProtocolProxyService)) ) foundInterface = static_cast<nsIProtocolProxyService2*>(this); else +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() + : mFilterLocalHosts(false) + , mFilters(nullptr) + , mProxyConfig(PROXYCONFIG_DIRECT) + , mHTTPProxyPort(-1) + , mFTPProxyPort(-1) + , mHTTPSProxyPort(-1) + , mSOCKSProxyPort(-1) + , mSOCKSProxyVersion(4) + , mSOCKSProxyRemoteDNS(false) + , mProxyOverTLS(true) + , mPACMan(nullptr) + , mSessionStart(PR_Now()) + , mFailedProxyTimeout(30 * 60) // 30 minute default +{ +} + +nsProtocolProxyService::~nsProtocolProxyService() +{ + // These should have been cleaned up in our Observe method. + NS_ASSERTION(mHostFiltersArray.Length() == 0 && mFilters == nullptr && + 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) { + nsXPIDLCString pacSpec; + prefs->GetCharPref(PROXY_PREF("autoconfig_url"), + getter_Copies(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; +} + + +NS_IMETHODIMP +nsProtocolProxyService::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + // cleanup + if (mHostFiltersArray.Length() > 0) { + mHostFiltersArray.Clear(); + } + if (mFilters) { + delete mFilters; + mFilters = nullptr; + } + if (mPACMan) { + mPACMan->Shutdown(); + mPACMan = 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)) { + 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; +} + +void +nsProtocolProxyService::PrefsChanged(nsIPrefBranch *prefBranch, + const char *pref) +{ + nsresult rv = NS_OK; + bool reloadPAC = false; + nsXPIDLCString tempString; + + 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: %lu; 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("ftp"))) + proxy_GetStringPref(prefBranch, PROXY_PREF("ftp"), mFTPProxyHost); + + if (!pref || !strcmp(pref, PROXY_PREF("ftp_port"))) + proxy_GetIntPref(prefBranch, PROXY_PREF("ftp_port"), mFTPProxyPort); + + 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("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"), + getter_Copies(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"), + getter_Copies(tempString)); + if (mPACMan && !mPACMan->IsPACURI(tempString)) { + LOG(("PAC Thread URI Changed - Reset Pac Thread")); + ResetPACThread(); + } + } else if (mProxyConfig == PROXYCONFIG_WPAD) { + // 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. + tempString.AssignLiteral(WPAD_URL); + } else if (mSystemProxySettings) { + // Get System Proxy settings if available + mSystemProxySettings->GetPACURI(tempString); + } + if (!tempString.IsEmpty()) + ConfigureFromPAC(tempString, false); + } +} + +bool +nsProtocolProxyService::CanUseProxy(nsIURI *aURI, int32_t defaultPort) +{ + if (mHostFiltersArray.Length() == 0) + return true; + + 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('.')) || + host.EqualsLiteral("127.0.0.1") || + host.EqualsLiteral("::1")) { + 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())) { + HostInfo *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 (!PL_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 (PL_strncasecmp(start, kProxyType_HTTP, 5) == 0) { + type = kProxyType_HTTP; + } + break; + case 5: + if (PL_strncasecmp(start, kProxyType_PROXY, 5) == 0) { + type = kProxyType_HTTP; + } else if (PL_strncasecmp(start, kProxyType_SOCKS, 5) == 0) { + type = kProxyType_SOCKS4; // assume v4 for 4x compat + } else if (PL_strncasecmp(start, kProxyType_HTTPS, 5) == 0) { + type = kProxyType_HTTPS; + } + break; + case 6: + if (PL_strncasecmp(start, kProxyType_DIRECT, 6) == 0) + type = kProxyType_DIRECT; + else if (PL_strncasecmp(start, kProxyType_SOCKS4, 6) == 0) + type = kProxyType_SOCKS4; + else if (PL_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) { + const char *host = nullptr, *hostEnd = nullptr; + 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; + } + + 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; + if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(pacURI), maybeURL)) && + NS_SUCCEEDED(pacURI->GetAsciiHost(urlHost)) && + !urlHost.IsEmpty()) { + // http://www.example.com:8080 + + pi->mHost = urlHost; + + int32_t tPort; + if (NS_SUCCEEDED(pacURI->GetPort(&tPort)) && tPort != -1) { + port = tPort; + } + pi->mPort = port; + } + else { + // www.example.com:8080 + if (start < end) { + host = start; + hostEnd = strchr(host, ':'); + if (!hostEnd || hostEnd > end) { + hostEnd = end; + // no port, so assume default + } + else { + port = atoi(hostEnd + 1); + } + } + // YES, it is ok to specify a null proxy host. + if (host) { + pi->mHost.Assign(host, hostEnd - host); + pi->mPort = port; + } + } + NS_ADDREF(*result = pi); + } + + 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.Put(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() +{ + if (mPACMan) + return NS_OK; + + mPACMan = new nsPACMan(); + + 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 = 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) +{ + SetupPACThread(); + + if (mPACMan->IsPACURI(spec) && !forceReload) + return NS_OK; + + mFailedProxies.Clear(); + + 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; + + nsXPIDLCString pacSpec; + if (type == PROXYCONFIG_PAC) + prefs->GetCharPref(PROXY_PREF("autoconfig_url"), getter_Copies(pacSpec)); + else if (type == PROXYCONFIG_WPAD) + pacSpec.AssignLiteral(WPAD_URL); + + if (!pacSpec.IsEmpty()) + 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") + , mStatus(NS_OK) + , mCompleted(false) + { + } + + void OnQueryComplete(nsresult status, + const nsCString &pacString, + const nsCString &newPACURL) override + { + MutexAutoLock lock(mMutex); + mCompleted = true; + mStatus = status; + mPACString = pacString; + mPACURL = newPACURL; + mCondVar.Notify(); + } + + void Lock() { mMutex.Lock(); } + void Unlock() { mMutex.Unlock(); } + void Wait() { mCondVar.Wait(PR_SecondsToInterval(3)); } + +private: + ~nsAsyncBridgeRequest() + { + } + + friend class nsProtocolProxyService; + + Mutex mMutex; + CondVar mCondVar; + + nsresult mStatus; + nsCString mPACString; + nsCString mPACURL; + bool mCompleted; +}; +NS_IMPL_ISUPPORTS0(nsAsyncBridgeRequest) + +// nsProtocolProxyService +nsresult +nsProtocolProxyService::DeprecatedBlockingResolve(nsIChannel *aChannel, + uint32_t aFlags, + nsIProxyInfo **retval) +{ + NS_ENSURE_ARG_POINTER(aChannel); + + nsCOMPtr<nsIURI> uri; + nsresult rv = GetProxyURI(aChannel, getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + nsProtocolInfo info; + rv = GetProtocolInfo(uri, &info); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIProxyInfo> pi; + bool usePACThread; + + // 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(aChannel, info, aFlags, + &usePACThread, getter_AddRefs(pi)); + if (NS_FAILED(rv)) + return rv; + + if (!usePACThread || !mPACMan) { + ApplyFilters(aChannel, info, pi); + pi.forget(retval); + return NS_OK; + } + + // Use the PAC thread to do the work, so we don't have to reimplement that + // code, but block this thread on that completion. + RefPtr<nsAsyncBridgeRequest> ctx = new nsAsyncBridgeRequest(); + ctx->Lock(); + if (NS_SUCCEEDED(mPACMan->AsyncGetProxyForURI(uri, ctx, false))) { + // this can really block the main thread, so cap it at 3 seconds + ctx->Wait(); + } + ctx->Unlock(); + if (!ctx->mCompleted) + return NS_ERROR_FAILURE; + if (NS_FAILED(ctx->mStatus)) + return ctx->mStatus; + + // pretty much duplicate real DoCallback logic + + // Generate proxy info from the PAC string if appropriate + if (!ctx->mPACString.IsEmpty()) { + LOG(("sync pac thread callback %s\n", ctx->mPACString.get())); + ProcessPACString(ctx->mPACString, 0, getter_AddRefs(pi)); + ApplyFilters(aChannel, info, pi); + pi.forget(retval); + return NS_OK; + } + + if (!ctx->mPACURL.IsEmpty()) { + NS_WARNING("sync pac thread callback indicates new pac file load\n"); + // This is a problem and is one of the reasons this blocking interface + // is deprecated. The main loop needs to spin to make this reload happen. So + // we are going to kick off the reload and return an error - it will work + // next time. Because this sync interface is only used in the java plugin it + // is extremely likely that the pac file has already been loaded anyhow. + + rv = ConfigureFromPAC(ctx->mPACURL, false); + if (NS_FAILED(rv)) + return rv; + return NS_ERROR_NOT_AVAILABLE; + } + + *retval = nullptr; + return NS_OK; +} + +nsresult +nsProtocolProxyService::AsyncResolveInternal(nsIChannel *channel, uint32_t flags, + nsIProtocolProxyCallback *callback, + nsICancelable **result, + bool isSyncOK) +{ + 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; + + // 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 + ApplyFilters(channel, info, pi); + ctx->SetResult(NS_OK, pi); + if (isSyncOK) { + ctx->Run(); + return NS_OK; + } + + rv = ctx->DispatchCallback(); + if (NS_SUCCEEDED(rv)) + ctx.forget(result); + return rv; + } + + // else kick off a PAC thread query + + rv = mPACMan->AsyncGetProxyForURI(uri, ctx, true); + if (NS_SUCCEEDED(rv)) + ctx.forget(result); + return rv; +} + +// nsIProtocolProxyService +NS_IMETHODIMP +nsProtocolProxyService::AsyncResolve2(nsIChannel *channel, uint32_t flags, + nsIProtocolProxyCallback *callback, + nsICancelable **result) +{ + return AsyncResolveInternal(channel, flags, callback, result, true); +} + +NS_IMETHODIMP +nsProtocolProxyService::AsyncResolve(nsISupports *channelOrURI, uint32_t flags, + nsIProtocolProxyCallback *callback, + 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; + } + + nsCOMPtr<nsIScriptSecurityManager> secMan( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIPrincipal> systemPrincipal; + rv = secMan->GetSystemPrincipal(getter_AddRefs(systemPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + + // 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, + systemPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + } + + return AsyncResolveInternal(channel, flags, callback, result, false); +} + +NS_IMETHODIMP +nsProtocolProxyService::NewProxyInfo(const nsACString &aType, + const nsACString &aHost, + int32_t aPort, + uint32_t aFlags, + uint32_t aFailoverTimeout, + nsIProxyInfo *aFailoverProxy, + nsIProxyInfo **aResult) +{ + return NewProxyInfoWithAuth(aType, aHost, aPort, + EmptyCString(), EmptyCString(), + aFlags, aFailoverTimeout, + aFailoverProxy, aResult); +} + +NS_IMETHODIMP +nsProtocolProxyService::NewProxyInfoWithAuth(const nsACString &aType, + const nsACString &aHost, + int32_t aPort, + const nsACString &aUsername, + const nsACString &aPassword, + 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 (uint32_t i = 0; i < ArrayLength(types); ++i) { + if (aType.LowerCaseEqualsASCII(types[i])) { + type = types[i]; + 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, + aFlags, aFailoverTimeout, + aFailoverProxy, 0, aResult); +} + +NS_IMETHODIMP +nsProtocolProxyService::GetFailoverForProxy(nsIProxyInfo *aProxy, + nsIURI *aURI, + nsresult aStatus, + nsIProxyInfo **aResult) +{ + // We only support failover when a PAC file is configured, either + // directly or via system settings + if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD && + mProxyConfig != PROXYCONFIG_SYSTEM) + return NS_ERROR_NOT_AVAILABLE; + + // 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. + 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)); + + NS_ADDREF(*aResult = pi->mNext); + return NS_OK; +} + +nsresult +nsProtocolProxyService::InsertFilterLink(FilterLink *link, uint32_t position) +{ + if (!mFilters) { + mFilters = link; + return NS_OK; + } + + // insert into mFilters in sorted order + FilterLink *last = nullptr; + for (FilterLink *iter = mFilters; iter; iter = iter->next) { + if (position < iter->position) { + if (last) { + link->next = last->next; + last->next = link; + } + else { + link->next = mFilters; + mFilters = link; + } + return NS_OK; + } + last = iter; + } + // our position is equal to or greater than the last link in the list + last->next = link; + return NS_OK; +} + +NS_IMETHODIMP +nsProtocolProxyService::RegisterFilter(nsIProtocolProxyFilter *filter, + uint32_t position) +{ + UnregisterFilter(filter); // remove this filter if we already have it + + FilterLink *link = new FilterLink(position, filter); + if (!link) { + return NS_ERROR_OUT_OF_MEMORY; + } + return InsertFilterLink(link, position); +} + +NS_IMETHODIMP +nsProtocolProxyService::RegisterChannelFilter(nsIProtocolProxyChannelFilter *channelFilter, + uint32_t position) +{ + UnregisterChannelFilter(channelFilter); // remove this filter if we already have it + + FilterLink *link = new FilterLink(position, channelFilter); + if (!link) { + return NS_ERROR_OUT_OF_MEMORY; + } + return InsertFilterLink(link, position); +} + +nsresult +nsProtocolProxyService::RemoveFilterLink(nsISupports* givenObject) +{ + FilterLink *last = nullptr; + for (FilterLink *iter = mFilters; iter; iter = iter->next) { + nsCOMPtr<nsISupports> object = do_QueryInterface(iter->filter); + nsCOMPtr<nsISupports> object2 = do_QueryInterface(iter->channelFilter); + if (object == givenObject || object2 == givenObject) { + if (last) + last->next = iter->next; + else + mFilters = iter->next; + iter->next = nullptr; + delete iter; + return NS_OK; + } + last = iter; + } + + // No need to throw an exception in this case. + return NS_OK; +} + +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) +{ + // check to see the owners flag? /!?/ TODO + if (mHostFiltersArray.Length() > 0) { + mHostFiltersArray.Clear(); + } + + if (aFilters.IsEmpty()) { + return; + } + + // + // filter = ( host | domain | ipaddr ["/" mask] ) [":" port] + // filters = filter *( "," LWS filter) + // + // Reset mFilterLocalHosts - will be set to true if "<local>" is in pref string + mFilterLocalHosts = false; + + 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; + } else if (token.Equals(mozilla::Tokenizer::Token::Char('/'))) { + t.Claim(hostStr); + t.Record(); + parsingMask = true; + continue; + } else 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); + + 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) +{ + NS_PRECONDITION(uri, "URI is null"); + NS_PRECONDITION(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; + + nsCOMPtr<nsIProtocolHandler> handler; + rv = ios->GetProtocolHandler(info->scheme.get(), getter_AddRefs(handler)); + if (NS_FAILED(rv)) + return rv; + + rv = handler->DoGetProtocolFlags(uri, &info->flags); + if (NS_FAILED(rv)) + return rv; + + rv = handler->GetDefaultPort(&info->defaultPort); + return rv; +} + +nsresult +nsProtocolProxyService::NewProxyInfo_Internal(const char *aType, + const nsACString &aHost, + int32_t aPort, + const nsACString &aUsername, + const nsACString &aPassword, + 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); + } + + nsProxyInfo *proxyInfo = new nsProxyInfo(); + if (!proxyInfo) + return NS_ERROR_OUT_OF_MEMORY; + + 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; + failover.swap(proxyInfo->mNext); + + NS_ADDREF(*aResult = proxyInfo); + return NS_OK; +} + +nsresult +nsProtocolProxyService::Resolve_Internal(nsIChannel *channel, + const nsProtocolInfo &info, + uint32_t flags, + bool *usePACThread, + nsIProxyInfo **result) +{ + NS_ENSURE_ARG_POINTER(channel); + nsresult rv = SetupPACThread(); + if (NS_FAILED(rv)) + return rv; + + *usePACThread = false; + *result = nullptr; + + if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY)) + return NS_OK; // Can't proxy this (filters may not override) + + nsCOMPtr<nsIURI> uri; + 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; + + 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 + + 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); + + // now try the system proxy settings for this particular url + if (NS_SUCCEEDED(mSystemProxySettings-> + GetProxyForURI(spec, scheme, host, port, + pacString))) { + ProcessPACString(pacString, 0, 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 (!mFTPProxyHost.IsEmpty() && mFTPProxyPort > 0 && + !(flags & RESOLVE_IGNORE_URI_SCHEME) && + info.scheme.EqualsLiteral("ftp")) { + host = &mFTPProxyHost; + type = kProxyType_HTTP; + port = mFTPProxyPort; + } + 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, + EmptyCString(), EmptyCString(), + 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; + + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + 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::ApplyFilters(nsIChannel *channel, + const nsProtocolInfo &info, + nsIProxyInfo **list) +{ + if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY)) + return; + + // 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. + + nsCOMPtr<nsIProxyInfo> result; + + for (FilterLink *iter = mFilters; iter; iter = iter->next) { + PruneProxyInfo(info, list); + nsresult rv = NS_OK; + if (iter->filter) { + nsCOMPtr<nsIURI> uri; + rv = GetProxyURI(channel, getter_AddRefs(uri)); + if (uri) { + rv = iter->filter->ApplyFilter(this, uri, *list, + getter_AddRefs(result)); + } + } else if (iter->channelFilter) { + rv = iter->channelFilter->ApplyFilter(this, channel, *list, + getter_AddRefs(result)); + } + if (NS_FAILED(rv)) + continue; + result.swap(*list); + } + + PruneProxyInfo(info, list); +} + +void +nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo &info, + nsIProxyInfo **list) +{ + if (!*list) + return; + nsProxyInfo *head = nullptr; + CallQueryInterface(*list, &head); + if (!head) { + NS_NOTREACHED("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; + } + + // Now, scan to see if all remaining proxies are disabled. If so, then + // we'll just bail and return them all. Otherwise, we'll go and prune the + // disabled ones. + + bool allDisabled = true; + + nsProxyInfo *iter; + for (iter = head; iter; iter = iter->mNext) { + if (!IsProxyDisabled(iter)) { + allDisabled = false; + break; + } + } + + if (allDisabled) + 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 +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsProtocolProxyService.h b/netwerk/base/nsProtocolProxyService.h new file mode 100644 index 000000000..9fe24699e --- /dev/null +++ b/netwerk/base/nsProtocolProxyService.h @@ -0,0 +1,414 @@ +/* -*- 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/. */ + +#ifndef nsProtocolProxyService_h__ +#define nsProtocolProxyService_h__ + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsTArray.h" +#include "nsIProtocolProxyService2.h" +#include "nsIProtocolProxyFilter.h" +#include "nsIProxyInfo.h" +#include "nsIObserver.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "prio.h" +#include "mozilla/Attributes.h" + +class nsIPrefBranch; +class nsISystemProxySettings; + +namespace mozilla { +namespace net { + +typedef nsDataHashtable<nsCStringHashKey, uint32_t> nsFailedProxyTable; + +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: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROTOCOLPROXYSERVICE2 + NS_DECL_NSIPROTOCOLPROXYSERVICE + NS_DECL_NSIOBSERVER + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROTOCOL_PROXY_SERVICE_IMPL_CID) + + nsProtocolProxyService(); + + nsresult Init(); + nsresult DeprecatedBlockingResolve(nsIChannel *aChannel, + uint32_t aFlags, + nsIProxyInfo **retval); + +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 *prefs, const char *name); + + /** + * 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 *proxy, + 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 &pacURI, 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 &result); + + /** + * @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 *result); + + /** + * 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, + uint32_t flags, + uint32_t timeout, + nsIProxyInfo *next, + 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); + + /** + * This method applies the registered filters to the given proxy info + * list, and returns a possibly modified list. + * + * @param channel + * The channel corresponding to this 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 ApplyFilters(nsIChannel *channel, const nsProtocolInfo &info, + nsIProxyInfo **proxyInfo); + + /** + * This method is a simple wrapper around ApplyFilters that takes the + * proxy info list inout param as a nsCOMPtr. + */ + inline void ApplyFilters(nsIChannel *channel, const nsProtocolInfo &info, + nsCOMPtr<nsIProxyInfo> &proxyInfo) + { + nsIProxyInfo *pi = nullptr; + proxyInfo.swap(pi); + ApplyFilters(channel, info, &pi); + proxyInfo.swap(pi); + } + + /** + * 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 **proxyInfo); + + /** + * This method populates mHostFiltersArray from the given string. + * + * @param hostFilters + * A "no-proxy-for" exclusion list. + */ + void LoadHostFilters(const nsACString& hostFilters); + + /** + * 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(); + nsresult ResetPACThread(); + nsresult ReloadNetworkPAC(); + +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; + int32_t port; + union { + HostInfoIP ip; + HostInfoName name; + }; + + HostInfo() + : is_ipaddr(false) + , port(0) + { /* other members intentionally uninitialized */ } + ~HostInfo() { + if (!is_ipaddr && name.host) + free(name.host); + } + }; + + // An instance of this struct is allocated for each registered + // nsIProtocolProxyFilter and each nsIProtocolProxyChannelFilter. + struct FilterLink { + struct FilterLink *next; + uint32_t position; + nsCOMPtr<nsIProtocolProxyFilter> filter; + nsCOMPtr<nsIProtocolProxyChannelFilter> channelFilter; + FilterLink(uint32_t p, nsIProtocolProxyFilter *f) + : next(nullptr), position(p), filter(f), channelFilter(nullptr) {} + FilterLink(uint32_t p, nsIProtocolProxyChannelFilter *cf) + : next(nullptr), position(p), filter(nullptr), channelFilter(cf) {} + // Chain deletion to simplify cleaning up the filter links + ~FilterLink() { if (next) delete next; } + }; + +private: + // Private methods to insert and remove FilterLinks from the FilterLink chain. + nsresult InsertFilterLink(FilterLink *link, uint32_t position); + nsresult RemoveFilterLink(nsISupports *givenObject); + +protected: + // Indicates if local hosts (plain hostnames, no dots) should use the proxy + bool mFilterLocalHosts; + + // Holds an array of HostInfo objects + nsTArray<nsAutoPtr<HostInfo> > mHostFiltersArray; + + // Points to the start of a sorted by position, singly linked list + // of FilterLink objects. + FilterLink *mFilters; + + uint32_t mProxyConfig; + + nsCString mHTTPProxyHost; + int32_t mHTTPProxyPort; + + nsCString mFTPProxyHost; + int32_t mFTPProxyPort; + + nsCString mHTTPSProxyHost; + int32_t mHTTPSProxyPort; + + // mSOCKSProxyTarget could be a host, a domain socket path, + // or a named-pipe name. + nsCString mSOCKSProxyTarget; + int32_t mSOCKSProxyPort; + int32_t mSOCKSProxyVersion; + bool mSOCKSProxyRemoteDNS; + bool mProxyOverTLS; + + RefPtr<nsPACMan> mPACMan; // non-null if we are using PAC + nsCOMPtr<nsISystemProxySettings> mSystemProxySettings; + + PRTime mSessionStart; + nsFailedProxyTable mFailedProxies; + int32_t mFailedProxyTimeout; + +private: + nsresult AsyncResolveInternal(nsIChannel *channel, uint32_t flags, + nsIProtocolProxyCallback *callback, + nsICancelable **result, + bool isSyncOK); + +}; + +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 000000000..8291711ab --- /dev/null +++ b/netwerk/base/nsProxyInfo.cpp @@ -0,0 +1,126 @@ +/* -*- 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 "nsCOMPtr.h" + +namespace mozilla { +namespace net { + +// Yes, we support QI to nsProxyInfo +NS_IMPL_ISUPPORTS(nsProxyInfo, nsProxyInfo, nsIProxyInfo) + +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::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; +} + +// 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[]; + +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; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsProxyInfo.h b/netwerk/base/nsProxyInfo.h new file mode 100644 index 000000000..007387b77 --- /dev/null +++ b/netwerk/base/nsProxyInfo.h @@ -0,0 +1,82 @@ +/* -*- 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/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 { + +// 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() { return mHost; } + int32_t Port() { return mPort; } + const char *Type() { return mType; } + uint32_t Flags() { return mFlags; } + const nsCString &Username() { return mUsername; } + const nsCString &Password() { return mPassword; } + + bool IsDirect(); + bool IsHTTP(); + bool IsHTTPS(); + bool IsSOCKS(); + +private: + friend class nsProtocolProxyService; + + explicit nsProxyInfo(const char *type = nullptr) + : mType(type) + , mPort(-1) + , mFlags(0) + , mResolveFlags(0) + , mTimeout(UINT32_MAX) + , mNext(nullptr) + {} + + ~nsProxyInfo() + { + NS_IF_RELEASE(mNext); + } + + const char *mType; // pointer to statically allocated value + nsCString mHost; + nsCString mUsername; + nsCString mPassword; + int32_t mPort; + uint32_t mFlags; + uint32_t mResolveFlags; + uint32_t mTimeout; + nsProxyInfo *mNext; +}; + +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 000000000..3482330e6 --- /dev/null +++ b/netwerk/base/nsReadLine.h @@ -0,0 +1,134 @@ +/* -*- 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 (1) { // 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/nsRequestObserverProxy.cpp b/netwerk/base/nsRequestObserverProxy.cpp new file mode 100644 index 000000000..4c3c718ba --- /dev/null +++ b/netwerk/base/nsRequestObserverProxy.cpp @@ -0,0 +1,195 @@ +/* -*- 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 "mozilla/DebugOnly.h" + +#include "nscore.h" +#include "nsRequestObserverProxy.h" +#include "nsIRequest.h" +#include "nsAutoPtr.h" +#include "mozilla/Logging.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) + : mRequest(request) +{ + NS_PRECONDITION(mRequest, "null pointer"); +} + +//----------------------------------------------------------------------------- +// nsOnStartRequestEvent internal class... +//----------------------------------------------------------------------------- + +class nsOnStartRequestEvent : public nsARequestObserverEvent +{ + RefPtr<nsRequestObserverProxy> mProxy; +public: + nsOnStartRequestEvent(nsRequestObserverProxy *proxy, + nsIRequest *request) + : nsARequestObserverEvent(request) + , mProxy(proxy) + { + NS_PRECONDITION(mProxy, "null pointer"); + } + + virtual ~nsOnStartRequestEvent() {} + + NS_IMETHOD Run() override + { + LOG(("nsOnStartRequestEvent::HandleEvent [req=%x]\n", mRequest.get())); + + if (!mProxy->mObserver) { + NS_NOTREACHED("already handled onStopRequest event (observer is null)"); + return NS_OK; + } + + LOG(("handle startevent=%p\n", this)); + nsresult rv = mProxy->mObserver->OnStartRequest(mRequest, mProxy->mContext); + if (NS_FAILED(rv)) { + LOG(("OnStartRequest failed [rv=%x] canceling request!\n", rv)); + rv = mRequest->Cancel(rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed for request!"); + } + + return NS_OK; + } +}; + +//----------------------------------------------------------------------------- +// nsOnStopRequestEvent internal class... +//----------------------------------------------------------------------------- + +class nsOnStopRequestEvent : public nsARequestObserverEvent +{ + RefPtr<nsRequestObserverProxy> mProxy; +public: + nsOnStopRequestEvent(nsRequestObserverProxy *proxy, + nsIRequest *request) + : nsARequestObserverEvent(request) + , mProxy(proxy) + { + NS_PRECONDITION(mProxy, "null pointer"); + } + + virtual ~nsOnStopRequestEvent() {} + + NS_IMETHOD Run() override + { + LOG(("nsOnStopRequestEvent::HandleEvent [req=%x]\n", mRequest.get())); + + nsMainThreadPtrHandle<nsIRequestObserver> observer = mProxy->mObserver; + if (!observer) { + NS_NOTREACHED("already handled onStopRequest event (observer is null)"); + return NS_OK; + } + // Do not allow any more events to be handled after OnStopRequest + mProxy->mObserver = 0; + + 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, mProxy->mContext, status); + + return NS_OK; + } +}; + +//----------------------------------------------------------------------------- +// nsRequestObserverProxy::nsISupports implementation... +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsRequestObserverProxy, + nsIRequestObserver, + nsIRequestObserverProxy) + +//----------------------------------------------------------------------------- +// nsRequestObserverProxy::nsIRequestObserver implementation... +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsRequestObserverProxy::OnStartRequest(nsIRequest *request, + nsISupports *context) +{ + MOZ_ASSERT(!context || context == mContext); + LOG(("nsRequestObserverProxy::OnStartRequest [this=%p req=%x]\n", this, request)); + + nsOnStartRequestEvent *ev = + new nsOnStartRequestEvent(this, request); + if (!ev) + return NS_ERROR_OUT_OF_MEMORY; + + LOG(("post startevent=%p\n", ev)); + nsresult rv = FireEvent(ev); + if (NS_FAILED(rv)) + delete ev; + return rv; +} + +NS_IMETHODIMP +nsRequestObserverProxy::OnStopRequest(nsIRequest *request, + nsISupports *context, + nsresult status) +{ + MOZ_ASSERT(!context || context == mContext); + LOG(("nsRequestObserverProxy: OnStopRequest [this=%p req=%x status=%x]\n", + this, request, 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). + + nsOnStopRequestEvent *ev = + new nsOnStopRequestEvent(this, request); + if (!ev) + return NS_ERROR_OUT_OF_MEMORY; + + LOG(("post stopevent=%p\n", ev)); + nsresult rv = FireEvent(ev); + if (NS_FAILED(rv)) + delete ev; + return rv; +} + +//----------------------------------------------------------------------------- +// nsRequestObserverProxy::nsIRequestObserverProxy implementation... +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsRequestObserverProxy::Init(nsIRequestObserver *observer, nsISupports *context) +{ + NS_ENSURE_ARG_POINTER(observer); + mObserver = new nsMainThreadPtrHolder<nsIRequestObserver>(observer); + mContext = new nsMainThreadPtrHolder<nsISupports>(context); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsRequestObserverProxy implementation... +//----------------------------------------------------------------------------- + +nsresult +nsRequestObserverProxy::FireEvent(nsARequestObserverEvent *event) +{ + nsCOMPtr<nsIEventTarget> mainThread(do_GetMainThread()); + 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 000000000..0ad07186b --- /dev/null +++ b/netwerk/base/nsRequestObserverProxy.h @@ -0,0 +1,58 @@ +/* -*- 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/. */ + +#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() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIREQUESTOBSERVERPROXY + + nsRequestObserverProxy() {} + + 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() {} + + nsCOMPtr<nsIRequest> mRequest; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsRequestObserverProxy_h__ diff --git a/netwerk/base/nsSecCheckWrapChannel.cpp b/netwerk/base/nsSecCheckWrapChannel.cpp new file mode 100644 index 000000000..330079a0f --- /dev/null +++ b/netwerk/base/nsSecCheckWrapChannel.cpp @@ -0,0 +1,196 @@ +/* -*- 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 "nsContentSecurityManager.h" +#include "nsSecCheckWrapChannel.h" +#include "nsIForcePendingChannel.h" +#include "nsIStreamListener.h" +#include "mozilla/Logging.h" +#include "nsCOMPtr.h" + +namespace mozilla { +namespace net { + +static LazyLogModule gChannelWrapperLog("ChannelWrapper"); +#define CHANNELWRAPPERLOG(args) MOZ_LOG(gChannelWrapperLog, LogLevel::Debug, args) + +NS_IMPL_ADDREF(nsSecCheckWrapChannelBase) +NS_IMPL_RELEASE(nsSecCheckWrapChannelBase) + +NS_INTERFACE_MAP_BEGIN(nsSecCheckWrapChannelBase) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannel, mHttpChannel) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIHttpChannelInternal, mHttpChannelInternal) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHttpChannel) + NS_INTERFACE_MAP_ENTRY(nsIRequest) + NS_INTERFACE_MAP_ENTRY(nsIChannel) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUploadChannel, mUploadChannel) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUploadChannel2, mUploadChannel2) + NS_INTERFACE_MAP_ENTRY(nsISecCheckWrapChannel) +NS_INTERFACE_MAP_END + +//--------------------------------------------------------- +// nsSecCheckWrapChannelBase implementation +//--------------------------------------------------------- + +nsSecCheckWrapChannelBase::nsSecCheckWrapChannelBase(nsIChannel* aChannel) + : mChannel(aChannel) + , mHttpChannel(do_QueryInterface(aChannel)) + , mHttpChannelInternal(do_QueryInterface(aChannel)) + , mRequest(do_QueryInterface(aChannel)) + , mUploadChannel(do_QueryInterface(aChannel)) + , mUploadChannel2(do_QueryInterface(aChannel)) +{ + MOZ_ASSERT(mChannel, "can not create a channel wrapper without a channel"); +} + +nsSecCheckWrapChannelBase::~nsSecCheckWrapChannelBase() +{ +} + +//--------------------------------------------------------- +// nsISecCheckWrapChannel implementation +//--------------------------------------------------------- + +NS_IMETHODIMP +nsSecCheckWrapChannelBase::GetInnerChannel(nsIChannel **aInnerChannel) +{ + NS_IF_ADDREF(*aInnerChannel = mChannel); + return NS_OK; +} + +//--------------------------------------------------------- +// nsSecCheckWrapChannel implementation +//--------------------------------------------------------- + +nsSecCheckWrapChannel::nsSecCheckWrapChannel(nsIChannel* aChannel, + nsILoadInfo* aLoadInfo) + : nsSecCheckWrapChannelBase(aChannel) + , mLoadInfo(aLoadInfo) +{ + { + nsCOMPtr<nsIURI> uri; + mChannel->GetURI(getter_AddRefs(uri)); + CHANNELWRAPPERLOG(("nsSecCheckWrapChannel::nsSecCheckWrapChannel [%p] (%s)", + this, uri ? uri->GetSpecOrDefault().get() : "")); + } +} + +// static +already_AddRefed<nsIChannel> +nsSecCheckWrapChannel::MaybeWrap(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) +{ + // Maybe a custom protocol handler actually returns a gecko + // http/ftpChannel - To check this we will check whether the channel + // implements a gecko non-scriptable interface e.g. nsIForcePendingChannel. + nsCOMPtr<nsIForcePendingChannel> isGeckoChannel = do_QueryInterface(aChannel); + + nsCOMPtr<nsIChannel> channel; + if (isGeckoChannel) { + // If it is a gecko channel (ftp or http) we do not need to wrap it. + channel = aChannel; + channel->SetLoadInfo(aLoadInfo); + } else { + channel = new nsSecCheckWrapChannel(aChannel, aLoadInfo); + } + return channel.forget(); +} + +nsSecCheckWrapChannel::~nsSecCheckWrapChannel() +{ +} + +//--------------------------------------------------------- +// SecWrapChannelStreamListener helper +//--------------------------------------------------------- + +class SecWrapChannelStreamListener final : public nsIStreamListener +{ + public: + SecWrapChannelStreamListener(nsIRequest *aRequest, + nsIStreamListener *aStreamListener) + : mRequest(aRequest) + , mListener(aStreamListener) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + private: + ~SecWrapChannelStreamListener() {} + + nsCOMPtr<nsIRequest> mRequest; + nsCOMPtr<nsIStreamListener> mListener; +}; + +NS_IMPL_ISUPPORTS(SecWrapChannelStreamListener, + nsIStreamListener, + nsIRequestObserver) + +NS_IMETHODIMP +SecWrapChannelStreamListener::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + return mListener->OnStartRequest(mRequest, aContext); +} + +NS_IMETHODIMP +SecWrapChannelStreamListener::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatus) +{ + return mListener->OnStopRequest(mRequest, aContext, aStatus); +} + +NS_IMETHODIMP +SecWrapChannelStreamListener::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aInStream, + uint64_t aOffset, + uint32_t aCount) +{ + return mListener->OnDataAvailable(mRequest, aContext, aInStream, aOffset, aCount); +} + +//--------------------------------------------------------- +// nsIChannel implementation +//--------------------------------------------------------- + +NS_IMETHODIMP +nsSecCheckWrapChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) +{ + CHANNELWRAPPERLOG(("nsSecCheckWrapChannel::GetLoadInfo() [%p]",this)); + NS_IF_ADDREF(*aLoadInfo = mLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsSecCheckWrapChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) +{ + CHANNELWRAPPERLOG(("nsSecCheckWrapChannel::SetLoadInfo() [%p]", this)); + mLoadInfo = aLoadInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsSecCheckWrapChannel::AsyncOpen2(nsIStreamListener *aListener) +{ + nsCOMPtr<nsIStreamListener> secWrapChannelListener = + new SecWrapChannelStreamListener(this, aListener); + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, secWrapChannelListener); + NS_ENSURE_SUCCESS(rv, rv); + return AsyncOpen(secWrapChannelListener, nullptr); +} + +NS_IMETHODIMP +nsSecCheckWrapChannel::Open2(nsIInputStream** aStream) +{ + nsCOMPtr<nsIStreamListener> listener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return Open(aStream); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsSecCheckWrapChannel.h b/netwerk/base/nsSecCheckWrapChannel.h new file mode 100644 index 000000000..35300e0ea --- /dev/null +++ b/netwerk/base/nsSecCheckWrapChannel.h @@ -0,0 +1,106 @@ +/* -*- 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/. */ + +#ifndef nsSecCheckWrapChannel_h__ +#define nsSecCheckWrapChannel_h__ + +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIUploadChannel.h" +#include "nsIUploadChannel2.h" +#include "nsISecCheckWrapChannel.h" +#include "nsIWyciwygChannel.h" +#include "mozilla/LoadInfo.h" + +namespace mozilla { +namespace net { + +/* + * The nsSecCheckWrapChannelBase wraps channels that do *not* + * * provide a newChannel2() implementation + * * provide get/setLoadInfo functions + * + * In order to perform security checks for channels + * a) before opening the channel, and + * b) after redirects + * we are attaching a loadinfo object to every channel which + * provides information about the content-type of the channel, + * who initiated the load, etc. + * + * Addon created channels might *not* provide that loadInfo object for + * some transition time before we mark the NewChannel-API as deprecated. + * We do not want to break those addons hence we wrap such channels + * using the provided wrapper in this class. + * + * Please note that the wrapper only forwards calls for + * * nsIRequest + * * nsIChannel + * * nsIHttpChannel + * * nsIHttpChannelInternal + * * nsIUploadChannel + * * nsIUploadChannel2 + * + * In case any addon needs to query the inner channel this class + * provides a readonly function to query the wrapped channel. + * + */ + +class nsSecCheckWrapChannelBase : public nsIHttpChannel + , public nsIHttpChannelInternal + , public nsISecCheckWrapChannel + , public nsIUploadChannel + , public nsIUploadChannel2 +{ +public: + NS_FORWARD_NSIHTTPCHANNEL(mHttpChannel->) + NS_FORWARD_NSIHTTPCHANNELINTERNAL(mHttpChannelInternal->) + NS_FORWARD_NSICHANNEL(mChannel->) + NS_FORWARD_NSIREQUEST(mRequest->) + NS_FORWARD_NSIUPLOADCHANNEL(mUploadChannel->) + NS_FORWARD_NSIUPLOADCHANNEL2(mUploadChannel2->) + NS_DECL_NSISECCHECKWRAPCHANNEL + NS_DECL_ISUPPORTS + + explicit nsSecCheckWrapChannelBase(nsIChannel* aChannel); + +protected: + virtual ~nsSecCheckWrapChannelBase(); + + nsCOMPtr<nsIChannel> mChannel; + // We do a QI in the constructor to set the following pointers. + nsCOMPtr<nsIHttpChannel> mHttpChannel; + nsCOMPtr<nsIHttpChannelInternal> mHttpChannelInternal; + nsCOMPtr<nsIRequest> mRequest; + nsCOMPtr<nsIUploadChannel> mUploadChannel; + nsCOMPtr<nsIUploadChannel2> mUploadChannel2; +}; + +/* We define a separate class here to make it clear that we're overriding + * Get/SetLoadInfo as well as AsyncOpen2() and Open2(), rather that using + * the forwarded implementations provided by NS_FORWARD_NSICHANNEL" + */ +class nsSecCheckWrapChannel : public nsSecCheckWrapChannelBase +{ +public: + NS_IMETHOD GetLoadInfo(nsILoadInfo **aLoadInfo); + NS_IMETHOD SetLoadInfo(nsILoadInfo *aLoadInfo); + + NS_IMETHOD AsyncOpen2(nsIStreamListener *aListener); + NS_IMETHOD Open2(nsIInputStream** aStream); + + nsSecCheckWrapChannel(nsIChannel* aChannel, nsILoadInfo* aLoadInfo); + static already_AddRefed<nsIChannel> MaybeWrap(nsIChannel* aChannel, + nsILoadInfo* aLoadInfo); + +protected: + virtual ~nsSecCheckWrapChannel(); + + nsCOMPtr<nsILoadInfo> mLoadInfo; +}; + +} // namespace net +} // namespace mozilla + +#endif // nsSecCheckWrapChannel_h__ diff --git a/netwerk/base/nsSerializationHelper.cpp b/netwerk/base/nsSerializationHelper.cpp new file mode 100644 index 000000000..68f971eae --- /dev/null +++ b/netwerk/base/nsSerializationHelper.cpp @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsAutoPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsStringStream.h" + +using namespace mozilla; + +nsresult +NS_SerializeToString(nsISerializable* obj, nsCSubstring& str) +{ + RefPtr<nsBase64Encoder> stream(new nsBase64Encoder()); + if (!stream) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsIObjectOutputStream> objstream = + do_CreateInstance("@mozilla.org/binaryoutputstream;1"); + if (!objstream) + return NS_ERROR_OUT_OF_MEMORY; + + objstream->SetOutputStream(stream); + nsresult rv = + objstream->WriteCompoundObject(obj, NS_GET_IID(nsISupports), true); + NS_ENSURE_SUCCESS(rv, rv); + return stream->Finish(str); +} + +nsresult +NS_DeserializeObject(const nsCSubstring& str, nsISupports** obj) +{ + nsCString decodedData; + nsresult rv = Base64Decode(str, decodedData); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewCStringInputStream(getter_AddRefs(stream), decodedData); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIObjectInputStream> objstream = + do_CreateInstance("@mozilla.org/binaryinputstream;1"); + if (!objstream) + return NS_ERROR_OUT_OF_MEMORY; + + objstream->SetInputStream(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 000000000..4a0b7339f --- /dev/null +++ b/netwerk/base/nsSerializationHelper.h @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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, + nsCSubstring& str); + +/** + * Deserialize an object. + */ +nsresult NS_DeserializeObject(const nsCSubstring& str, + nsISupports** obj); + +class nsSerializationHelper final : public nsISerializationHelper +{ + ~nsSerializationHelper() {} + + NS_DECL_ISUPPORTS + NS_DECL_NSISERIALIZATIONHELPER +}; + +#endif diff --git a/netwerk/base/nsServerSocket.cpp b/netwerk/base/nsServerSocket.cpp new file mode 100644 index 000000000..8f5d3b029 --- /dev/null +++ b/netwerk/base/nsServerSocket.cpp @@ -0,0 +1,560 @@ +/* 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 "nsAutoPtr.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 "nsServiceManagerUtils.h" +#include "nsIFile.h" + +namespace mozilla { namespace net { + +static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); + +//----------------------------------------------------------------------------- + +typedef void (nsServerSocket:: *nsServerSocketFunc)(void); + +static nsresult +PostEvent(nsServerSocket *s, nsServerSocketFunc func) +{ + nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(s, func); + if (!gSocketTransportService) + return NS_ERROR_FAILURE; + + return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL); +} + +//----------------------------------------------------------------------------- +// nsServerSocket +//----------------------------------------------------------------------------- + +nsServerSocket::nsServerSocket() + : mFD(nullptr) + , mLock("nsServerSocket.mLock") + , mAttached(false) + , mKeepWhenOffline(false) +{ + // 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(kSocketTransportServiceCID); + } + // 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(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; + NetAddr clientAddr; + + // 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); + PRNetAddrToNetAddr(&prClientAddr, &clientAddr); + if (!clientFD) { + NS_WARNING("PR_Accept failed"); + mCondition = NS_ERROR_UNEXPECTED; + return; + } + + // 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 = mListener.forget(); + } + + // XXX we need to proxy the release to the listener's target thread to work + // around bug 337492. + if (listener) { + NS_ProxyRelease(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::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::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) +{ + 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()); + } + + 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() {} + +public: + explicit ServerSocketListenerProxy(nsIServerSocketListener* aListener) + : mListener(new nsMainThreadPtrHolder<nsIServerSocketListener>(aListener)) + , mTargetThread(do_GetCurrentThread()) + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISERVERSOCKETLISTENER + + class OnSocketAcceptedRunnable : public Runnable + { + public: + OnSocketAcceptedRunnable(const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener, + nsIServerSocket* aServ, + nsISocketTransport* aTransport) + : 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) + : mListener(aListener) + , mServ(aServ) + , mStatus(aStatus) + { } + + NS_DECL_NSIRUNNABLE + + private: + nsMainThreadPtrHandle<nsIServerSocketListener> mListener; + nsCOMPtr<nsIServerSocket> mServ; + nsresult mStatus; + }; + +private: + nsMainThreadPtrHandle<nsIServerSocketListener> mListener; + nsCOMPtr<nsIEventTarget> mTargetThread; +}; + +NS_IMPL_ISUPPORTS(ServerSocketListenerProxy, + nsIServerSocketListener) + +NS_IMETHODIMP +ServerSocketListenerProxy::OnSocketAccepted(nsIServerSocket* aServ, + nsISocketTransport* aTransport) +{ + RefPtr<OnSocketAcceptedRunnable> r = + new OnSocketAcceptedRunnable(mListener, aServ, aTransport); + return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +ServerSocketListenerProxy::OnStopListening(nsIServerSocket* aServ, + nsresult aStatus) +{ + RefPtr<OnStopListeningRunnable> r = + new OnStopListeningRunnable(mListener, aServ, aStatus); + return mTargetThread->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 = NS_GetCurrentThread(); + } + + // 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 000000000..ef5f24231 --- /dev/null +++ b/netwerk/base/nsServerSocket.h @@ -0,0 +1,67 @@ +/* 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 "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; + nsCOMPtr<nsIServerSocketListener> mListener; + +private: + void OnMsgClose(); + void OnMsgAttach(); + + // try attaching our socket (mFD) to the STS's poll list. + nsresult TryAttach(); + + // lock protects access to mListener; so it is not cleared while being used. + mozilla::Mutex mLock; + PRNetAddr mAddr; + nsCOMPtr<nsIEventTarget> mListenerTarget; + bool mAttached; + bool mKeepWhenOffline; +}; + +} // namespace net +} // namespace mozilla + +//----------------------------------------------------------------------------- + +#endif // nsServerSocket_h__ diff --git a/netwerk/base/nsSimpleNestedURI.cpp b/netwerk/base/nsSimpleNestedURI.cpp new file mode 100644 index 000000000..fd3f87a9c --- /dev/null +++ b/netwerk/base/nsSimpleNestedURI.cpp @@ -0,0 +1,190 @@ +/* -*- 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 "base/basictypes.h" + +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsSimpleNestedURI.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" + +#include "mozilla/ipc/URIUtils.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS_INHERITED(nsSimpleNestedURI, nsSimpleURI, nsINestedURI) + +nsSimpleNestedURI::nsSimpleNestedURI(nsIURI* innerURI) + : mInnerURI(innerURI) +{ + NS_ASSERTION(innerURI, "Must have inner URI"); + NS_TryToSetImmutable(innerURI); +} + +// nsISerializable + +NS_IMETHODIMP +nsSimpleNestedURI::Read(nsIObjectInputStream* aStream) +{ + nsresult rv = nsSimpleURI::Read(aStream); + if (NS_FAILED(rv)) return rv; + + NS_ASSERTION(!mMutable, "How did that happen?"); + + 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; + + NS_TryToSetImmutable(mInnerURI); + + 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; +} + +// nsIIPCSerializableURI +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()); + + NS_TryToSetImmutable(mInnerURI); + return true; +} + +// nsINestedURI + +NS_IMETHODIMP +nsSimpleNestedURI::GetInnerURI(nsIURI** uri) +{ + NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED); + + return NS_EnsureSafeToReturn(mInnerURI, uri); +} + +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; + if (refHandlingMode == eHonorRef) { + rv = mInnerURI->Clone(getter_AddRefs(innerClone)); + } else if (refHandlingMode == eReplaceRef) { + rv = mInnerURI->CloneWithNewRef(newRef, getter_AddRefs(innerClone)); + } else { + rv = mInnerURI->CloneIgnoringRef(getter_AddRefs(innerClone)); + } + + if (NS_FAILED(rv)) { + return nullptr; + } + + nsSimpleNestedURI* url = new nsSimpleNestedURI(innerClone); + SetRefOnClone(url, refHandlingMode, newRef); + url->SetMutable(false); + + return url; +} + +// nsIClassInfo overrides + +NS_IMETHODIMP +nsSimpleNestedURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) +{ + static NS_DEFINE_CID(kSimpleNestedURICID, NS_SIMPLENESTEDURI_CID); + + *aClassIDNoAlloc = kSimpleNestedURICID; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsSimpleNestedURI.h b/netwerk/base/nsSimpleNestedURI.h new file mode 100644 index 000000000..2402ba4d3 --- /dev/null +++ b/netwerk/base/nsSimpleNestedURI.h @@ -0,0 +1,75 @@ +/* -*- 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/. */ + +/** + * 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. All objects of this class should always be immutable, so that the + * inner URI and this URI don't get out of sync. The Clone() implementation + * will guarantee this for the clone, but it's up to the protocol handlers + * creating these URIs to ensure that in the first place. The innerURI passed + * to this URI will be set immutable if possible. + */ + +#ifndef nsSimpleNestedURI_h__ +#define nsSimpleNestedURI_h__ + +#include "nsCOMPtr.h" +#include "nsSimpleURI.h" +#include "nsINestedURI.h" + +#include "nsIIPCSerializableURI.h" + +namespace mozilla { +namespace net { + +class nsSimpleNestedURI : public nsSimpleURI, + public nsINestedURI +{ +protected: + ~nsSimpleNestedURI() {} + +public: + // To be used by deserialization only. Leaves this object in an + // uninitialized state that will throw on most accesses. + nsSimpleNestedURI() + { + } + + // Constructor that should generally be used when constructing an object of + // this class with |operator new|. + explicit nsSimpleNestedURI(nsIURI* innerURI); + + 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; + + // nsISerializable overrides + NS_IMETHOD Read(nsIObjectInputStream* aStream) override; + NS_IMETHOD Write(nsIObjectOutputStream* aStream) override; + + // nsIIPCSerializableURI overrides + NS_DECL_NSIIPCSERIALIZABLEURI + + // Override the nsIClassInfo method GetClassIDNoAlloc to make sure our + // nsISerializable impl works right. + NS_IMETHOD GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) override; + +protected: + nsCOMPtr<nsIURI> mInnerURI; +}; + +} // namespace net +} // namespace mozilla + +#endif /* nsSimpleNestedURI_h__ */ diff --git a/netwerk/base/nsSimpleStreamListener.cpp b/netwerk/base/nsSimpleStreamListener.cpp new file mode 100644 index 000000000..da00de281 --- /dev/null +++ b/netwerk/base/nsSimpleStreamListener.cpp @@ -0,0 +1,83 @@ +/* -*- 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 "nsSimpleStreamListener.h" + +namespace mozilla { +namespace net { + +// +//---------------------------------------------------------------------------- +// nsISupports implementation... +//---------------------------------------------------------------------------- +// +NS_IMPL_ISUPPORTS(nsSimpleStreamListener, + nsISimpleStreamListener, + nsIStreamListener, + nsIRequestObserver) + +// +//---------------------------------------------------------------------------- +// nsIRequestObserver implementation... +//---------------------------------------------------------------------------- +// +NS_IMETHODIMP +nsSimpleStreamListener::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + return mObserver ? + mObserver->OnStartRequest(aRequest, aContext) : NS_OK; +} + +NS_IMETHODIMP +nsSimpleStreamListener::OnStopRequest(nsIRequest* request, + nsISupports *aContext, + nsresult aStatus) +{ + return mObserver ? + mObserver->OnStopRequest(request, aContext, aStatus) : NS_OK; +} + +// +//---------------------------------------------------------------------------- +// nsIStreamListener implementation... +//---------------------------------------------------------------------------- +// +NS_IMETHODIMP +nsSimpleStreamListener::OnDataAvailable(nsIRequest* request, + nsISupports *aContext, + 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) +{ + NS_PRECONDITION(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 000000000..05f78a5bb --- /dev/null +++ b/netwerk/base/nsSimpleStreamListener.h @@ -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/. */ + +#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() { } + +protected: + virtual ~nsSimpleStreamListener() {} + + 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 000000000..ae5c51a1e --- /dev/null +++ b/netwerk/base/nsSimpleURI.cpp @@ -0,0 +1,847 @@ +/* -*- 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 "mozilla/DebugOnly.h" + +#undef LOG +#include "IPCMessageUtils.h" + +#include "nsSimpleURI.h" +#include "nscore.h" +#include "nsString.h" +#include "plstr.h" +#include "nsURLHelper.h" +#include "nsNetCID.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsEscape.h" +#include "nsError.h" +#include "nsIIPCSerializableURI.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ipc/URIUtils.h" + +using namespace mozilla::ipc; + +namespace mozilla { +namespace net { + +static NS_DEFINE_CID(kThisSimpleURIImplementationCID, + NS_THIS_SIMPLEURI_IMPLEMENTATION_CID); +static NS_DEFINE_CID(kSimpleURICID, NS_SIMPLEURI_CID); + +//////////////////////////////////////////////////////////////////////////////// +// nsSimpleURI methods: + +nsSimpleURI::nsSimpleURI() + : mMutable(true) + , mIsRefValid(false) + , mIsQueryValid(false) +{ +} + +nsSimpleURI::~nsSimpleURI() +{ +} + +NS_IMPL_ADDREF(nsSimpleURI) +NS_IMPL_RELEASE(nsSimpleURI) +NS_INTERFACE_TABLE_HEAD(nsSimpleURI) +NS_INTERFACE_TABLE(nsSimpleURI, nsIURI, nsIURIWithQuery, nsISerializable, + nsIClassInfo, nsIMutable, nsIIPCSerializableURI) +NS_INTERFACE_TABLE_TO_MAP_SEGUE + 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) +{ + nsresult rv; + + bool isMutable; + rv = aStream->ReadBoolean(&isMutable); + if (NS_FAILED(rv)) return rv; + mMutable = 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(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; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIIPCSerializableURI methods: + +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); + } + + params.isMutable() = mMutable; + + 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; + } + + mMutable = params.isMutable(); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIURI methods: + +NS_IMETHODIMP +nsSimpleURI::GetSpec(nsACString &result) +{ + if (!result.Assign(mScheme, fallible) || + !result.Append(NS_LITERAL_CSTRING(":"), fallible) || + !result.Append(mPath, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (mIsQueryValid) { + if (!result.Append(NS_LITERAL_CSTRING("?"), 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_LITERAL_CSTRING("#"), 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_LITERAL_CSTRING(":") + mPath; + if (mIsQueryValid) { + result += NS_LITERAL_CSTRING("?") + mQuery; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetHasRef(bool *result) +{ + *result = mIsRefValid; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::SetSpec(const nsACString &aSpec) +{ + NS_ENSURE_STATE(mMutable); + + // filter out unexpected chars "\r\n\t" if necessary + nsAutoCString filteredSpec; + net_FilterURIString(aSpec, filteredSpec); + + // nsSimpleURI currently restricts the charset to US-ASCII + nsAutoCString spec; + nsresult rv = NS_EscapeURL(filteredSpec, esc_OnlyNonASCII, spec, fallible); + if (NS_FAILED(rv)) { + return rv; + } + + int32_t colonPos = spec.FindChar(':'); + if (colonPos < 0 || !net_IsValidScheme(spec.get(), colonPos)) + return NS_ERROR_MALFORMED_URI; + + mScheme.Truncate(); + DebugOnly<int32_t> n = spec.Left(mScheme, colonPos); + NS_ASSERTION(n == colonPos, "Left failed"); + ToLowerCase(mScheme); + + // This sets mPath, mQuery and mRef. + return SetPath(Substring(spec, colonPos + 1)); +} + +NS_IMETHODIMP +nsSimpleURI::GetScheme(nsACString &result) +{ + result = mScheme; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::SetScheme(const nsACString &scheme) +{ + NS_ENSURE_STATE(mMutable); + + const nsPromiseFlatCString &flat = PromiseFlatCString(scheme); + if (!net_IsValidScheme(flat)) { + 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_LITERAL_CSTRING(":"); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetUserPass(nsACString &result) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::SetUserPass(const nsACString &userPass) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::GetUsername(nsACString &result) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::SetUsername(const nsACString &userName) +{ + NS_ENSURE_STATE(mMutable); + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::GetPassword(nsACString &result) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::SetPassword(const nsACString &password) +{ + NS_ENSURE_STATE(mMutable); + + 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; +} + +NS_IMETHODIMP +nsSimpleURI::SetHostPort(const nsACString &result) +{ + NS_ENSURE_STATE(mMutable); + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::SetHostAndPort(const nsACString &result) +{ + NS_ENSURE_STATE(mMutable); + + 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; +} + +NS_IMETHODIMP +nsSimpleURI::SetHost(const nsACString &host) +{ + NS_ENSURE_STATE(mMutable); + + 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; +} + +NS_IMETHODIMP +nsSimpleURI::SetPort(int32_t port) +{ + NS_ENSURE_STATE(mMutable); + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSimpleURI::GetPath(nsACString &result) +{ + result = mPath; + if (mIsQueryValid) { + result += NS_LITERAL_CSTRING("?") + mQuery; + } + if (mIsRefValid) { + result += NS_LITERAL_CSTRING("#") + mRef; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::SetPath(const nsACString &aPath) +{ + NS_ENSURE_STATE(mMutable); + + nsAutoCString path; + if (!path.Assign(aPath, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + int32_t queryPos = path.FindChar('?'); + int32_t hashPos = path.FindChar('#'); + + if (queryPos != kNotFound && hashPos != kNotFound && hashPos < queryPos) { + queryPos = kNotFound; + } + + nsAutoCString query; + if (queryPos != kNotFound) { + query.Assign(Substring(path, queryPos)); + path.Truncate(queryPos); + } + + nsAutoCString hash; + if (hashPos != kNotFound) { + if (query.IsEmpty()) { + hash.Assign(Substring(path, hashPos)); + path.Truncate(hashPos); + } else { + // We have to search the hash character in the query + hashPos = query.FindChar('#'); + hash.Assign(Substring(query, hashPos)); + query.Truncate(hashPos); + } + } + + mIsQueryValid = false; + mQuery.Truncate(); + + mIsRefValid = false; + mRef.Truncate(); + + // The path + if (!mPath.Assign(path, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = SetQuery(query); + if (NS_FAILED(rv)) { + return rv; + } + + return SetRef(hash); +} + +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 #). +NS_IMETHODIMP +nsSimpleURI::SetRef(const nsACString &aRef) +{ + NS_ENSURE_STATE(mMutable); + + nsAutoCString ref; + nsresult rv = NS_EscapeURL(aRef, esc_OnlyNonASCII, 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 + 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); + NS_PRECONDITION(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) +{ + NS_ENSURE_ARG_POINTER(o_Equals); + if (!i_Scheme) return NS_ERROR_NULL_POINTER; + + 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 = PL_strcasecmp(this_scheme, i_Scheme) ? false : true; + } 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); + } +} + +NS_IMETHODIMP +nsSimpleURI::Clone(nsIURI** result) +{ + return CloneInternal(eHonorRef, EmptyCString(), result); +} + +NS_IMETHODIMP +nsSimpleURI::CloneIgnoringRef(nsIURI** result) +{ + return CloneInternal(eIgnoreRef, EmptyCString(), result); +} + +NS_IMETHODIMP +nsSimpleURI::CloneWithNewRef(const nsACString &newRef, nsIURI** result) +{ + return CloneInternal(eReplaceRef, newRef, 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; + + // Note: |url| may well have mMutable false at this point, so + // don't call any setter methods. + 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) +{ + result = relativePath; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetAsciiSpec(nsACString &result) +{ + nsAutoCString buf; + nsresult rv = GetSpec(buf); + if (NS_FAILED(rv)) return rv; + return NS_EscapeURL(buf, esc_OnlyNonASCII|esc_AlwaysCopy, result, fallible); +} + +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; +} + +NS_IMETHODIMP +nsSimpleURI::GetOriginCharset(nsACString &result) +{ + result.Truncate(); + return NS_OK; +} + +//---------------------------------------------------------------------------- +// nsSimpleURI::nsIClassInfo +//---------------------------------------------------------------------------- + +NS_IMETHODIMP +nsSimpleURI::GetInterfaces(uint32_t *count, nsIID * **array) +{ + *count = 0; + *array = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetScriptableHelper(nsIXPCScriptable **_retval) +{ + *_retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetContractID(char * *aContractID) +{ + // Make sure to modify any subclasses as needed if this ever + // changes. + *aContractID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetClassDescription(char * *aClassDescription) +{ + *aClassDescription = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetClassID(nsCID * *aClassID) +{ + // Make sure to modify any subclasses as needed if this ever + // changes to not call the virtual GetClassIDNoAlloc. + *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID)); + if (!*aClassID) + return NS_ERROR_OUT_OF_MEMORY; + return GetClassIDNoAlloc(*aClassID); +} + +NS_IMETHODIMP +nsSimpleURI::GetFlags(uint32_t *aFlags) +{ + *aFlags = nsIClassInfo::MAIN_THREAD_ONLY; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) +{ + *aClassIDNoAlloc = kSimpleURICID; + return NS_OK; +} + +//---------------------------------------------------------------------------- +// nsSimpleURI::nsISimpleURI +//---------------------------------------------------------------------------- +NS_IMETHODIMP +nsSimpleURI::GetMutable(bool *value) +{ + *value = mMutable; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::SetMutable(bool value) +{ + NS_ENSURE_ARG(mMutable || !value); + + mMutable = value; + 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); +} + +//---------------------------------------------------------------------------- +// nsSimpleURI::nsIURIWithQuery +//---------------------------------------------------------------------------- + +NS_IMETHODIMP +nsSimpleURI::GetFilePath(nsACString& aFilePath) +{ + aFilePath = mPath; + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleURI::SetFilePath(const nsACString& aFilePath) +{ + return NS_ERROR_FAILURE; +} + +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::SetQuery(const nsACString& aQuery) +{ + NS_ENSURE_STATE(mMutable); + + 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 + return NS_OK; + } + + mIsQueryValid = true; + + // Gracefully skip initial question mark + if (query[0] == '?') { + mQuery = Substring(query, 1); + } else { + mQuery = query; + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsSimpleURI.h b/netwerk/base/nsSimpleURI.h new file mode 100644 index 000000000..29bc9b313 --- /dev/null +++ b/netwerk/base/nsSimpleURI.h @@ -0,0 +1,110 @@ +/* -*- 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/. */ + +#ifndef nsSimpleURI_h__ +#define nsSimpleURI_h__ + +#include "mozilla/MemoryReporting.h" +#include "nsIURI.h" +#include "nsIURIWithQuery.h" +#include "nsISerializable.h" +#include "nsString.h" +#include "nsIClassInfo.h" +#include "nsIMutable.h" +#include "nsISizeOf.h" +#include "nsIIPCSerializableURI.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 nsIURIWithQuery + , public nsISerializable + , public nsIClassInfo + , public nsIMutable + , public nsISizeOf + , public nsIIPCSerializableURI +{ +protected: + virtual ~nsSimpleURI(); + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSIURIWITHQUERY + NS_DECL_NSISERIALIZABLE + NS_DECL_NSICLASSINFO + NS_DECL_NSIMUTABLE + NS_DECL_NSIIPCSERIALIZABLEURI + + // nsSimpleURI methods: + + nsSimpleURI(); + + // 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 + }; + + // 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** clone); + + 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 mMutable; + bool mIsRefValid; // To distinguish between empty-ref and no-ref. + bool mIsQueryValid; // To distinguish between empty-query and no-query. +}; + +} // namespace net +} // namespace mozilla + +#endif // nsSimpleURI_h__ diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp new file mode 100644 index 000000000..1bfd1fc91 --- /dev/null +++ b/netwerk/base/nsSocketTransport2.cpp @@ -0,0 +1,3245 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsSocketTransport2.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Telemetry.h" +#include "nsIOService.h" +#include "nsStreamUtils.h" +#include "nsNetSegmentUtils.h" +#include "nsNetAddr.h" +#include "nsTransportUtils.h" +#include "nsProxyInfo.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "plstr.h" +#include "prerr.h" +#include "NetworkActivityMonitor.h" +#include "NSSErrorsService.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/net/NeckoChild.h" +#include "nsThreadUtils.h" +#include "nsISocketProviderService.h" +#include "nsISocketProvider.h" +#include "nsISSLSocketControl.h" +#include "nsIPipe.h" +#include "nsIClassInfoImpl.h" +#include "nsURLHelper.h" +#include "nsIDNSService.h" +#include "nsIDNSRecord.h" +#include "nsICancelable.h" +#include <algorithm> + +#include "nsPrintfCString.h" +#include "xpcpublic.h" + +#if defined(XP_WIN) +#include "mozilla/WindowsVersion.h" +#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(kSocketProviderServiceCID, NS_SOCKETPROVIDERSERVICE_CID); +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) + : mTransport(transport) + , mType(type) + , mStatus(status) + , mParam(param) + {} + + NS_IMETHOD Run() override + { + mTransport->OnSocketEvent(mType, mStatus, mParam); + return NS_OK; + } + +private: + RefPtr<nsSocketTransport> mTransport; + + uint32_t mType; + nsresult mStatus; + nsCOMPtr<nsISupports> mParam; +}; + +//----------------------------------------------------------------------------- + +//#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; + 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=%x]\n", errorCode, rv)); + return rv; +} + +//----------------------------------------------------------------------------- +// socket input stream impl +//----------------------------------------------------------------------------- + +nsSocketInputStream::nsSocketInputStream(nsSocketTransport *trans) + : mTransport(trans) + , mReaderRefCnt(0) + , mCondition(NS_OK) + , mCallbackFlags(0) + , mByteCount(0) +{ +} + +nsSocketInputStream::~nsSocketInputStream() +{ +} + +// 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=%x]\n", + this, condition)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong 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 = mCallback; + mCallback = nullptr; + 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::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); + 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=%x]\n", this, 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(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) + , mWriterRefCnt(0) + , mCondition(NS_OK) + , mCallbackFlags(0) + , mByteCount(0) +{ +} + +nsSocketOutputStream::~nsSocketOutputStream() +{ +} + +// 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=%x]\n", + this, condition)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong 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 = mCallback; + mCallback = nullptr; + 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::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); + 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=%x]\n", this, 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() + : mTypes(nullptr) + , mTypeCount(0) + , mPort(0) + , mProxyPort(0) + , mOriginPort(0) + , mProxyTransparent(false) + , mProxyTransparentResolvesHost(false) + , mHttpsProxy(false) + , mConnectionFlags(0) + , mState(STATE_CLOSED) + , mAttached(false) + , mInputClosed(true) + , mOutputClosed(true) + , mResolving(false) + , mNetAddrIsSet(false) + , mSelfAddrIsSet(false) + , mNetAddrPreResolved(false) + , mLock("nsSocketTransport.mLock") + , mFD(this) + , mFDref(0) + , mFDconnected(false) + , mSocketTransportService(gSocketTransportService) + , mInput(this) + , mOutput(this) + , mQoSBits(0x00) + , mKeepaliveEnabled(false) + , mKeepaliveIdleTimeS(-1) + , mKeepaliveRetryIntervalS(-1) + , mKeepaliveProbeCount(-1) + , mDoNotRetryToConnect(false) +{ + SOCKET_LOG(("creating nsSocketTransport @%p\n", this)); + + mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX; // no timeout + mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout +} + +nsSocketTransport::~nsSocketTransport() +{ + SOCKET_LOG(("destroying nsSocketTransport @%p\n", this)); + + CleanupTypes(); +} + +void +nsSocketTransport::CleanupTypes() +{ + // cleanup socket type info + if (mTypes) { + for (uint32_t i = 0; i < mTypeCount; ++i) { + PL_strfree(mTypes[i]); + } + free(mTypes); + mTypes = nullptr; + } + mTypeCount = 0; +} + +nsresult +nsSocketTransport::Init(const char **types, uint32_t typeCount, + const nsACString &host, uint16_t port, + const nsACString &hostRoute, uint16_t portRoute, + nsIProxyInfo *givenProxyInfo) +{ + nsCOMPtr<nsProxyInfo> proxyInfo; + if (givenProxyInfo) { + proxyInfo = do_QueryInterface(givenProxyInfo); + NS_ENSURE_ARG(proxyInfo); + } + + // init socket type info + + mOriginHost = host; + mOriginPort = port; + if (!hostRoute.IsEmpty()) { + mHost = hostRoute; + mPort = portRoute; + } else { + mHost = host; + mPort = port; + } + + 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_LOG(("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" + mTypeCount = typeCount + (proxyType != nullptr); + if (!mTypeCount) + return NS_OK; + + // if we have socket types, then the socket provider service had + // better exist! + nsresult rv; + nsCOMPtr<nsISocketProviderService> spserv = + do_GetService(kSocketProviderServiceCID, &rv); + if (NS_FAILED(rv)) return rv; + + mTypes = (char **) malloc(mTypeCount * sizeof(char *)); + if (!mTypes) + 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 < mTypeCount; ++i) { + // store socket types + if (i == 0 && proxyType) + mTypes[i] = PL_strdup(proxyType); + else + mTypes[i] = PL_strdup(types[type++]); + + if (!mTypes[i]) { + mTypeCount = i; + return NS_ERROR_OUT_OF_MEMORY; + } + nsCOMPtr<nsISocketProvider> provider; + rv = spserv->GetSocketProvider(mTypes[i], 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 ((strcmp(mTypes[i], "socks") == 0) || + (strcmp(mTypes[i], "socks4") == 0)) { + 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; +} + +nsresult +nsSocketTransport::InitPreResolved(const char **socketTypes, uint32_t typeCount, + const nsACString &host, uint16_t port, + const nsACString &hostRoute, uint16_t portRoute, + nsIProxyInfo *proxyInfo, + const mozilla::net::NetAddr* addr) +{ + nsresult rv = Init(socketTypes, typeCount, host, port, hostRoute, portRoute, proxyInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mNetAddr = *addr; + mNetAddrPreResolved = true; + return NS_OK; +} + +nsresult +nsSocketTransport::InitWithFilename(const char *filename) +{ +#if defined(XP_UNIX) + size_t filenameLength = strlen(filename); + + if (filenameLength > sizeof(mNetAddr.local.path) - 1) + return NS_ERROR_FILE_NAME_TOO_LONG; + + mHost.Assign(filename); + mPort = 0; + mTypeCount = 0; + + mNetAddr.local.family = AF_LOCAL; + memcpy(mNetAddr.local.path, filename, filenameLength); + mNetAddr.local.path[filenameLength] = '\0'; + mNetAddrIsSet = true; + + return NS_OK; +#else + return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; +#endif +} + +nsresult +nsSocketTransport::InitWithConnectedSocket(PRFileDesc *fd, const NetAddr *addr) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ASSERTION(!mFD.IsInitialized(), "already initialized"); + + char buf[kNetAddrMaxCStrBufSize]; + NetAddrToString(addr, 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); + mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; + mState = STATE_TRANSFERRING; + SetSocketName(fd); + mNetAddrIsSet = true; + + { + MutexAutoLock lock(mLock); + + mFD = fd; + mFDref = 1; + mFDconnected = 1; + } + + // 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, + nsISupports* aSecInfo) +{ + mSecInfo = aSecInfo; + return InitWithConnectedSocket(aFD, aAddr); +} + +nsresult +nsSocketTransport::PostEvent(uint32_t type, nsresult status, nsISupports *param) +{ + SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%x param=%p]\n", + this, type, status, param)); + + nsCOMPtr<nsIRunnable> event = new nsSocketEvent(this, type, status, param); + if (!event) + return NS_ERROR_OUT_OF_MEMORY; + + return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL); +} + +void +nsSocketTransport::SendStatus(nsresult status) +{ + SOCKET_LOG(("nsSocketTransport::SendStatus [this=%p status=%x]\n", this, status)); + + nsCOMPtr<nsITransportEventSink> sink; + uint64_t progress; + { + MutexAutoLock lock(mLock); + sink = mEventSink; + switch (status) { + case NS_NET_STATUS_SENDING_TO: + progress = mOutput.ByteCount(); + break; + case NS_NET_STATUS_RECEIVING_FROM: + progress = mInput.ByteCount(); + 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]\n", + this, SocketHost().get(), SocketPort(), + mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? + " bypass cache" : "")); + + nsresult rv; + + if (mNetAddrPreResolved) { + mState = STATE_RESOLVING; + return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr); + } + + 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.Equals(NS_LITERAL_CSTRING("*"))) { + 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); + } + } + + nsCOMPtr<nsIDNSService> dns = do_GetService(kDNSServiceCID, &rv); + if (NS_FAILED(rv)) return rv; + + mResolving = true; + + uint32_t dnsFlags = 0; + if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE) + dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE; + if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6) + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6; + if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4; + + 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->AsyncResolveExtended(SocketHost(), dnsFlags, mNetworkInterfaceId, this, + nullptr, 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; + + proxyTransparent = false; + usingSSL = false; + + if (mTypeCount == 0) { + fd = PR_OpenTCPSocket(mNetAddr.raw.family); + rv = fd ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + else { +#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; + + nsCOMPtr<nsISocketProviderService> spserv = + do_GetService(kSocketProviderServiceCID, &rv); + if (NS_FAILED(rv)) return rv; + + // 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<nsIProxyInfo> proxyInfo = mProxyInfo; + uint32_t controlFlags = 0; + + uint32_t i; + for (i=0; i<mTypeCount; ++i) { + nsCOMPtr<nsISocketProvider> provider; + + SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i])); + + rv = spserv->GetSocketProvider(mTypes[i], getter_AddRefs(provider)); + if (NS_FAILED(rv)) + break; + + 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::MITM_OK) + controlFlags |= nsISocketProvider::MITM_OK; + + if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE) + controlFlags |= nsISocketProvider::BE_CONSERVATIVE; + + nsCOMPtr<nsISupports> secinfo; + if (i == 0) { + // if this is the first type, we'll want the + // service to allocate a new socket + + // 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() : host, + mHttpsProxy ? mProxyPort : port, + proxyInfo, mOriginAttributes, + controlFlags, &fd, + getter_AddRefs(secinfo)); + + if (NS_SUCCEEDED(rv) && !fd) { + NS_NOTREACHED("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, fd, + getter_AddRefs(secinfo)); + } + + // 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 = (strcmp(mTypes[i], "ssl") == 0); + if (isSSL || (strcmp(mTypes[i], "starttls") == 0)) { + // remember security info and give notification callbacks to PSM... + nsCOMPtr<nsIInterfaceRequestor> callbacks; + { + MutexAutoLock lock(mLock); + mSecInfo = secinfo; + callbacks = mCallbacks; + SOCKET_LOG((" [secinfo=%x callbacks=%x]\n", mSecInfo.get(), mCallbacks.get())); + } + // don't call into PSM while holding mLock!! + nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo)); + if (secCtrl) + secCtrl->SetNotificationCallbacks(callbacks); + // remember if socket type is SSL so we can ProxyStartSSL if need be. + usingSSL = isSSL; + } + else if ((strcmp(mTypes[i], "socks") == 0) || + (strcmp(mTypes[i], "socks4") == 0)) { + // 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=%x]\n", i, mTypes[i], rv)); + if (fd) { + CloseSocket(fd, + mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()); + } + } + } + + return rv; +} + +nsresult +nsSocketTransport::InitiateSocket() +{ + SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this)); + + nsresult rv; + bool isLocal; + IsLocal(&isLocal); + + if (gIOService->IsNetTearingDown()) { + return NS_ERROR_ABORT; + } + if (gIOService->IsOffline()) { + if (!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() && + !(IsIPAddrAny(&mNetAddr) || IsIPAddrLocal(&mNetAddr))) { + 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()); + MOZ_CRASH("Attempting to connect to non-local address!"); + } + } + + // Hosts/Proxy Hosts that are Local IP Literals should not be speculatively + // connected - Bug 853423. + if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 && + IsIPAddrLocal(&mNetAddr)) { + if (SOCKET_LOG_ENABLED()) { + nsAutoCString netAddrCString; + netAddrCString.SetCapacity(kIPv6CStrBufSize); + if (!NetAddrToString(&mNetAddr, + netAddrCString.BeginWriting(), + kIPv6CStrBufSize)) + netAddrCString = NS_LITERAL_CSTRING("<IP-to-string failed>"); + 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. + // + 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=%x]\n", rv)); + return rv; + } + + // Attach network activity monitor + NetworkActivityMonitor::AttachIOLayer(fd); + + 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"); + + // 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 + + // 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; + + // assign mFD so that we can properly handle OnSocketDetached before we've + // established a connection. + { + MutexAutoLock lock(mLock); + mFD = fd; + mFDref = 1; + mFDconnected = false; + } + + SOCKET_LOG((" advancing to STATE_CONNECTING\n")); + mState = STATE_CONNECTING; + mPollTimeout = mTimeouts[TIMEOUT_CONNECT]; + SendStatus(NS_NET_STATUS_CONNECTING_TO); + + if (SOCKET_LOG_ENABLED()) { + char buf[kNetAddrMaxCStrBufSize]; + NetAddrToString(&mNetAddr, buf, sizeof(buf)); + SOCKET_LOG((" trying address: %s\n", buf)); + } + + // + // Initiate the connect() to the host... + // + PRNetAddr 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 + + // 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(); + } + + status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT); + + if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() && + connectStarted) { + 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 { + PRErrorCode code = PR_GetError(); +#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 (mSecInfo && !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. + nsCOMPtr<nsISSLSocketControl> secCtrl = + do_QueryInterface(mSecInfo); + if (secCtrl) { + SOCKET_LOG((" calling ProxyStartSSL()\n")); + secCtrl->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) { + 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=%x]\n", + this, mState, 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 + + // can only recover from errors in these states + if (mState != STATE_RESOLVING && mState != STATE_CONNECTING) + return false; + + nsresult rv; + + // OK to check this outside mLock + NS_ASSERTION(!mFDconnected, "socket should not be connected"); + + // all connection failures need to be reported to DNS so that the next + // time we will use a different address if available. + if (mState == STATE_CONNECTING && mDNSRecord) { + mDNSRecord->ReportUnusable(SocketPort()); + } + + // can only recover from these errors + 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) + return false; + + bool tryAgain = false; + + if ((mState == STATE_CONNECTING) && mDNSRecord && + mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + if (mNetAddr.raw.family == AF_INET) { + Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY, + UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS); + } else if (mNetAddr.raw.family == AF_INET6) { + Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY, + UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS); + } + } + + if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4) && + mCondition == NS_ERROR_UNKNOWN_HOST && + mState == STATE_RESOLVING && + !mProxyTransparentResolvesHost) { + SOCKET_LOG((" trying lookup again with both ipv4/ipv6 enabled\n")); + mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4); + tryAgain = true; + } + + // try next ip address only if past the resolver stage... + if (mState == STATE_CONNECTING && mDNSRecord) { + nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr); + if (NS_SUCCEEDED(rv)) { + SOCKET_LOG((" trying again with next ip address\n")); + tryAgain = true; + } + else if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4)) { + // Drop state to closed. This will trigger new round of DNS + // resolving bellow. + // XXX Could be optimized to only switch the flags to save duplicate + // connection attempts. + SOCKET_LOG((" failed to connect all ipv4-only or ipv6-only hosts," + " trying lookup/connect again with both ipv4/ipv6\n")); + mState = STATE_CLOSED; + mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4); + 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=%x]\n", + this, reason)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong 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=%x]\n", + this, reason)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong 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(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + SOCKET_LOG((" advancing to STATE_TRANSFERRING\n")); + + mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT); + mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; + 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; + +#ifdef XP_WIN + if (!IsWin2003OrLater()) { // windows xp + PRSocketOptionData opt; + opt.option = PR_SockOpt_RecvBufferSize; + if (PR_GetSocketOption(mFD, &opt) == PR_SUCCESS) { + SOCKET_LOG(("%p checking rwin on xp originally=%u\n", + this, opt.value.recv_buffer_size)); + if (opt.value.recv_buffer_size < 65535) { + opt.value.recv_buffer_size = 65535; + PR_SetSocketOption(mFD, &opt); + } + } + } +#endif + } + + // 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%x]", rv)); + } + } + + SendStatus(NS_NET_STATUS_CONNECTED_TO); +} + +void +nsSocketTransport::SetSocketName(PRFileDesc *fd) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong 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) : mFD(fd) {} + + NS_IMETHOD Run() override + { + nsSocketTransport::CloseSocket(mFD, + gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()); + return NS_OK; + } +private: + PRFileDesc *mFD; +}; + +void +STS_PRCloseOnSocketTransport(PRFileDesc *fd) +{ + if (gSocketTransportService) { + // Can't PR_Close() a socket off STS thread. Thunk it to STS to die + // FIX - Should use RUN_ON_THREAD once it's generally available + // RUN_ON_THREAD(gSocketThread,WrapRunnableNM(&PR_Close, mFD); + 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"); + SOCKET_LOG(("JIMB: ReleaseFD_Locked: mFDref = %d\n", mFDref)); + + 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 (PR_GetCurrentThread() == gSocketThread) { + 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); + } + mFD = nullptr; + } +} + +//----------------------------------------------------------------------------- +// socket event handler impl + +void +nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status, nsISupports *param) +{ + SOCKET_LOG(("nsSocketTransport::OnSocketEvent [this=%p type=%u status=%x param=%p]\n", + this, type, status, param)); + + if (NS_FAILED(mCondition)) { + // block event since we're apparently already dead. + SOCKET_LOG((" blocking event [condition=%x]\n", 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")); + // + // 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 +#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 (param) { + mDNSRecord = static_cast<nsIDNSRecord *>(param); + mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr); + } + // 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")); + 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=%x]\n", this, mCondition)); + if (!mAttached) // need to process this error ourselves... + OnSocketDetached(nullptr); + } + else if (mPollFlags == PR_POLL_EXCEPT) + mPollFlags = 0; // make idle +} + +//----------------------------------------------------------------------------- +// socket handler impl + +void +nsSocketTransport::OnSocketReady(PRFileDesc *fd, int16_t outFlags) +{ + SOCKET_LOG(("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 + 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 (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + if (mNetAddr.raw.family == AF_INET) { + Telemetry::Accumulate( + Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY, + SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS); + } else if (mNetAddr.raw.family == AF_INET6) { + 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 + 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=%x]\n", 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=%x]\n", + this, mCondition)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // 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); + } + + // break any potential reference cycle between the security info object + // and ourselves by resetting its notification callbacks object. see + // bug 285991 for details. + nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo); + if (secCtrl) + secCtrl->SetNotificationCallbacks(nullptr); + + // 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 = IsLoopBackAddress(&mNetAddr); + } +} + +//----------------------------------------------------------------------------- +// 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 **result) +{ + SOCKET_LOG(("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n", + this, flags)); + + NS_ENSURE_TRUE(!mInput.IsReferenced(), NS_ERROR_UNEXPECTED); + + nsresult rv; + nsCOMPtr<nsIAsyncInputStream> pipeIn; + + 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; + rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), + !openBlocking, true, segsize, segcount); + if (NS_FAILED(rv)) return rv; + + // async copy from socket to pipe + rv = NS_AsyncCopy(&mInput, pipeOut, mSocketTransportService, + NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize); + if (NS_FAILED(rv)) return rv; + + *result = pipeIn; + } + else + *result = &mInput; + + // flag input stream as open + mInputClosed = false; + + rv = PostEvent(MSG_ENSURE_CONNECT); + if (NS_FAILED(rv)) return rv; + + NS_ADDREF(*result); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::OpenOutputStream(uint32_t flags, + uint32_t segsize, + uint32_t segcount, + nsIOutputStream **result) +{ + SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n", + this, flags)); + + NS_ENSURE_TRUE(!mOutput.IsReferenced(), NS_ERROR_UNEXPECTED); + + nsresult rv; + nsCOMPtr<nsIAsyncOutputStream> pipeOut; + 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; + rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), + true, !openBlocking, segsize, segcount); + if (NS_FAILED(rv)) return rv; + + // async copy from socket to pipe + rv = NS_AsyncCopy(pipeIn, &mOutput, mSocketTransportService, + NS_ASYNCCOPY_VIA_READSEGMENTS, segsize); + if (NS_FAILED(rv)) return rv; + + *result = pipeOut; + } + else + *result = &mOutput; + + // flag output stream as open + mOutputClosed = false; + + rv = PostEvent(MSG_ENSURE_CONNECT); + if (NS_FAILED(rv)) return rv; + + NS_ADDREF(*result); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::Close(nsresult reason) +{ + if (NS_SUCCEEDED(reason)) + reason = NS_BASE_STREAM_CLOSED; + + mDoNotRetryToConnect = true; + + mInput.CloseWithStatus(reason); + mOutput.CloseWithStatus(reason); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetSecurityInfo(nsISupports **secinfo) +{ + MutexAutoLock lock(mLock); + NS_IF_ADDREF(*secinfo = mSecInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor **callbacks) +{ + MutexAutoLock lock(mLock); + NS_IF_ADDREF(*callbacks = mCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor *callbacks) +{ + nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks; + NS_NewNotificationCallbacksAggregation(callbacks, nullptr, + NS_GetCurrentThread(), + getter_AddRefs(threadsafeCallbacks)); + + nsCOMPtr<nsISupports> secinfo; + { + MutexAutoLock lock(mLock); + mCallbacks = threadsafeCallbacks; + SOCKET_LOG(("Reset callbacks for secinfo=%p callbacks=%p\n", + mSecInfo.get(), mCallbacks.get())); + + secinfo = mSecInfo; + } + + // don't call into PSM while holding mLock!! + nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo)); + if (secCtrl) + secCtrl->SetNotificationCallbacks(threadsafeCallbacks); + + 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::GetNetworkInterfaceId(nsACString_internal &aNetworkInterfaceId) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + aNetworkInterfaceId = mNetworkInterfaceId; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetNetworkInterfaceId(const nsACString_internal &aNetworkInterfaceId) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + mNetworkInterfaceId = aNetworkInterfaceId; + 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); + + NeckoOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + mOriginAttributes = attrs; + return NS_OK; +} + +nsresult +nsSocketTransport::GetOriginAttributes(NeckoOriginAttributes* aOriginAttributes) +{ + NS_ENSURE_ARG(aOriginAttributes); + *aOriginAttributes = mOriginAttributes; + return NS_OK; +} + +nsresult +nsSocketTransport::SetOriginAttributes(const NeckoOriginAttributes& 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); + if (mAttached) { + return NS_ERROR_FAILURE; + } + + mBindAddr = new 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; + + NS_ADDREF(*addr = new nsNetAddr(&rawAddr)); + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetScriptableSelfAddr(nsINetAddr * *addr) +{ + NetAddr rawAddr; + + nsresult rv; + rv = GetSelfAddr(&rawAddr); + if (NS_FAILED(rv)) + return rv; + + NS_ADDREF(*addr = new nsNetAddr(&rawAddr)); + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetTimeout(uint32_t type, uint32_t *value) +{ + NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE); + *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); + // truncate overly large timeout values. + mTimeouts[type] = (uint16_t) std::min<uint32_t>(value, UINT16_MAX); + PostEvent(MSG_TIMEOUT_CHANGED); + 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) +{ + // flag host lookup complete for the benefit of the ResolveHost method. + mResolving = false; + + nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, rec); + + // 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))) { + return mDNSRecord ? + mDNSRecord->QueryInterface(iid, result) : NS_ERROR_NO_INTERFACE; + } + return this->QueryInterface(iid, result); +} + +NS_IMETHODIMP +nsSocketTransport::GetInterfaces(uint32_t *count, nsIID * **array) +{ + return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(count, array); +} + +NS_IMETHODIMP +nsSocketTransport::GetScriptableHelper(nsIXPCScriptable **_retval) +{ + *_retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetClassDescription(char * *aClassDescription) +{ + *aClassDescription = nullptr; + 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) +{ + mConnectionFlags = value; + mIsPrivate = value & nsISocketTransport::NO_PERMANENT_STORAGE; + return NS_OK; +} + +void +nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong 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%x]", + aEnabled ? "enable" : "disable", 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%x]", rv)); + return rv; + } + rv = fd.SetKeepaliveEnabled(enable); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabled failed rv[0x%x]", 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(PR_GetCurrentThread() == gSocketThread, "wrong 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%x] initializing keepalive vals", + this, 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%x]", 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(PR_GetCurrentThread() == gSocketThread, "wrong 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(PR_GetCurrentThread() == gSocketThread, "wrong 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(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + +#ifdef XP_WIN + DWORD errCode = WSAGetLastError(); + LPVOID errMessage; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &errMessage, + 0, NULL); +#else + int errCode = errno; + char *errMessage = strerror(errno); +#endif + NS_WARNING(nsPrintfCString( + "%s [%p] OS error[0x%x] %s", + aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode, + errMessage ? errMessage : "<no error text>").get()); +#ifdef XP_WIN + LocalFree(errMessage); +#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(PR_GetCurrentThread() == gSocketThread, "wrong 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::ID aIDNormal, + Telemetry::ID aIDShutdown, + Telemetry::ID aIDConnectivityChange, + Telemetry::ID aIDLinkChange, + Telemetry::ID 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)); + } +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsSocketTransport2.h b/netwerk/base/nsSocketTransport2.h new file mode 100644 index 000000000..7c85ccdc4 --- /dev/null +++ b/netwerk/base/nsSocketTransport2.h @@ -0,0 +1,471 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "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 "nsIClassInfo.h" +#include "mozilla/net/DNS.h" +#include "nsASocketHandler.h" +#include "mozilla/Telemetry.h" + +#include "prerror.h" +#include "nsAutoPtr.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 nsSocketTransport; + +class nsSocketInputStream : public nsIAsyncInputStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + explicit nsSocketInputStream(nsSocketTransport *); + virtual ~nsSocketInputStream(); + + bool IsReferenced() { return mReaderRefCnt > 0; } + nsresult Condition() { return mCondition; } + uint64_t ByteCount() { return mByteCount; } + + // called by the socket transport on the socket thread... + void OnSocketReady(nsresult condition); + +private: + nsSocketTransport *mTransport; + ThreadSafeAutoRefCnt mReaderRefCnt; + + // access to these is protected by mTransport->mLock + nsresult mCondition; + nsCOMPtr<nsIInputStreamCallback> mCallback; + uint32_t mCallbackFlags; + uint64_t mByteCount; +}; + +//----------------------------------------------------------------------------- + +class nsSocketOutputStream : public nsIAsyncOutputStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + + explicit nsSocketOutputStream(nsSocketTransport *); + virtual ~nsSocketOutputStream(); + + bool IsReferenced() { return mWriterRefCnt > 0; } + nsresult Condition() { return mCondition; } + uint64_t ByteCount() { 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; + + // access to these is protected by mTransport->mLock + nsresult mCondition; + nsCOMPtr<nsIOutputStreamCallback> mCallback; + uint32_t mCallbackFlags; + uint64_t mByteCount; +}; + +//----------------------------------------------------------------------------- + +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 char **socketTypes, uint32_t typeCount, + const nsACString &host, uint16_t port, + const nsACString &hostRoute, uint16_t portRoute, + nsIProxyInfo *proxyInfo); + + // Alternative Init method for when the IP-address of the host + // has been pre-resolved using a alternative means (e.g. FlyWeb service + // info). + nsresult InitPreResolved(const char **socketTypes, uint32_t typeCount, + const nsACString &host, uint16_t port, + const nsACString &hostRoute, uint16_t portRoute, + nsIProxyInfo *proxyInfo, + const mozilla::net::NetAddr* addr); + + // 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 security info. + nsresult InitWithConnectedSocket(PRFileDesc* aSocketFD, + const NetAddr* aAddr, + nsISupports* aSecInfo); + + // 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); + + // nsASocketHandler methods: + void OnSocketReady(PRFileDesc *, int16_t outFlags) override; + void OnSocketDetached(PRFileDesc *) override; + void IsLocal(bool *aIsLocal) override; + void OnKeepaliveEnabledPrefChange(bool aEnabled) override final; + + // called when a socket event is handled + void OnSocketEvent(uint32_t type, nsresult status, nsISupports *param); + + uint64_t ByteCountReceived() override { return mInput.ByteCount(); } + uint64_t ByteCountSent() override { return mOutput.ByteCount(); } + static void CloseSocket(PRFileDesc *aFd, bool aTelemetryEnabled); + static void SendPRBlockingTelemetry(PRIntervalTime aStart, + Telemetry::ID aIDNormal, + Telemetry::ID aIDShutdown, + Telemetry::ID aIDConnectivityChange, + Telemetry::ID aIDLinkChange, + Telemetry::ID aIDOffline); +protected: + + virtual ~nsSocketTransport(); + void CleanupTypes(); + +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); + + 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() {} + 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: + char **mTypes; + uint32_t mTypeCount; + nsCString mHost; + nsCString mProxyHost; + nsCString mOriginHost; + uint16_t mPort; + nsCOMPtr<nsIProxyInfo> mProxyInfo; + uint16_t mProxyPort; + uint16_t mOriginPort; + bool mProxyTransparent; + bool mProxyTransparentResolvesHost; + bool mHttpsProxy; + uint32_t mConnectionFlags; + + // 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. + NeckoOriginAttributes mOriginAttributes; + + uint16_t SocketPort() { return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyPort : mPort; } + const nsCString &SocketHost() { return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyHost : mHost; } + + //------------------------------------------------------------------------- + // members accessible only on the socket transport thread: + // (the exception being initialization/shutdown time) + //------------------------------------------------------------------------- + + // socket state vars: + uint32_t mState; // STATE_??? flags + bool mAttached; + bool mInputClosed; + bool mOutputClosed; + + // The platform-specific network interface id that this socket + // associated with. + nsCString mNetworkInterfaceId; + + // 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; + + nsCOMPtr<nsICancelable> mDNSRequest; + nsCOMPtr<nsIDNSRecord> mDNSRecord; + + // 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; + Atomic<bool, Relaxed> mSelfAddrIsSet; + Atomic<bool, Relaxed> mNetAddrPreResolved; + + nsAutoPtr<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() + { + if (mState == STATE_TRANSFERRING) + mPollFlags |= (PR_POLL_READ | PR_POLL_EXCEPT); + } + void OnMsgOutputPending() + { + 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). + + Mutex mLock; // protects members in this section. + LockedPRFileDesc mFD; + nsrefcnt mFDref; // mFD is closed when mFDref goes to zero. + bool mFDconnected; // mFD is available to consumer when TRUE. + + // 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<nsISupports> mSecInfo; + + nsSocketInputStream mInput; + nsSocketOutputStream mOutput; + + friend class nsSocketInputStream; + friend class nsSocketOutputStream; + + // socket timeouts are not protected by any lock. + uint16_t mTimeouts[2]; + + // QoS setting for socket + uint8_t mQoSBits; + + // + // mFD access methods: called with mLock held. + // + PRFileDesc *GetFD_Locked(); + void ReleaseFD_Locked(PRFileDesc *fd); + + // + // stream state changes (called outside mLock): + // + void OnInputClosed(nsresult reason) + { + // no need to post an event if called on the socket thread + if (PR_GetCurrentThread() == gSocketThread) + OnMsgInputClosed(reason); + else + PostEvent(MSG_INPUT_CLOSED, reason); + } + void OnInputPending() + { + // no need to post an event if called on the socket thread + if (PR_GetCurrentThread() == gSocketThread) + OnMsgInputPending(); + else + PostEvent(MSG_INPUT_PENDING); + } + void OnOutputClosed(nsresult reason) + { + // no need to post an event if called on the socket thread + if (PR_GetCurrentThread() == gSocketThread) + 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 (PR_GetCurrentThread() == gSocketThread) + 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; + + // Keepalive config (support varies by platform). + int32_t mKeepaliveIdleTimeS; + int32_t mKeepaliveRetryIntervalS; + int32_t mKeepaliveProbeCount; + + bool mDoNotRetryToConnect; +}; + +} // namespace net +} // namespace mozilla + +#endif // !nsSocketTransport_h__ diff --git a/netwerk/base/nsSocketTransportService2.cpp b/netwerk/base/nsSocketTransportService2.cpp new file mode 100644 index 000000000..d2f20651e --- /dev/null +++ b/netwerk/base/nsSocketTransportService2.cpp @@ -0,0 +1,1627 @@ +// vim:set sw=4 sts=4 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 "nsSocketTransport2.h" +#include "NetworkActivityMonitor.h" +#include "mozilla/Preferences.h" +#include "nsIOService.h" +#include "nsASocketHandler.h" +#include "nsError.h" +#include "prnetdb.h" +#include "prerror.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsServiceManagerUtils.h" +#include "nsIObserverService.h" +#include "mozilla/Atomics.h" +#include "mozilla/Services.h" +#include "mozilla/Likely.h" +#include "mozilla/PublicSSL.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Telemetry.h" +#include "nsThreadUtils.h" +#include "nsIFile.h" +#include "nsIWidget.h" +#include "mozilla/dom/FlyWebService.h" + +#if defined(XP_WIN) +#include "mozilla/WindowsVersion.h" +#endif + +namespace mozilla { +namespace net { + +LazyLogModule gSocketTransportLog("nsSocketTransport"); +LazyLogModule gUDPSocketLog("UDPSocket"); +LazyLogModule gTCPSocketLog("TCPSocket"); + +nsSocketTransportService *gSocketTransportService = nullptr; +Atomic<PRThread*, Relaxed> gSocketThread; + +#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 SOCKET_LIMIT_MIN 50U +#define BLIP_INTERVAL_PREF "network.activity.blipIntervalMilliseconds" +#define MAX_TIME_BETWEEN_TWO_POLLS "network.sts.max_time_for_events_between_two_polls" +#define TELEMETRY_PREF "toolkit.telemetry.enabled" +#define MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN "network.sts.max_time_for_pr_close_during_shutdown" + +#define REPAIR_POLLABLE_EVENT_TIME 10 + +uint32_t nsSocketTransportService::gMaxCount; +PRCallOnceType nsSocketTransportService::gMaxCountInitOnce; + +//----------------------------------------------------------------------------- +// ctor/dtor (called on the main/UI thread by the service manager) + +nsSocketTransportService::nsSocketTransportService() + : mThread(nullptr) + , mLock("nsSocketTransportService::mLock") + , mInitialized(false) + , mShuttingDown(false) + , mOffline(false) + , mGoingOffline(false) + , mRawThread(nullptr) + , mActiveListSize(SOCKET_LIMIT_MIN) + , mIdleListSize(SOCKET_LIMIT_MIN) + , mActiveCount(0) + , mIdleCount(0) + , mSentBytesCount(0) + , mReceivedBytesCount(0) + , mSendBufferSize(0) + , mKeepaliveIdleTimeS(600) + , mKeepaliveRetryIntervalS(1) + , mKeepaliveProbeCount(kDefaultTCPKeepCount) + , mKeepaliveEnabledPref(false) + , mServingPendingQueue(false) + , mMaxTimePerPollIter(100) + , mTelemetryEnabledPref(false) + , mMaxTimeForPrClosePref(PR_SecondsToInterval(5)) + , mSleepPhase(false) + , mProbedMaxCount(false) +#if defined(XP_WIN) + , mPolling(false) +#endif +{ + NS_ASSERTION(NS_IsMainThread(), "wrong thread"); + + PR_CallOnce(&gMaxCountInitOnce, DiscoverMaxCount); + mActiveList = (SocketContext *) + moz_xmalloc(sizeof(SocketContext) * mActiveListSize); + mIdleList = (SocketContext *) + moz_xmalloc(sizeof(SocketContext) * mIdleListSize); + mPollList = (PRPollDesc *) + moz_xmalloc(sizeof(PRPollDesc) * (mActiveListSize + 1)); + + NS_ASSERTION(!gSocketTransportService, "must not instantiate twice"); + gSocketTransportService = this; +} + +nsSocketTransportService::~nsSocketTransportService() +{ + NS_ASSERTION(NS_IsMainThread(), "wrong thread"); + NS_ASSERTION(!mInitialized, "not shutdown properly"); + + free(mActiveList); + free(mIdleList); + free(mPollList); + 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::IsOnCurrentThread(bool *result) +{ + nsCOMPtr<nsIThread> thread = GetThreadSafely(); + NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED); + return thread->IsOnCurrentThread(result); +} + +//----------------------------------------------------------------------------- +// socket api (socket thread only) + +NS_IMETHODIMP +nsSocketTransportService::NotifyWhenCanAttachSocket(nsIRunnable *event) +{ + SOCKET_LOG(("nsSocketTransportService::NotifyWhenCanAttachSocket\n")); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong 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)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + if (!CanAttachSocket()) { + return NS_ERROR_NOT_AVAILABLE; + } + + SocketContext sock; + sock.mFD = fd; + sock.mHandler = handler; + sock.mElapsedTime = 0; + + nsresult rv = AddToIdleList(&sock); + if (NS_SUCCEEDED(rv)) + NS_ADDREF(handler); + return rv; +} + +// 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() +{ + static bool reported900FDLimit = false; + + uint32_t total = mActiveCount + mIdleCount; + bool rv = total < gMaxCount; + + if (mTelemetryEnabledPref && + (((total >= 900) || !rv) && !reported900FDLimit)) { + reported900FDLimit = true; + Telemetry::Accumulate(Telemetry::NETWORK_SESSION_AT_900FD, true); + } + + return rv; +} + +nsresult +nsSocketTransportService::DetachSocket(SocketContext *listHead, SocketContext *sock) +{ + SOCKET_LOG(("nsSocketTransportService::DetachSocket [handler=%p]\n", sock->mHandler)); + 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; + NS_RELEASE(sock->mHandler); + + 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; +} + +nsresult +nsSocketTransportService::AddToPollList(SocketContext *sock) +{ + MOZ_ASSERT(!(static_cast<uint32_t>(sock - mActiveList) < mActiveListSize), + "AddToPollList Socket Already Active"); + + SOCKET_LOG(("nsSocketTransportService::AddToPollList [handler=%p]\n", sock->mHandler)); + if (mActiveCount == mActiveListSize) { + SOCKET_LOG((" Active List size of %d met\n", mActiveCount)); + if (!GrowActiveList()) { + NS_ERROR("too many active sockets"); + return NS_ERROR_OUT_OF_MEMORY; + } + } + + uint32_t newSocketIndex = mActiveCount; + if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) { + newSocketIndex = ChaosMode::randomUint32LessThan(mActiveCount + 1); + PodMove(mActiveList + newSocketIndex + 1, mActiveList + newSocketIndex, + mActiveCount - newSocketIndex); + PodMove(mPollList + newSocketIndex + 2, mPollList + newSocketIndex + 1, + mActiveCount - newSocketIndex); + } + mActiveList[newSocketIndex] = *sock; + mActiveCount++; + + mPollList[newSocketIndex + 1].fd = sock->mFD; + mPollList[newSocketIndex + 1].in_flags = sock->mHandler->mPollFlags; + mPollList[newSocketIndex + 1].out_flags = 0; + + SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount)); + return NS_OK; +} + +void +nsSocketTransportService::RemoveFromPollList(SocketContext *sock) +{ + SOCKET_LOG(("nsSocketTransportService::RemoveFromPollList [handler=%p]\n", sock->mHandler)); + + uint32_t index = sock - mActiveList; + MOZ_ASSERT(index < mActiveListSize, "invalid index"); + + SOCKET_LOG((" index=%u mActiveCount=%u\n", index, mActiveCount)); + + if (index != mActiveCount-1) { + mActiveList[index] = mActiveList[mActiveCount-1]; + mPollList[index+1] = mPollList[mActiveCount]; + } + mActiveCount--; + + SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount)); +} + +nsresult +nsSocketTransportService::AddToIdleList(SocketContext *sock) +{ + MOZ_ASSERT(!(static_cast<uint32_t>(sock - mIdleList) < mIdleListSize), + "AddToIdlelList Socket Already Idle"); + + SOCKET_LOG(("nsSocketTransportService::AddToIdleList [handler=%p]\n", sock->mHandler)); + if (mIdleCount == mIdleListSize) { + SOCKET_LOG((" Idle List size of %d met\n", mIdleCount)); + if (!GrowIdleList()) { + NS_ERROR("too many idle sockets"); + return NS_ERROR_OUT_OF_MEMORY; + } + } + + mIdleList[mIdleCount] = *sock; + mIdleCount++; + + SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount)); + return NS_OK; +} + +void +nsSocketTransportService::RemoveFromIdleList(SocketContext *sock) +{ + SOCKET_LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%p]\n", sock->mHandler)); + + uint32_t index = sock - mIdleList; + NS_ASSERTION(index < mIdleListSize, "invalid index in idle list"); + + if (index != mIdleCount-1) + mIdleList[index] = mIdleList[mIdleCount-1]; + mIdleCount--; + + SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount)); +} + +void +nsSocketTransportService::MoveToIdleList(SocketContext *sock) +{ + nsresult rv = AddToIdleList(sock); + if (NS_FAILED(rv)) + DetachSocket(mActiveList, sock); + else + RemoveFromPollList(sock); +} + +void +nsSocketTransportService::MoveToPollList(SocketContext *sock) +{ + nsresult rv = AddToPollList(sock); + if (NS_FAILED(rv)) + DetachSocket(mIdleList, sock); + else + RemoveFromIdleList(sock); +} + +bool +nsSocketTransportService::GrowActiveList() +{ + int32_t toAdd = gMaxCount - mActiveListSize; + if (toAdd > 100) { + toAdd = 100; + } else if (toAdd < 1) { + MOZ_ASSERT(false, "CanAttachSocket() should prevent this"); + return false; + } + + mActiveListSize += toAdd; + mActiveList = (SocketContext *) + moz_xrealloc(mActiveList, sizeof(SocketContext) * mActiveListSize); + mPollList = (PRPollDesc *) + moz_xrealloc(mPollList, sizeof(PRPollDesc) * (mActiveListSize + 1)); + return true; +} + +bool +nsSocketTransportService::GrowIdleList() +{ + int32_t toAdd = gMaxCount - mIdleListSize; + if (toAdd > 100) { + toAdd = 100; + } else if (toAdd < 1) { + MOZ_ASSERT(false, "CanAttachSocket() should prevent this"); + return false; + } + + mIdleListSize += toAdd; + mIdleList = (SocketContext *) + moz_xrealloc(mIdleList, sizeof(SocketContext) * mIdleListSize); + return true; +} + +PRIntervalTime +nsSocketTransportService::PollTimeout() +{ + if (mActiveCount == 0) + return NS_SOCKET_POLL_TIMEOUT; + + // compute minimum time before any socket timeout expires. + uint32_t minR = UINT16_MAX; + for (uint32_t i=0; i<mActiveCount; ++i) { + const SocketContext &s = mActiveList[i]; + // mPollTimeout could be less than mElapsedTime if setTimeout + // was called with a value smaller than mElapsedTime. + uint32_t r = (s.mElapsedTime < s.mHandler->mPollTimeout) + ? s.mHandler->mPollTimeout - s.mElapsedTime + : 0; + if (r < minR) + minR = r; + } + // nsASocketHandler defines UINT16_MAX as do not timeout + if (minR == UINT16_MAX) { + SOCKET_LOG(("poll timeout: none\n")); + return NS_SOCKET_POLL_TIMEOUT; + } + SOCKET_LOG(("poll timeout: %lu\n", minR)); + return PR_SecondsToInterval(minR); +} + +int32_t +nsSocketTransportService::Poll(uint32_t *interval, + TimeDuration *pollDuration) +{ + PRPollDesc *pollList; + uint32_t pollCount; + PRIntervalTime pollTimeout; + *pollDuration = 0; + + // 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; + pollList = mPollList; + pollCount = mActiveCount + 1; + pollTimeout = pendingEvents ? PR_INTERVAL_NO_WAIT : PollTimeout(); + } + else { + // no pollable event, so busy wait... + pollCount = mActiveCount; + if (pollCount) + pollList = &mPollList[1]; + else + pollList = nullptr; + pollTimeout = + pendingEvents ? PR_INTERVAL_NO_WAIT : PR_MillisecondsToInterval(25); + } + + PRIntervalTime ts = PR_IntervalNow(); + + TimeStamp pollStart; + if (mTelemetryEnabledPref) { + pollStart = TimeStamp::NowLoRes(); + } + + SOCKET_LOG((" timeout = %i milliseconds\n", + PR_IntervalToMilliseconds(pollTimeout))); + int32_t rv = PR_Poll(pollList, pollCount, pollTimeout); + + PRIntervalTime passedInterval = PR_IntervalNow() - ts; + + if (mTelemetryEnabledPref && !pollStart.IsNull()) { + *pollDuration = TimeStamp::NowLoRes() - pollStart; + } + + SOCKET_LOG((" ...returned after %i milliseconds\n", + PR_IntervalToMilliseconds(passedInterval))); + + *interval = PR_IntervalToSeconds(passedInterval); + return rv; +} + +//----------------------------------------------------------------------------- +// xpcom api + +NS_IMPL_ISUPPORTS(nsSocketTransportService, + nsISocketTransportService, + nsIRoutedSocketTransportService, + nsIEventTarget, + nsIThreadObserver, + nsIRunnable, + nsPISocketTransportService, + nsIObserver) + +// 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; + nsresult rv = NS_NewNamedThread("Socket Thread", getter_AddRefs(thread), this); + if (NS_FAILED(rv)) return rv; + + { + MutexAutoLock lock(mLock); + // Install our mThread, protecting against concurrent readers + thread.swap(mThread); + } + + nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (tmpPrefService) { + tmpPrefService->AddObserver(SEND_BUFFER_PREF, this, false); + tmpPrefService->AddObserver(KEEPALIVE_ENABLED_PREF, this, false); + tmpPrefService->AddObserver(KEEPALIVE_IDLE_TIME_PREF, this, false); + tmpPrefService->AddObserver(KEEPALIVE_RETRY_INTERVAL_PREF, this, false); + tmpPrefService->AddObserver(KEEPALIVE_PROBE_COUNT_PREF, this, false); + tmpPrefService->AddObserver(MAX_TIME_BETWEEN_TWO_POLLS, this, false); + tmpPrefService->AddObserver(TELEMETRY_PREF, this, false); + tmpPrefService->AddObserver(MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN, this, false); + } + UpdatePrefs(); + + nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); + 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); + } + + 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) + return NS_OK; + + if (mShuttingDown) + return NS_ERROR_UNEXPECTED; + + { + MutexAutoLock lock(mLock); + + // signal the socket thread to shutdown + mShuttingDown = true; + + if (mPollableEvent) { + mPollableEvent->Signal(); + } + } + + if (!aXpcomShutdown) { + return ShutdownThread(); + } + + return NS_OK; +} + +nsresult +nsSocketTransportService::ShutdownThread() +{ + SOCKET_LOG(("nsSocketTransportService::ShutdownThread\n")); + + NS_ENSURE_STATE(NS_IsMainThread()); + + if (!mInitialized || !mShuttingDown) + return NS_OK; + + // join with thread + mThread->Shutdown(); + { + MutexAutoLock lock(mLock); + // Drop our reference to mThread and make sure that any concurrent + // readers are excluded + mThread = nullptr; + } + + nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (tmpPrefService) + tmpPrefService->RemoveObserver(SEND_BUFFER_PREF, 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"); + } + + if (mAfterWakeUpTimer) { + mAfterWakeUpTimer->Cancel(); + mAfterWakeUpTimer = nullptr; + } + + NetworkActivityMonitor::Shutdown(); + + mInitialized = false; + mShuttingDown = false; + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::GetOffline(bool *offline) +{ + *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 char **types, + uint32_t typeCount, + const nsACString &host, + int32_t port, + nsIProxyInfo *proxyInfo, + nsISocketTransport **result) +{ + return CreateRoutedTransport(types, typeCount, host, port, NS_LITERAL_CSTRING(""), 0, + proxyInfo, result); +} + +NS_IMETHODIMP +nsSocketTransportService::CreateRoutedTransport(const char **types, + uint32_t typeCount, + const nsACString &host, + int32_t port, + const nsACString &hostRoute, + int32_t portRoute, + nsIProxyInfo *proxyInfo, + nsISocketTransport **result) +{ + // Check FlyWeb table for host mappings. If one exists, then use that. + RefPtr<mozilla::dom::FlyWebService> fws = + mozilla::dom::FlyWebService::GetExisting(); + if (fws) { + nsresult rv = fws->CreateTransportForHost(types, typeCount, host, port, + hostRoute, portRoute, + proxyInfo, result); + NS_ENSURE_SUCCESS(rv, rv); + + if (*result) { + return NS_OK; + } + } + + 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, typeCount, host, port, hostRoute, portRoute, proxyInfo); + if (NS_FAILED(rv)) { + return rv; + } + + trans.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::CreateUnixDomainTransport(nsIFile *aPath, + nsISocketTransport **result) +{ + nsresult rv; + + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + nsAutoCString path; + rv = aPath->GetNativePath(path); + if (NS_FAILED(rv)) + return rv; + + RefPtr<nsSocketTransport> trans = new nsSocketTransport(); + + rv = trans->InitWithFilename(path.get()); + if (NS_FAILED(rv)) + return rv; + + trans.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransportService::OnDispatchedEvent(nsIThreadInternal *thread) +{ +#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 (PR_GetCurrentThread() == gSocketThread) { + // 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)); + + psm::InitializeSSLServerCertVerificationThreads(); + + gSocketThread = PR_GetCurrentThread(); + + { + MutexAutoLock lock(mLock); + mPollableEvent.reset(new PollableEvent()); + // + // 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")); + } + + mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr; + mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT; + mPollList[0].out_flags = 0; + } + + mRawThread = NS_GetCurrentThread(); + + // 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; + int numberOfPendingEventsLastCycle; + + // 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; + int numberOfPendingEvents; + + // 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; + + numberOfPendingEvents = 0; + numberOfPendingEventsLastCycle = 0; + if (mTelemetryEnabledPref) { + startOfCycleForLastCycleCalc = TimeStamp::NowLoRes(); + startOfNextIteration = TimeStamp::NowLoRes(); + } + pollDuration = 0; + + do { + if (mTelemetryEnabledPref) { + pollCycleStart = TimeStamp::NowLoRes(); + } + + DoPollIteration(&singlePollDuration); + + if (mTelemetryEnabledPref && !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(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 (mTelemetryEnabledPref) { + 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); + numberOfPendingEvents++; + pendingEvents = false; + mRawThread->HasPendingEvents(&pendingEvents); + } while (pendingEvents && mServingPendingQueue && + ((TimeStamp::NowLoRes() - + eventQueueStart).ToMilliseconds() < + mMaxTimePerPollIter)); + + if (mTelemetryEnabledPref && !mServingPendingQueue && + !startOfIteration.IsNull()) { + Telemetry::AccumulateTimeDelta( + Telemetry::STS_POLL_AND_EVENTS_CYCLE, + startOfIteration + pollDuration, + TimeStamp::NowLoRes()); + + Telemetry::Accumulate( + Telemetry::STS_NUMBER_OF_PENDING_EVENTS, + numberOfPendingEvents); + + numberOfPendingEventsLastCycle += numberOfPendingEvents; + numberOfPendingEvents = 0; + pollDuration = 0; + } + } + } while (pendingEvents); + + bool goingOffline = false; + // now that our event queue is empty, check to see if we should exit + { + MutexAutoLock lock(mLock); + if (mShuttingDown) { + if (mTelemetryEnabledPref && + !startOfCycleForLastCycleCalc.IsNull()) { + Telemetry::Accumulate( + Telemetry::STS_NUMBER_OF_PENDING_EVENTS_IN_THE_LAST_CYCLE, + numberOfPendingEventsLastCycle); + Telemetry::AccumulateTimeDelta( + Telemetry::STS_POLL_AND_EVENT_THE_LAST_CYCLE, + startOfCycleForLastCycleCalc, + TimeStamp::NowLoRes()); + } + break; + } + 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); + + // Final pass over the event queue. This makes sure that events posted by + // socket detach handlers get processed. + NS_ProcessPendingEvents(mRawThread); + + gSocketThread = nullptr; + + psm::StopSSLServerCertVerificationThreads(); + + SOCKET_LOG(("STS thread exit\n")); + return NS_OK; +} + +void +nsSocketTransportService::DetachSocketWithGuard(bool aGuardLocals, + SocketContext *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 = mActiveCount - 1; i >= 0; --i) { + DetachSocketWithGuard(aGuardLocals, mActiveList, i); + } + for (i = mIdleCount - 1; i >= 0; --i) { + DetachSocketWithGuard(aGuardLocals, mIdleList, i); + } +} + +nsresult +nsSocketTransportService::DoPollIteration(TimeDuration *pollDuration) +{ + SOCKET_LOG(("STS poll iter\n")); + + 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 = mIdleCount; + for (i=mActiveCount-1; i>=0; --i) { + //--- + SOCKET_LOG((" active [%u] { handler=%p condition=%x pollflags=%hu }\n", i, + mActiveList[i].mHandler, + 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; + } + } + } + for (i=count-1; i>=0; --i) { + //--- + SOCKET_LOG((" idle [%u] { handler=%p condition=%x pollflags=%hu }\n", i, + mIdleList[i].mHandler, + 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]); + } + + SOCKET_LOG((" calling PR_Poll [active=%u idle=%u]\n", mActiveCount, mIdleCount)); + +#if defined(XP_WIN) + // 30 active connections is the historic limit before firefox 7's 256. A few + // windows systems have troubles with the higher limit, so actively probe a + // limit the first time we exceed 30. + if ((mActiveCount > 30) && !mProbedMaxCount) + ProbeMaxCount(); +#endif + + // Measures seconds spent while blocked on PR_Poll + uint32_t pollInterval = 0; + int32_t n = 0; + *pollDuration = 0; + if (!gIOService->IsNetTearingDown()) { + // Let's not do polling during shutdown. +#if defined(XP_WIN) + StartPolling(); +#endif + n = Poll(&pollInterval, pollDuration); +#if defined(XP_WIN) + EndPolling(); +#endif + } + + if (n < 0) { + SOCKET_LOG((" PR_Poll error [%d] os error [%d]\n", PR_GetError(), + PR_GetOSError())); + } + else { + // + // service "active" sockets... + // + uint32_t numberOfOnSocketReadyCalls = 0; + for (i=0; i<int32_t(mActiveCount); ++i) { + PRPollDesc &desc = mPollList[i+1]; + SocketContext &s = mActiveList[i]; + if (n > 0 && desc.out_flags != 0) { + s.mElapsedTime = 0; + s.mHandler->OnSocketReady(desc.fd, desc.out_flags); + numberOfOnSocketReadyCalls++; + } + // check for timeout errors unless disabled... + else if (s.mHandler->mPollTimeout != UINT16_MAX) { + // update elapsed time counter + // (NOTE: We explicitly cast UINT16_MAX to be an unsigned value + // here -- otherwise, some compilers will treat it as signed, + // which makes them fire signed/unsigned-comparison build + // warnings for the comparison against 'pollInterval'.) + if (MOZ_UNLIKELY(pollInterval > + static_cast<uint32_t>(UINT16_MAX) - + s.mElapsedTime)) + s.mElapsedTime = UINT16_MAX; + else + s.mElapsedTime += uint16_t(pollInterval); + // check for timeout expiration + if (s.mElapsedTime >= s.mHandler->mPollTimeout) { + s.mElapsedTime = 0; + s.mHandler->OnSocketReady(desc.fd, -1); + numberOfOnSocketReadyCalls++; + } + } + } + if (mTelemetryEnabledPref) { + Telemetry::Accumulate( + Telemetry::STS_NUMBER_OF_ONSOCKETREADY_CALLS, + numberOfOnSocketReadyCalls); + } + + // + // check for "dead" sockets and remove them (need to do this in + // reverse order obviously). + // + for (i=mActiveCount-1; i>=0; --i) { + if (NS_FAILED(mActiveList[i].mHandler->mCondition)) + DetachSocket(mActiveList, &mActiveList[i]); + } + + if (n != 0 && (mPollList[0].out_flags & (PR_POLL_READ | PR_POLL_EXCEPT))) { + MutexAutoLock lock(mLock); + + // acknowledge pollable event (should not block) + if (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". + 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; + } + } + } + + return NS_OK; +} + +void +nsSocketTransportService::UpdateSendBufferPref(nsIPrefBranch *pref) +{ + int32_t bufferSize; + + // If the pref is set, honor it. 0 means use OS defaults. + nsresult rv = pref->GetIntPref(SEND_BUFFER_PREF, &bufferSize); + if (NS_SUCCEEDED(rv)) { + mSendBufferSize = bufferSize; + return; + } + +#if defined(XP_WIN) + // If the pref is not set but this is windows set it depending on windows version + if (!IsWin2003OrLater()) { // windows xp + mSendBufferSize = 131072; + } else { // vista or later + mSendBufferSize = 131072 * 4; + } +#endif +} + +nsresult +nsSocketTransportService::UpdatePrefs() +{ + mSendBufferSize = 0; + + nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (tmpPrefService) { + UpdateSendBufferPref(tmpPrefService); + + // Default TCP Keepalive Values. + int32_t keepaliveIdleTimeS; + nsresult rv = tmpPrefService->GetIntPref(KEEPALIVE_IDLE_TIME_PREF, + &keepaliveIdleTimeS); + if (NS_SUCCEEDED(rv)) + mKeepaliveIdleTimeS = clamped(keepaliveIdleTimeS, + 1, kMaxTCPKeepIdle); + + int32_t keepaliveRetryIntervalS; + rv = tmpPrefService->GetIntPref(KEEPALIVE_RETRY_INTERVAL_PREF, + &keepaliveRetryIntervalS); + if (NS_SUCCEEDED(rv)) + mKeepaliveRetryIntervalS = clamped(keepaliveRetryIntervalS, + 1, kMaxTCPKeepIntvl); + + int32_t keepaliveProbeCount; + rv = tmpPrefService->GetIntPref(KEEPALIVE_PROBE_COUNT_PREF, + &keepaliveProbeCount); + if (NS_SUCCEEDED(rv)) + mKeepaliveProbeCount = clamped(keepaliveProbeCount, + 1, kMaxTCPKeepCount); + bool keepaliveEnabled = false; + rv = tmpPrefService->GetBoolPref(KEEPALIVE_ENABLED_PREF, + &keepaliveEnabled); + if (NS_SUCCEEDED(rv) && keepaliveEnabled != mKeepaliveEnabledPref) { + mKeepaliveEnabledPref = keepaliveEnabled; + OnKeepaliveEnabledPrefChange(); + } + + int32_t maxTimePref; + rv = tmpPrefService->GetIntPref(MAX_TIME_BETWEEN_TWO_POLLS, + &maxTimePref); + if (NS_SUCCEEDED(rv) && maxTimePref >= 0) { + mMaxTimePerPollIter = maxTimePref; + } + + bool telemetryPref = false; + rv = tmpPrefService->GetBoolPref(TELEMETRY_PREF, + &telemetryPref); + if (NS_SUCCEEDED(rv)) { + mTelemetryEnabledPref = telemetryPref; + } + + int32_t maxTimeForPrClosePref; + rv = tmpPrefService->GetIntPref(MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN, + &maxTimeForPrClosePref); + if (NS_SUCCEEDED(rv) && maxTimeForPrClosePref >=0) { + mMaxTimeForPrClosePref = PR_MillisecondsToInterval(maxTimeForPrClosePref); + } + } + + return NS_OK; +} + +void +nsSocketTransportService::OnKeepaliveEnabledPrefChange() +{ + // Dispatch to socket thread if we're not executing there. + if (PR_GetCurrentThread() != gSocketThread) { + gSocketTransportService->Dispatch( + NewRunnableMethod( + 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 = mActiveCount - 1; i >= 0; --i) { + NotifyKeepaliveEnabledPrefChange(&mActiveList[i]); + } + for (int32_t i = mIdleCount - 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::Observe(nsISupports *subject, + const char *topic, + const char16_t *data) +{ + if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + UpdatePrefs(); + return NS_OK; + } + + if (!strcmp(topic, "profile-initial-state")) { + int32_t blipInterval = Preferences::GetInt(BLIP_INTERVAL_PREF, 0); + if (blipInterval <= 0) { + return NS_OK; + } + + return net::NetworkActivityMonitor::Init(blipInterval); + } + + if (!strcmp(topic, "last-pb-context-exited")) { + nsCOMPtr<nsIRunnable> ev = + NewRunnableMethod(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) { + mAfterWakeUpTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mAfterWakeUpTimer) { + mAfterWakeUpTimer->Init(this, 2000, nsITimer::TYPE_ONE_SHOT); + } + } + } else if (!strcmp(topic, "xpcom-shutdown-threads")) { + ShutdownThread(); + } + + return NS_OK; +} + +void +nsSocketTransportService::ClosePrivateConnections() +{ + // Must be called on the socket thread. +#ifdef DEBUG + bool onSTSThread; + IsOnCurrentThread(&onSTSThread); + MOZ_ASSERT(onSTSThread); +#endif + + for (int32_t i = mActiveCount - 1; i >= 0; --i) { + if (mActiveList[i].mHandler->mIsPrivate) { + DetachSocket(mActiveList, &mActiveList[i]); + } + } + for (int32_t i = mIdleCount - 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 + +// Right now the only need to do this is on windows. +#if defined(XP_WIN) +void +nsSocketTransportService::ProbeMaxCount() +{ + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + if (mProbedMaxCount) + return; + mProbedMaxCount = true; + + // Allocate and test a PR_Poll up to the gMaxCount number of unconnected + // sockets. See bug 692260 - windows should be able to handle 1000 sockets + // in select() without a problem, but LSPs have been known to balk at lower + // numbers. (64 in the bug). + + // Allocate + struct PRPollDesc pfd[SOCKET_LIMIT_TARGET]; + uint32_t numAllocated = 0; + + for (uint32_t index = 0 ; index < gMaxCount; ++index) { + pfd[index].in_flags = PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT; + pfd[index].out_flags = 0; + pfd[index].fd = PR_OpenTCPSocket(PR_AF_INET); + if (!pfd[index].fd) { + SOCKET_LOG(("Socket Limit Test index %d failed\n", index)); + if (index < SOCKET_LIMIT_MIN) + gMaxCount = SOCKET_LIMIT_MIN; + else + gMaxCount = index; + break; + } + ++numAllocated; + } + + // Test + static_assert(SOCKET_LIMIT_MIN >= 32U, "Minimum Socket Limit is >= 32"); + while (gMaxCount <= numAllocated) { + int32_t rv = PR_Poll(pfd, gMaxCount, PR_MillisecondsToInterval(0)); + + SOCKET_LOG(("Socket Limit Test poll() size=%d rv=%d\n", + gMaxCount, rv)); + + if (rv >= 0) + break; + + SOCKET_LOG(("Socket Limit Test poll confirmationSize=%d rv=%d error=%d\n", + gMaxCount, rv, PR_GetError())); + + gMaxCount -= 32; + if (gMaxCount <= SOCKET_LIMIT_MIN) { + gMaxCount = SOCKET_LIMIT_MIN; + break; + } + } + + // Free + for (uint32_t index = 0 ; index < numAllocated; ++index) + if (pfd[index].fd) + PR_Close(pfd[index].fd); + + Telemetry::Accumulate(Telemetry::NETWORK_PROBE_MAXCOUNT, gMaxCount); + SOCKET_LOG(("Socket Limit Test max was confirmed at %d\n", gMaxCount)); +} +#endif // windows + +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, + struct 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); + + bool tcp = PR_GetDescType(idLayer) == PR_DESC_SOCKET_TCP; + + PRNetAddr peer_addr; + PodZero(&peer_addr); + PRStatus rv = PR_GetPeerName(aFD, &peer_addr); + if (rv != PR_SUCCESS) + return; + + char host[64] = {0}; + rv = PR_NetAddrToString(&peer_addr, host, sizeof(host)); + if (rv != PR_SUCCESS) + return; + + uint16_t port; + if (peer_addr.raw.family == PR_AF_INET) + port = peer_addr.inet.port; + else + port = peer_addr.ipv6.port; + port = PR_ntohs(port); + uint64_t sent = context->mHandler->ByteCountSent(); + uint64_t received = context->mHandler->ByteCountReceived(); + SocketInfo info = { nsCString(host), sent, received, port, aActive, tcp }; + + data->AppendElement(info); +} + +void +nsSocketTransportService::GetSocketConnections(nsTArray<SocketInfo> *data) +{ + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + for (uint32_t i = 0; i < mActiveCount; i++) + AnalyzeConnection(data, &mActiveList[i], true); + for (uint32_t i = 0; i < mIdleCount; i++) + AnalyzeConnection(data, &mIdleList[i], false); +} + +#if defined(XP_WIN) +void +nsSocketTransportService::StartPollWatchdog() +{ + MutexAutoLock lock(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. + MOZ_ASSERT(gIOService->IsNetTearingDown()); + if (mPolling && !mPollRepairTimer) { + mPollRepairTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + mPollRepairTimer->Init(this, 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 + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsSocketTransportService2.h b/netwerk/base/nsSocketTransportService2.h new file mode 100644 index 000000000..81c806793 --- /dev/null +++ b/netwerk/base/nsSocketTransportService2.h @@ -0,0 +1,282 @@ +/* vim:set ts=4 sw=4 sts=4 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 "nsPISocketTransportService.h" +#include "nsIThreadInternal.h" +#include "nsIRunnable.h" +#include "nsEventQueue.h" +#include "nsCOMPtr.h" +#include "prinrval.h" +#include "mozilla/Logging.h" +#include "prinit.h" +#include "nsIObserver.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Mutex.h" +#include "mozilla/net/DashboardTypes.h" +#include "mozilla/Atomics.h" +#include "mozilla/TimeStamp.h" +#include "nsITimer.h" +#include "mozilla/UniquePtr.h" +#include "PollableEvent.h" + +class nsASocketHandler; +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_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() {} + + already_AddRefed<nsIRunnable> TakeEvent() + { + return mEvent.forget(); + } +private: + nsCOMPtr<nsIRunnable> mEvent; +}; + +//----------------------------------------------------------------------------- + +class nsSocketTransportService final : public nsPISocketTransportService + , public nsIEventTarget + , public nsIThreadObserver + , public nsIRunnable + , public nsIObserver +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSPISOCKETTRANSPORTSERVICE + NS_DECL_NSISOCKETTRANSPORTSERVICE + NS_DECL_NSIROUTEDSOCKETTRANSPORTSERVICE + NS_DECL_NSIEVENTTARGET + NS_DECL_NSITHREADOBSERVER + NS_DECL_NSIRUNNABLE + NS_DECL_NSIOBSERVER + using nsIEventTarget::Dispatch; + + 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() { return mTelemetryEnabledPref && + !mSleepPhase; } + PRIntervalTime MaxTimeForPrClosePref() {return mMaxTimeForPrClosePref; } +protected: + + virtual ~nsSocketTransportService(); + +private: + + //------------------------------------------------------------------------- + // misc (any thread) + //------------------------------------------------------------------------- + + nsCOMPtr<nsIThread> mThread; // protected by mLock + UniquePtr<PollableEvent> mPollableEvent; + + // Returns mThread, protecting the get-and-addref with mLock + already_AddRefed<nsIThread> GetThreadSafely(); + + //------------------------------------------------------------------------- + // initialization and shutdown (any thread) + //------------------------------------------------------------------------- + + Mutex mLock; + bool mInitialized; + bool mShuttingDown; + // indicates whether we are currently in the + // process of shutting down + bool mOffline; + bool mGoingOffline; + + // 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,... + //------------------------------------------------------------------------- + + struct SocketContext + { + PRFileDesc *mFD; + nsASocketHandler *mHandler; + uint16_t mElapsedTime; // time elapsed w/o activity + }; + + SocketContext *mActiveList; /* mListSize entries */ + SocketContext *mIdleList; /* mListSize entries */ + nsIThread *mRawThread; + + uint32_t mActiveListSize; + uint32_t mIdleListSize; + uint32_t mActiveCount; + uint32_t mIdleCount; + + nsresult DetachSocket(SocketContext *, SocketContext *); + nsresult AddToIdleList(SocketContext *); + nsresult AddToPollList(SocketContext *); + void RemoveFromIdleList(SocketContext *); + void RemoveFromPollList(SocketContext *); + void MoveToIdleList(SocketContext *sock); + void MoveToPollList(SocketContext *sock); + + bool GrowActiveList(); + bool GrowIdleList(); + void InitMaxCount(); + + // Total bytes number transfered through all the sockets except active ones + uint64_t mSentBytesCount; + uint64_t mReceivedBytesCount; + //------------------------------------------------------------------------- + // poll list (socket thread only) + // + // first element of the poll list is mPollableEvent (or null if the pollable + // event cannot be created). + //------------------------------------------------------------------------- + + PRPollDesc *mPollList; /* mListSize + 1 entries */ + + PRIntervalTime PollTimeout(); // computes ideal poll timeout + nsresult DoPollIteration(TimeDuration *pollDuration); + // perfoms a single poll iteration + int32_t Poll(uint32_t *interval, + TimeDuration *pollDuration); + // 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(); + void UpdateSendBufferPref(nsIPrefBranch *); + int32_t mSendBufferSize; + // Number of seconds of connection is idle before first keepalive ping. + int32_t mKeepaliveIdleTimeS; + // Number of seconds between retries should keepalive pings fail. + int32_t mKeepaliveRetryIntervalS; + // Number of keepalive probes to send. + int32_t mKeepaliveProbeCount; + // True if TCP keepalive is enabled globally. + bool mKeepaliveEnabledPref; + + Atomic<bool> mServingPendingQueue; + Atomic<int32_t, Relaxed> mMaxTimePerPollIter; + Atomic<bool, Relaxed> mTelemetryEnabledPref; + Atomic<PRIntervalTime, Relaxed> mMaxTimeForPrClosePref; + + // Between a computer going to sleep and waking up the PR_*** telemetry + // will be corrupted - so do not record it. + Atomic<bool, Relaxed> mSleepPhase; + nsCOMPtr<nsITimer> mAfterWakeUpTimer; + + void OnKeepaliveEnabledPrefChange(); + void NotifyKeepaliveEnabledPrefChange(SocketContext *sock); + + // Socket thread only for dynamically adjusting max socket size +#if defined(XP_WIN) + void ProbeMaxCount(); +#endif + bool mProbedMaxCount; + + void AnalyzeConnection(nsTArray<SocketInfo> *data, + SocketContext *context, bool aActive); + + void ClosePrivateConnections(); + void DetachSocketWithGuard(bool aGuardLocals, + SocketContext *socketList, + int32_t index); + + void MarkTheLastElementOfPendingQueue(); + +#if defined(XP_WIN) + Atomic<bool> mPolling; + nsCOMPtr<nsITimer> mPollRepairTimer; + void StartPollWatchdog(); + void DoPollRepair(); + void StartPolling(); + void EndPolling(); +#endif +}; + +extern nsSocketTransportService *gSocketTransportService; +extern Atomic<PRThread*, Relaxed> gSocketThread; + +} // namespace net +} // namespace mozilla + +#endif // !nsSocketTransportService_h__ diff --git a/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp new file mode 100644 index 000000000..922d8a3ba --- /dev/null +++ b/netwerk/base/nsStandardURL.cpp @@ -0,0 +1,3619 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=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 "IPCMessageUtils.h" + +#include "nsStandardURL.h" +#include "nsCRT.h" +#include "nsEscape.h" +#include "nsIFile.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIIDNService.h" +#include "mozilla/Logging.h" +#include "nsAutoPtr.h" +#include "nsIURLParser.h" +#include "nsNetCID.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ipc/URIUtils.h" +#include <algorithm> +#include "mozilla/dom/EncodingUtils.h" +#include "nsContentUtils.h" +#include "prprf.h" +#include "nsReadableUtils.h" + +using mozilla::dom::EncodingUtils; +using namespace mozilla::ipc; + +namespace mozilla { +namespace net { + +static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID); +static NS_DEFINE_CID(kStandardURLCID, NS_STANDARDURL_CID); + +nsIIDNService *nsStandardURL::gIDN = nullptr; +bool nsStandardURL::gInitialized = false; +bool nsStandardURL::gEscapeUTF8 = true; +bool nsStandardURL::gAlwaysEncodeInUTF8 = true; +char nsStandardURL::gHostLimitDigits[] = { '/', '\\', '?', '#', 0 }; + +// +// setenv MOZ_LOG nsStandardURL:5 +// +static 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) + +//---------------------------------------------------------------------------- + +#define ENSURE_MUTABLE() \ + PR_BEGIN_MACRO \ + if (!mMutable) { \ + NS_WARNING("attempt to modify an immutable nsStandardURL"); \ + return NS_ERROR_ABORT; \ + } \ + PR_END_MACRO + +//---------------------------------------------------------------------------- + +#ifdef MOZ_RUST_URLPARSE +extern "C" int32_t c_fn_set_size(void * container, size_t size) +{ + ((nsACString *) container)->SetLength(size); + return 0; +} + +extern "C" char * c_fn_get_buffer(void * container) +{ + return ((nsACString *) container)->BeginWriting(); +} +#endif + +static nsresult +EncodeString(nsIUnicodeEncoder *encoder, const nsAFlatString &str, nsACString &result) +{ + nsresult rv; + int32_t len = str.Length(); + int32_t maxlen; + + rv = encoder->GetMaxLength(str.get(), len, &maxlen); + if (NS_FAILED(rv)) + return rv; + + char buf[256], *p = buf; + if (uint32_t(maxlen) > sizeof(buf) - 1) { + p = (char *) malloc(maxlen + 1); + if (!p) + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = encoder->Convert(str.get(), &len, p, &maxlen); + if (NS_FAILED(rv)) + goto end; + if (rv == NS_ERROR_UENC_NOMAPPING) { + NS_WARNING("unicode conversion failed"); + rv = NS_ERROR_UNEXPECTED; + goto end; + } + p[maxlen] = 0; + result.Assign(p); + + len = sizeof(buf) - 1; + rv = encoder->Finish(buf, &len); + if (NS_FAILED(rv)) + goto end; + buf[len] = 0; + result.Append(buf); + +end: + encoder->Reset(); + + if (p != buf) + free(p); + return rv; +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsPrefObserver +//---------------------------------------------------------------------------- + +#define NS_NET_PREF_ESCAPEUTF8 "network.standard-url.escape-utf8" +#define NS_NET_PREF_ALWAYSENCODEINUTF8 "network.standard-url.encode-utf8" + +NS_IMPL_ISUPPORTS(nsStandardURL::nsPrefObserver, nsIObserver) + +NS_IMETHODIMP nsStandardURL:: +nsPrefObserver::Observe(nsISupports *subject, + const char *topic, + const char16_t *data) +{ + if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + nsCOMPtr<nsIPrefBranch> prefBranch( do_QueryInterface(subject) ); + if (prefBranch) { + PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get()); + } + } + return NS_OK; +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsSegmentEncoder +//---------------------------------------------------------------------------- + +nsStandardURL:: +nsSegmentEncoder::nsSegmentEncoder(const char *charset) + : mCharset(charset) +{ +} + +int32_t nsStandardURL:: +nsSegmentEncoder::EncodeSegmentCount(const char *str, + const URLSegment &seg, + int16_t mask, + nsAFlatCString &result, + bool &appended, + uint32_t extraLen) +{ + // extraLen is characters outside the segment that will be + // added when the segment is not empty (like the @ following + // a username). + appended = false; + if (!str) + return 0; + int32_t len = 0; + if (seg.mLen > 0) { + uint32_t pos = seg.mPos; + len = seg.mLen; + + // first honor the origin charset if appropriate. as an optimization, + // only do this if the segment is non-ASCII. Further, if mCharset is + // null or the empty string then the origin charset is UTF-8 and there + // is nothing to do. + nsAutoCString encBuf; + if (mCharset && *mCharset && !nsCRT::IsAscii(str + pos, len)) { + // we have to encode this segment + if (mEncoder || InitUnicodeEncoder()) { + NS_ConvertUTF8toUTF16 ucsBuf(Substring(str + pos, str + pos + len)); + if (NS_SUCCEEDED(EncodeString(mEncoder, ucsBuf, encBuf))) { + str = encBuf.get(); + pos = 0; + len = encBuf.Length(); + } + // else some failure occurred... assume UTF-8 is ok. + } + } + + // escape per RFC2396 unless UTF-8 and allowed by preferences + int16_t escapeFlags = (gEscapeUTF8 || mEncoder) ? 0 : esc_OnlyASCII; + + uint32_t initLen = result.Length(); + + // now perform any required escaping + if (NS_EscapeURL(str + pos, len, mask | escapeFlags, result)) { + len = result.Length() - initLen; + appended = true; + } + else if (str == encBuf.get()) { + result += encBuf; // append only!! + len = encBuf.Length(); + appended = true; + } + len += extraLen; + } + return len; +} + +const nsACString &nsStandardURL:: +nsSegmentEncoder::EncodeSegment(const nsASingleFragmentCString &str, + int16_t mask, + nsAFlatCString &result) +{ + const char *text; + bool encoded; + EncodeSegmentCount(str.BeginReading(text), URLSegment(0, str.Length()), mask, result, encoded); + if (encoded) + return result; + return str; +} + +bool nsStandardURL:: +nsSegmentEncoder::InitUnicodeEncoder() +{ + NS_ASSERTION(!mEncoder, "Don't call this if we have an encoder already!"); + // "replacement" won't survive another label resolution + nsDependentCString label(mCharset); + if (label.EqualsLiteral("replacement")) { + mEncoder = EncodingUtils::EncoderForEncoding(label); + return true; + } + nsAutoCString encoding; + if (!EncodingUtils::FindEncodingForLabelNoReplacement(label, encoding)) { + return false; + } + mEncoder = EncodingUtils::EncoderForEncoding(encoding); + return true; +} + +#define GET_SEGMENT_ENCODER_INTERNAL(name, useUTF8) \ + nsSegmentEncoder name(useUTF8 ? nullptr : mOriginCharset.get()) + +#define GET_SEGMENT_ENCODER(name) \ + GET_SEGMENT_ENCODER_INTERNAL(name, gAlwaysEncodeInUTF8) + +#define GET_QUERY_ENCODER(name) \ + GET_SEGMENT_ENCODER_INTERNAL(name, false) + +//---------------------------------------------------------------------------- +// nsStandardURL <public> +//---------------------------------------------------------------------------- + +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN +static PRCList gAllURLs; +#endif + +nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL) + : mDefaultPort(-1) + , mPort(-1) + , mHostA(nullptr) + , mHostEncoding(eEncoding_ASCII) + , mSpecEncoding(eEncoding_Unknown) + , mURLType(URLTYPE_STANDARD) + , mMutable(true) + , mSupportsFileURL(aSupportsFileURL) +{ + LOG(("Creating nsStandardURL @%p\n", this)); + + if (!gInitialized) { + gInitialized = true; + InitGlobalObjects(); + } + + // default parser in case nsIStandardURL::Init is never called + mParser = net_GetStdURLParser(); + +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN + memset(&mDebugCList, 0, sizeof(mDebugCList)); + if (NS_IsMainThread()) { + if (aTrackURL) { + PR_APPEND_LINK(&mDebugCList, &gAllURLs); + } else { + PR_INIT_CLIST(&mDebugCList); + } + } +#endif +} + +nsStandardURL::~nsStandardURL() +{ + LOG(("Destroying nsStandardURL @%p\n", this)); + + if (mHostA) { + free(mHostA); + } +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN + if (NS_IsMainThread()) { + if (!PR_CLIST_IS_EMPTY(&mDebugCList)) { + PR_REMOVE_LINK(&mDebugCList); + } + } +#endif +} + +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN +struct DumpLeakedURLs { + DumpLeakedURLs() {} + ~DumpLeakedURLs(); +}; + +DumpLeakedURLs::~DumpLeakedURLs() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!PR_CLIST_IS_EMPTY(&gAllURLs)) { + printf("Leaked URLs:\n"); + for (PRCList *l = PR_LIST_HEAD(&gAllURLs); l != &gAllURLs; l = PR_NEXT_LINK(l)) { + nsStandardURL *url = reinterpret_cast<nsStandardURL*>(reinterpret_cast<char*>(l) - offsetof(nsStandardURL, mDebugCList)); + url->PrintSpec(); + } + } +} +#endif + +void +nsStandardURL::InitGlobalObjects() +{ + nsCOMPtr<nsIPrefBranch> prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID) ); + if (prefBranch) { + nsCOMPtr<nsIObserver> obs( new nsPrefObserver() ); + prefBranch->AddObserver(NS_NET_PREF_ESCAPEUTF8, obs.get(), false); + prefBranch->AddObserver(NS_NET_PREF_ALWAYSENCODEINUTF8, obs.get(), false); + + PrefsChanged(prefBranch, nullptr); + } + +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN + PR_INIT_CLIST(&gAllURLs); +#endif +} + +void +nsStandardURL::ShutdownGlobalObjects() +{ + NS_IF_RELEASE(gIDN); + +#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(). + static DumpLeakedURLs d; + } +#endif +} + +//---------------------------------------------------------------------------- +// nsStandardURL <private> +//---------------------------------------------------------------------------- + +void +nsStandardURL::Clear() +{ + mSpec.Truncate(); + + mPort = -1; + + mScheme.Reset(); + mAuthority.Reset(); + mUsername.Reset(); + mPassword.Reset(); + mHost.Reset(); + mHostEncoding = eEncoding_ASCII; + + mPath.Reset(); + mFilepath.Reset(); + mDirectory.Reset(); + mBasename.Reset(); + + mExtension.Reset(); + mQuery.Reset(); + mRef.Reset(); + + InvalidateCache(); +} + +void +nsStandardURL::InvalidateCache(bool invalidateCachedFile) +{ + if (invalidateCachedFile) + mFile = nullptr; + if (mHostA) { + free(mHostA); + mHostA = nullptr; + } + mSpecEncoding = eEncoding_Unknown; +} + +// |base| should be 8, 10 or 16. Not check the precondition for performance. +/* static */ inline bool +nsStandardURL::IsValidOfBase(unsigned char c, const uint32_t base) { + MOZ_ASSERT(base == 8 || base == 10 || base == 16, "invalid base"); + if ('0' <= c && c <= '7') { + return true; + } else if (c == '8' || c== '9') { + return base != 8; + } else if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')) { + return base == 16; + } + return false; +} + +/* static */ inline nsresult +nsStandardURL::ParseIPv4Number(nsCString &input, uint32_t &number) +{ + if (input.Length() == 0) { + return NS_ERROR_FAILURE; + } + uint32_t base; + uint32_t prefixLength = 0; + + if (input[0] == '0') { + if (input.Length() == 1) { + base = 10; + } else if (input[1] == 'x' || input[1] == 'X') { + base = 16; + prefixLength = 2; + } else { + base = 8; + prefixLength = 1; + } + } else { + base = 10; + } + if (prefixLength == input.Length()) { + return NS_ERROR_FAILURE; + } + // ignore leading zeros to calculate the valid length of number + while (prefixLength < input.Length() && input[prefixLength] == '0') { + prefixLength++; + } + // all zero case + if (prefixLength == input.Length()) { + number = 0; + return NS_OK; + } + // overflow case + if (input.Length() - prefixLength > 16) { + return NS_ERROR_FAILURE; + } + for (uint32_t i = prefixLength; i < input.Length(); ++i) { + if (!IsValidOfBase(input[i], base)) { + return NS_ERROR_FAILURE; + } + } + const char* fmt = ""; + switch (base) { + case 8: + fmt = "%llo"; + break; + case 10: + fmt = "%lli"; + break; + case 16: + fmt = "%llx"; + break; + default: + return NS_ERROR_FAILURE; + } + uint64_t number64; + if (PR_sscanf(input.get(), fmt, &number64) == 1 && + number64 <= 0xffffffffu) { + number = number64; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +// IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser +/* static */ nsresult +nsStandardURL::NormalizeIPv4(const nsCSubstring &host, nsCString &result) +{ + if (host.Length() == 0 || + host[0] < '0' || '9' < host[0] || // bail-out fast + FindInReadable(NS_LITERAL_CSTRING(".."), host)) { + return NS_ERROR_FAILURE; + } + + nsTArray<nsCString> parts; + if (!ParseString(host, '.', parts) || + parts.Length() == 0 || + parts.Length() > 4) { + return NS_ERROR_FAILURE; + } + uint32_t n = 0; + nsTArray<int32_t> numbers; + for (uint32_t i = 0; i < parts.Length(); ++i) { + if (NS_FAILED(ParseIPv4Number(parts[i], n))) { + return NS_ERROR_FAILURE; + } + numbers.AppendElement(n); + } + uint32_t ipv4 = numbers.LastElement(); + static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu, + 0xffffu, 0xffu}; + if (ipv4 > upperBounds[numbers.Length() - 1]) { + return NS_ERROR_FAILURE; + } + for (uint32_t i = 0; i < numbers.Length() - 1; ++i) { + if (numbers[i] > 255) { + return NS_ERROR_FAILURE; + } + ipv4 += numbers[i] << (8 * (3 - i)); + } + + 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 nsCSubstring &host, nsCString &result) +{ + // If host is ACE, then convert to UTF-8. Else, if host is already UTF-8, + // then make sure it is normalized per IDN. + + // this function returns true if normalization succeeds. + + // NOTE: As a side-effect this function sets mHostEncoding. While it would + // be nice to avoid side-effects in this function, the implementation of + // this function is already somewhat bound to the behavior of the + // callsites. Anyways, this function exists to avoid code duplication, so + // side-effects abound :-/ + + NS_ASSERTION(mHostEncoding == eEncoding_ASCII, "unexpected default encoding"); + + bool isASCII; + if (!gIDN) { + nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID)); + if (serv) { + NS_ADDREF(gIDN = serv.get()); + } + } + + result.Truncate(); + nsresult rv = NS_ERROR_UNEXPECTED; + if (gIDN) { + rv = gIDN->ConvertToDisplayIDN(host, &isASCII, result); + if (NS_SUCCEEDED(rv) && !isASCII) { + mHostEncoding = eEncoding_UTF8; + } + } + + return rv; +} + +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(host + 1, length - 2); + } + + if (openBracket || closeBracket) { + // Fail if only one of the brackets is present + return false; + } + + const char *end = host + length; + if (end != net_FindCharInSet(host, end, CONTROL_CHARACTERS " #/:?@[\\]*<>|\"")) { + // We still allow % because it is in the ID of addons. + // Any percent encoded ASCII characters that are not allowed in the + // hostname are not percent decoded, and will be parsed just fine. + 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; +} + +// 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) +{ + // 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. + { + GET_SEGMENT_ENCODER(encoder); + GET_QUERY_ENCODER(queryEncoder); + // Items using an extraLen of 1 don't add anything unless mLen > 0 + // Username@ + approxLen += encoder.EncodeSegmentCount(spec, mUsername, esc_Username, encUsername, useEncUsername, 1); + // :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) { + if (nsContentUtils::EncodeDecodeURLHash()) { + approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref, + encRef, useEncRef); + } else { + approxLen += 1 + mRef.mLen; + useEncRef = false; + } + } + } + + // 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. + mHostEncoding = eEncoding_ASCII; + // 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 (NS_SUCCEEDED(NormalizeIPv4(encHost, ipString))) { + 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; + } + } + + // 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 filepath(mFilepath); + URLSegment directory(mDirectory); + URLSegment basename(mBasename); + URLSegment extension(mExtension); + URLSegment query(mQuery); + URLSegment ref(mRef); + + // + // 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(buf); + 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) { + i = AppendSegmentToBuf(buf, i, spec, username, mUsername, + &encUsername, useEncUsername, &diff); + ShiftFromPassword(diff); + if (password.mLen >= 0) { + buf[i++] = ':'; + i = AppendSegmentToBuf(buf, i, spec, password, mPassword, + &encPassword, useEncPassword, &diff); + ShiftFromHost(diff); + } + buf[i++] = '@'; + } + 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'; + + 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.SetLength(strlen(buf)); + NS_ASSERTION(mSpec.Length() <= approxLen, "We've overflowed the mSpec buffer!"); + 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 !PL_strncasecmp(mSpec.get() + seg.mPos, val, seg.mLen) + && (val[seg.mLen] == '\0'); + else + 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 !PL_strncasecmp(spec + seg.mPos, val, seg.mLen) + && (val[seg.mLen] == '\0'); + else + 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 !PL_strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen); + else + 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 > net_GetURLMaxLength()) { + return NS_ERROR_MALFORMED_URI; + } + + // + // parse given URL string + // + rv = mParser->ParseURL(spec, specLen, + &mScheme.mPos, &mScheme.mLen, + &mAuthority.mPos, &mAuthority.mLen, + &mPath.mPos, &mPath.mLen); + if (NS_FAILED(rv)) return rv; + +#ifdef DEBUG + if (mScheme.mLen <= 0) { + printf("spec=%s\n", spec); + NS_WARNING("malformed url: no scheme"); + } +#endif + + if (mAuthority.mLen > 0) { + rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen, + &mUsername.mPos, &mUsername.mLen, + &mPassword.mPos, &mPassword.mLen, + &mHost.mPos, &mHost.mLen, + &mPort); + if (NS_FAILED(rv)) return rv; + + // 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 > net_GetURLMaxLength()) { + return NS_ERROR_MALFORMED_URI; + } + + nsresult rv = mParser->ParsePath(spec + pathPos, pathLen, + &mFilepath.mPos, &mFilepath.mLen, + &mQuery.mPos, &mQuery.mLen, + &mRef.mPos, &mRef.mLen); + if (NS_FAILED(rv)) return rv; + + mFilepath.mPos += pathPos; + mQuery.mPos += pathPos; + mRef.mPos += pathPos; + + if (mFilepath.mLen > 0) { + rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen, + &mDirectory.mPos, &mDirectory.mLen, + &mBasename.mPos, &mBasename.mLen, + &mExtension.mPos, &mExtension.mLen); + if (NS_FAILED(rv)) return rv; + + 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); + if (result) { + 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; + + rv = stream->Read32(&seg.mPos); + if (NS_FAILED(rv)) return rv; + + rv = stream->Read32((uint32_t *) &seg.mLen); + if (NS_FAILED(rv)) return rv; + + 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; +} + +/* static */ void +nsStandardURL::PrefsChanged(nsIPrefBranch *prefs, const char *pref) +{ + bool val; + + LOG(("nsStandardURL::PrefsChanged [pref=%s]\n", pref)); + +#define PREF_CHANGED(p) ((pref == nullptr) || !strcmp(pref, p)) +#define GOT_PREF(p, b) (NS_SUCCEEDED(prefs->GetBoolPref(p, &b))) + + if (PREF_CHANGED(NS_NET_PREF_ESCAPEUTF8)) { + if (GOT_PREF(NS_NET_PREF_ESCAPEUTF8, val)) + gEscapeUTF8 = val; + LOG(("escape UTF-8 %s\n", gEscapeUTF8 ? "enabled" : "disabled")); + } + + if (PREF_CHANGED(NS_NET_PREF_ALWAYSENCODEINUTF8)) { + if (GOT_PREF(NS_NET_PREF_ALWAYSENCODEINUTF8, val)) + gAlwaysEncodeInUTF8 = val; + LOG(("encode in UTF-8 %s\n", gAlwaysEncodeInUTF8 ? "enabled" : "disabled")); + } +#undef PREF_CHANGED +#undef GOT_PREF +} + +#define SHIFT_FROM(name, what) \ +void \ +nsStandardURL::name(int32_t diff) \ +{ \ + if (!diff) return; \ + if (what.mLen >= 0) { \ + CheckedInt<int32_t> pos = what.mPos; \ + pos += diff; \ + MOZ_ASSERT(pos.isValid()); \ + what.mPos = pos.value(); \ + } + +#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::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(nsIURIWithQuery) + NS_INTERFACE_MAP_ENTRY(nsIURL) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL) + NS_INTERFACE_MAP_ENTRY(nsIStandardURL) + NS_INTERFACE_MAP_ENTRY(nsISerializable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY(nsIMutable) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableURI) + 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() <= (uint32_t) net_GetURLMaxLength(), + "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) +{ + result = mSpec; + if (mPassword.mLen >= 0) { + result.Replace(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) { + URLSegment noRef(0, mRef.mPos - 1); + + result = Segment(noRef); + } else { + result = mSpec; + } + return NS_OK; +} + +// result may contain unescaped UTF-8 characters +NS_IMETHODIMP +nsStandardURL::GetPrePath(nsACString &result) +{ + result = Prepath(); + 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) +{ + result = Hostport(); + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetHost(nsACString &result) +{ + result = Host(); + return NS_OK; +} + +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::GetPath(nsACString &result) +{ + result = Path(); + return NS_OK; +} + +// result is ASCII +NS_IMETHODIMP +nsStandardURL::GetAsciiSpec(nsACString &result) +{ + if (mSpecEncoding == eEncoding_Unknown) { + if (IsASCII(mSpec)) + mSpecEncoding = eEncoding_ASCII; + else + mSpecEncoding = eEncoding_UTF8; + } + + if (mSpecEncoding == eEncoding_ASCII) { + result = mSpec; + return NS_OK; + } + + // try to guess the capacity required for result... + result.SetCapacity(mSpec.Length() + std::min<uint32_t>(32, mSpec.Length()/10)); + + result = Substring(mSpec, 0, mScheme.mLen + 3); + + // This is left fallible as this entire function is expected to be + // infallible. + NS_EscapeURL(Userpass(true), esc_OnlyNonASCII | esc_AlwaysCopy, result); + + // get the hostport + nsAutoCString hostport; + MOZ_ALWAYS_SUCCEEDS(GetAsciiHostPort(hostport)); + result += hostport; + + // This is left fallible as this entire function is expected to be + // infallible. + NS_EscapeURL(Path(), esc_OnlyNonASCII | esc_AlwaysCopy, result); + return NS_OK; +} + +// result is ASCII +NS_IMETHODIMP +nsStandardURL::GetAsciiHostPort(nsACString &result) +{ + if (mHostEncoding == eEncoding_ASCII) { + result = Hostport(); + return NS_OK; + } + + MOZ_ALWAYS_SUCCEEDS(GetAsciiHost(result)); + + // As our mHostEncoding is not eEncoding_ASCII, we know that + // the our host is not ipv6, and we can avoid looking at it. + MOZ_ASSERT(result.FindChar(':') == -1, "The host must not be ipv6"); + + // hostport = "hostA" + ":port" + uint32_t pos = mHost.mPos + mHost.mLen; + if (pos < mPath.mPos) + result += Substring(mSpec, pos, mPath.mPos - pos); + + return NS_OK; +} + +// result is ASCII +NS_IMETHODIMP +nsStandardURL::GetAsciiHost(nsACString &result) +{ + if (mHostEncoding == eEncoding_ASCII) { + result = Host(); + return NS_OK; + } + + // perhaps we have it cached... + if (mHostA) { + result = mHostA; + return NS_OK; + } + + if (gIDN) { + nsresult rv; + rv = gIDN->ConvertUTF8toACE(Host(), result); + if (NS_SUCCEEDED(rv)) { + mHostA = ToNewCString(result); + return NS_OK; + } + NS_WARNING("nsIDNService::ConvertUTF8toACE failed"); + } + + // something went wrong... guess all we can do is URL escape :-/ + NS_EscapeURL(Host(), esc_OnlyNonASCII | esc_AlwaysCopy, result); + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetOriginCharset(nsACString &result) +{ + if (mOriginCharset.IsEmpty()) + result.AssignLiteral("UTF-8"); + else + result = mOriginCharset; + 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"); +} + +NS_IMETHODIMP +nsStandardURL::SetSpec(const nsACString &input) +{ + ENSURE_MUTABLE(); + + const nsPromiseFlatCString &flat = PromiseFlatCString(input); + LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get())); + + if (input.Length() > (uint32_t) net_GetURLMaxLength()) { + 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 curent URL + nsStandardURL prevURL(false,false); + prevURL.CopyMembers(this, eHonorRef, EmptyCString()); + Clear(); + + if (IsSpecialProtocol(filteredURI)) { + // Bug 652186: Replace all backslashes with slashes when parsing paths + // Stop when we reach the query or the hash. + nsAutoCString::iterator start; + nsAutoCString::iterator end; + filteredURI.BeginWriting(start); + filteredURI.EndWriting(end); + 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 (NS_SUCCEEDED(rv)) { + // finally, use the URLSegment member variables to build a normalized + // copy of |spec| + rv = BuildNormalizedSpec(spec); + } + + 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, EmptyCString()); + return rv; + } + + if (LOG_ENABLED()) { + LOG((" spec = %s\n", mSpec.get())); + LOG((" port = %d\n", mPort)); + LOG((" scheme = (%u,%d)\n", mScheme.mPos, mScheme.mLen)); + LOG((" authority = (%u,%d)\n", mAuthority.mPos, mAuthority.mLen)); + LOG((" username = (%u,%d)\n", mUsername.mPos, mUsername.mLen)); + LOG((" password = (%u,%d)\n", mPassword.mPos, mPassword.mLen)); + LOG((" hostname = (%u,%d)\n", mHost.mPos, mHost.mLen)); + LOG((" path = (%u,%d)\n", mPath.mPos, mPath.mLen)); + LOG((" filepath = (%u,%d)\n", mFilepath.mPos, mFilepath.mLen)); + LOG((" directory = (%u,%d)\n", mDirectory.mPos, mDirectory.mLen)); + LOG((" basename = (%u,%d)\n", mBasename.mPos, mBasename.mLen)); + LOG((" extension = (%u,%d)\n", mExtension.mPos, mExtension.mLen)); + LOG((" query = (%u,%d)\n", mQuery.mPos, mQuery.mLen)); + LOG((" ref = (%u,%d)\n", mRef.mPos, mRef.mLen)); + } + return rv; +} + +NS_IMETHODIMP +nsStandardURL::SetScheme(const nsACString &input) +{ + ENSURE_MUTABLE(); + + const nsPromiseFlatCString &scheme = PromiseFlatCString(input); + + 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() > (uint32_t) net_GetURLMaxLength()) { + return NS_ERROR_MALFORMED_URI; + } + + 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); + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::SetUserPass(const nsACString &input) +{ + ENSURE_MUTABLE(); + + 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() > (uint32_t) net_GetURLMaxLength()) { + return NS_ERROR_MALFORMED_URI; + } + + InvalidateCache(); + + if (userpass.IsEmpty()) { + // remove user:pass + if (mUsername.mLen > 0) { + if (mPassword.mLen > 0) + mUsername.mLen += (mPassword.mLen + 1); + mUsername.mLen++; + mSpec.Cut(mUsername.mPos, mUsername.mLen); + mAuthority.mLen -= mUsername.mLen; + ShiftFromHost(-mUsername.mLen); + mUsername.mLen = -1; + mPassword.mLen = -1; + } + return NS_OK; + } + + 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) { + GET_SEGMENT_ENCODER(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); + } + if (mUsername.mLen < 0) + buf.Append('@'); + } + + uint32_t shift = 0; + + if (mUsername.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 = mUsername.mLen; + if (mPassword.mLen >= 0) + userpassLen += (mPassword.mLen + 1); + mSpec.Replace(mUsername.mPos, userpassLen, buf); + shift = buf.Length() - userpassLen; + } + if (shift) { + ShiftFromHost(shift); + mAuthority.mLen += shift; + } + // update positions and lengths + mUsername.mLen = usernameLen; + mPassword.mLen = passwordLen; + if (passwordLen) + mPassword.mPos = mUsername.mPos + mUsername.mLen + 1; + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::SetUsername(const nsACString &input) +{ + ENSURE_MUTABLE(); + + 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 (username.IsEmpty()) + return SetUserPass(username); + + if (mSpec.Length() + input.Length() - Username().Length() > (uint32_t) net_GetURLMaxLength()) { + return NS_ERROR_MALFORMED_URI; + } + + InvalidateCache(); + + // escape username if necessary + nsAutoCString buf; + GET_SEGMENT_ENCODER(encoder); + const nsACString &escUsername = + encoder.EncodeSegment(username, esc_Username, buf); + + int32_t shift; + + if (mUsername.mLen < 0) { + mUsername.mPos = mAuthority.mPos; + mSpec.Insert(escUsername + NS_LITERAL_CSTRING("@"), mUsername.mPos); + shift = escUsername.Length() + 1; + } + else + shift = ReplaceSegment(mUsername.mPos, mUsername.mLen, escUsername); + + if (shift) { + mUsername.mLen = escUsername.Length(); + mAuthority.mLen += shift; + ShiftFromPassword(shift); + } + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::SetPassword(const nsACString &input) +{ + ENSURE_MUTABLE(); + + const nsPromiseFlatCString &password = PromiseFlatCString(input); + + 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 (mUsername.mLen <= 0) { + NS_WARNING("cannot set password without existing username"); + return NS_ERROR_FAILURE; + } + + if (mSpec.Length() + input.Length() - Password().Length() > (uint32_t) net_GetURLMaxLength()) { + return NS_ERROR_MALFORMED_URI; + } + + InvalidateCache(); + + if (password.IsEmpty()) { + if (mPassword.mLen >= 0) { + // cut(":password") + mSpec.Cut(mPassword.mPos - 1, mPassword.mLen + 1); + ShiftFromHost(-(mPassword.mLen + 1)); + mAuthority.mLen -= (mPassword.mLen + 1); + mPassword.mLen = -1; + } + return NS_OK; + } + + // escape password if necessary + nsAutoCString buf; + GET_SEGMENT_ENCODER(encoder); + const nsACString &escPassword = + encoder.EncodeSegment(password, esc_Password, buf); + + int32_t shift; + + if (mPassword.mLen < 0) { + mPassword.mPos = mUsername.mPos + mUsername.mLen + 1; + mSpec.Insert(NS_LITERAL_CSTRING(":") + escPassword, mPassword.mPos - 1); + shift = escPassword.Length() + 1; + } + else + shift = ReplaceSegment(mPassword.mPos, mPassword.mLen, escPassword); + + if (shift) { + mPassword.mLen = escPassword.Length(); + mAuthority.mLen += shift; + ShiftFromHost(shift); + } + 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!!! +NS_IMETHODIMP +nsStandardURL::SetHostPort(const nsACString &aValue) +{ + ENSURE_MUTABLE(); + + // 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; + } + } + + nsresult rv = SetHost(Substring(start, iter)); + NS_ENSURE_SUCCESS(rv, rv); + + // Also set the port if needed. + if (iter != end) { + iter++; + if (iter != end) { + nsCString portStr(Substring(iter, end)); + nsresult rv; + int32_t port = portStr.ToInteger(&rv); + if (NS_SUCCEEDED(rv)) { + rv = SetPort(port); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // Failure parsing port number + return NS_ERROR_MALFORMED_URI; + } + } else { + // port number is missing + return NS_ERROR_MALFORMED_URI; + } + } + + return NS_OK; +} + +// This function is different than SetHostPort in that the port number will be +// reset as well if aValue parameter does not contain a port port number. +NS_IMETHODIMP +nsStandardURL::SetHostAndPort(const nsACString &aValue) +{ + // Reset the port and than call SetHostPort. SetHostPort does not reset + // the port number. + nsresult rv = SetPort(-1); + NS_ENSURE_SUCCESS(rv, rv); + return SetHostPort(aValue); +} + +NS_IMETHODIMP +nsStandardURL::SetHost(const nsACString &input) +{ + ENSURE_MUTABLE(); + + const nsPromiseFlatCString &hostname = PromiseFlatCString(input); + + nsACString::const_iterator start, end; + hostname.BeginReading(start); + hostname.EndReading(end); + + FindHostLimit(start, end); + + const nsCString unescapedHost(Substring(start, end)); + // Do percent decoding on the the input. + nsAutoCString flat; + NS_UnescapeURL(unescapedHost.BeginReading(), unescapedHost.Length(), + 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; + } else { + 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() > (uint32_t) net_GetURLMaxLength()) { + return NS_ERROR_MALFORMED_URI; + } + + InvalidateCache(); + mHostEncoding = eEncoding_ASCII; + + 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 (NS_SUCCEEDED(NormalizeIPv4(hostBuf, ipString))) { + 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; +} + +NS_IMETHODIMP +nsStandardURL::SetPort(int32_t port) +{ + ENSURE_MUTABLE(); + + 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; + } + + 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) +{ + MOZ_ASSERT(mMutable, "Caller should ensure we're mutable"); + 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"); + + // 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); +} + +NS_IMETHODIMP +nsStandardURL::SetPath(const nsACString &input) +{ + ENSURE_MUTABLE(); + + const nsPromiseFlatCString &path = PromiseFlatCString(input); + LOG(("nsStandardURL::SetPath [path=%s]\n", path.get())); + + InvalidateCache(); + + if (!path.IsEmpty()) { + nsAutoCString spec; + + spec.Assign(mSpec.get(), mPath.mPos); + if (path.First() != '/') + spec.Append('/'); + spec.Append(path); + + return SetSpec(spec); + } + else 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; +} + +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); + NS_PRECONDITION(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) +{ + NS_PRECONDITION(result, "null pointer"); + + *result = SegmentIs(mScheme, scheme); + return NS_OK; +} + +/* virtual */ nsStandardURL* +nsStandardURL::StartClone() +{ + nsStandardURL *clone = new nsStandardURL(); + return clone; +} + +NS_IMETHODIMP +nsStandardURL::Clone(nsIURI **result) +{ + return CloneInternal(eHonorRef, EmptyCString(), result); +} + + +NS_IMETHODIMP +nsStandardURL::CloneIgnoringRef(nsIURI **result) +{ + return CloneInternal(eIgnoreRef, EmptyCString(), result); +} + +NS_IMETHODIMP +nsStandardURL::CloneWithNewRef(const nsACString& newRef, nsIURI **result) +{ + return CloneInternal(eReplaceRef, newRef, result); +} + +nsresult +nsStandardURL::CloneInternal(nsStandardURL::RefHandlingEnum refHandlingMode, + const nsACString& newRef, + nsIURI **result) + +{ + RefPtr<nsStandardURL> clone = StartClone(); + if (!clone) + return NS_ERROR_OUT_OF_MEMORY; + + // Copy local members into clone. + // Also copies the cached members mFile, mHostA + clone->CopyMembers(this, refHandlingMode, newRef, true); + + clone.forget(result); + return NS_OK; +} + +nsresult nsStandardURL::CopyMembers(nsStandardURL * source, + nsStandardURL::RefHandlingEnum refHandlingMode, const nsACString& newRef, + bool copyCached) +{ + 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; + mOriginCharset = source->mOriginCharset; + mURLType = source->mURLType; + mParser = source->mParser; + mMutable = true; + mSupportsFileURL = source->mSupportsFileURL; + mHostEncoding = source->mHostEncoding; + + if (copyCached) { + mFile = source->mFile; + mHostA = source->mHostA ? strdup(source->mHostA) : nullptr; + mSpecEncoding = source->mSpecEncoding; + } else { + // The same state as after calling InvalidateCache() + mFile = nullptr; + mHostA = nullptr; + mSpecEncoding = eEncoding_Unknown; + } + + if (refHandlingMode == eIgnoreRef) { + SetRef(EmptyCString()); + } 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; + + // relative urls should never contain a host, so we always want to use + // the noauth url parser. + // use it to extract a possible scheme + rv = mParser->ParseURL(relpath, + relpathLen, + &scheme.mPos, &scheme.mLen, + 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(); + + nsAutoCString protocol(Segment(scheme)); + nsAutoCString baseProtocol(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)) { + + nsAutoCString::iterator start; + nsAutoCString::iterator end; + buf.BeginWriting(start); + buf.EndWriting(end); + 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 (nsCRT::strncmp(relpath + scheme.mPos + scheme.mLen, + "://",3) == 0) { + // now this is really absolute + // because a :// follows the scheme + result = NS_strdup(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_strdup(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(NS_LITERAL_CSTRING("%2F"), + 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 = PL_strstr(result, "://"); + if (resultPath) { + resultPath = PL_strchr(resultPath + 3, '/'); + 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 + nsStandardURL *stdurl2; + nsresult rv = uri2->QueryInterface(kThisImplCID, (void **) &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) + { + if (NS_SUCCEEDED(rv)) + NS_RELEASE(stdurl2); + 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()); + + NS_RELEASE(stdurl2); + 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; + + nsStandardURL *stdurl2; + nsresult rv = uri2->QueryInterface(kThisImplCID, (void **) &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) + { + if (NS_SUCCEEDED(rv)) + NS_RELEASE(stdurl2); + + 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 != '/')) + { + NS_RELEASE(stdurl2); + 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)); + + NS_RELEASE(stdurl2); + 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; +} + +// 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; +} + +// 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; +} + +NS_IMETHODIMP +nsStandardURL::SetFilePath(const nsACString &input) +{ + ENSURE_MUTABLE(); + + const nsPromiseFlatCString &flat = PromiseFlatCString(input); + const char *filepath = flat.get(); + + LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath)); + + // 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 SetPath(flat); + + if (filepath && *filepath) { + nsAutoCString spec; + uint32_t dirPos, basePos, extPos; + int32_t dirLen, baseLen, extLen; + nsresult rv; + + rv = mParser->ParseFilePath(filepath, flat.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('/'); + + GET_SEGMENT_ENCODER(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 SetSpec(spec); + } + else if (mPath.mLen > 1) { + mSpec.Cut(mPath.mPos + 1, mFilepath.mLen - 1); + // left shift query, and ref + ShiftFromQuery(1 - mFilepath.mLen); + // these contain only a '/' + mPath.mLen = 1; + mDirectory.mLen = 1; + mFilepath.mLen = 1; + // these are no longer defined + mBasename.mLen = -1; + mExtension.mLen = -1; + } + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::SetQuery(const nsACString &input) +{ + ENSURE_MUTABLE(); + + const nsPromiseFlatCString &flat = PromiseFlatCString(input); + const char *query = flat.get(); + + LOG(("nsStandardURL::SetQuery [query=%s]\n", query)); + + if (mPath.mLen < 0) + return SetPath(flat); + + if (mSpec.Length() + input.Length() - Query().Length() > (uint32_t) net_GetURLMaxLength()) { + return NS_ERROR_MALFORMED_URI; + } + + InvalidateCache(); + + if (!query || !*query) { + // 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; + } + + int32_t queryLen = flat.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; + GET_QUERY_ENCODER(encoder); + 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; +} + +NS_IMETHODIMP +nsStandardURL::SetRef(const nsACString &input) +{ + ENSURE_MUTABLE(); + + const nsPromiseFlatCString &flat = PromiseFlatCString(input); + const char *ref = flat.get(); + + LOG(("nsStandardURL::SetRef [ref=%s]\n", ref)); + + if (mPath.mLen < 0) + return SetPath(flat); + + if (mSpec.Length() + input.Length() - Ref().Length() > (uint32_t) net_GetURLMaxLength()) { + return NS_ERROR_MALFORMED_URI; + } + + InvalidateCache(); + + if (!ref || !*ref) { + // 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; + } + + int32_t refLen = flat.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; + if (nsContentUtils::EncodeDecodeURLHash()) { + // encode ref if necessary + bool encoded; + GET_SEGMENT_ENCODER(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; +} + +NS_IMETHODIMP +nsStandardURL::SetDirectory(const nsACString &input) +{ + NS_NOTYETIMPLEMENTED(""); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStandardURL::SetFileName(const nsACString &input) +{ + ENSURE_MUTABLE(); + + const nsPromiseFlatCString &flat = PromiseFlatCString(input); + const char *filename = flat.get(); + + LOG(("nsStandardURL::SetFileName [filename=%s]\n", filename)); + + if (mPath.mLen < 0) + return SetPath(flat); + + if (mSpec.Length() + input.Length() - Filename().Length() > (uint32_t) net_GetURLMaxLength()) { + 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; + URLSegment basename, extension; + + // let the parser locate the basename and extension + rv = mParser->ParseFileName(filename, flat.Length(), + &basename.mPos, &basename.mLen, + &extension.mPos, &extension.mLen); + if (NS_FAILED(rv)) return rv; + + 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; + GET_SEGMENT_ENCODER(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; +} + +NS_IMETHODIMP +nsStandardURL::SetFileBaseName(const nsACString &input) +{ + nsAutoCString extension; + nsresult rv = GetFileExtension(extension); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString newFileName(input); + + if (!extension.IsEmpty()) { + newFileName.Append('.'); + newFileName.Append(extension); + } + + return SetFileName(newFileName); +} + +NS_IMETHODIMP +nsStandardURL::SetFileExtension(const nsACString &input) +{ + nsAutoCString newFileName; + nsresult rv = GetFileBaseName(newFileName); + NS_ENSURE_SUCCESS(rv, rv); + + if (!input.IsEmpty()) { + newFileName.Append('.'); + newFileName.Append(input); + } + + return SetFileName(newFileName); +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsIFileURL +//---------------------------------------------------------------------------- + +nsresult +nsStandardURL::EnsureFile() +{ + NS_PRECONDITION(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) +{ + NS_PRECONDITION(mSupportsFileURL, + "GetFile() called on a URL that doesn't support files!"); + nsresult rv = EnsureFile(); + if (NS_FAILED(rv)) + return rv; + + if (LOG_ENABLED()) { + nsAutoCString path; + mFile->GetNativePath(path); + LOG(("nsStandardURL::GetFile [this=%p spec=%s resulting_path=%s]\n", + this, mSpec.get(), path.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); +} + +NS_IMETHODIMP +nsStandardURL::SetFile(nsIFile *file) +{ + ENSURE_MUTABLE(); + + NS_ENSURE_ARG_POINTER(file); + + nsresult rv; + nsAutoCString url; + + rv = net_GetURLSpecFromFile(file, url); + if (NS_FAILED(rv)) return rv; + + SetSpec(url); + + rv = Init(mURLType, mDefaultPort, url, nullptr, nullptr); + + // must clone |file| since its value is not guaranteed to remain constant + if (NS_SUCCEEDED(rv)) { + 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 rv; +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsIStandardURL +//---------------------------------------------------------------------------- + +inline bool +IsUTFCharset(const char *aCharset) +{ + return ((aCharset[0] == 'U' || aCharset[0] == 'u') && + (aCharset[1] == 'T' || aCharset[1] == 't') && + (aCharset[2] == 'F' || aCharset[2] == 'f')); +} + +NS_IMETHODIMP +nsStandardURL::Init(uint32_t urlType, + int32_t defaultPort, + const nsACString &spec, + const char *charset, + nsIURI *baseURI) +{ + ENSURE_MUTABLE(); + + if (spec.Length() > (uint32_t) net_GetURLMaxLength() || + 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: + NS_NOTREACHED("bad urlType"); + return NS_ERROR_INVALID_ARG; + } + mDefaultPort = defaultPort; + mURLType = urlType; + + mOriginCharset.Truncate(); + + //if charset override is absent, use UTF8 as url encoding + if (charset != nullptr && *charset != '\0' && !IsUTFCharset(charset)) { + mOriginCharset = charset; + } + + if (baseURI && net_IsAbsoluteURL(spec)) { + baseURI = nullptr; + } + + if (!baseURI) + return SetSpec(spec); + + nsAutoCString buf; + nsresult rv = baseURI->Resolve(spec, buf); + if (NS_FAILED(rv)) return rv; + + return SetSpec(buf); +} + +NS_IMETHODIMP +nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort) +{ + ENSURE_MUTABLE(); + + 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; +} + +NS_IMETHODIMP +nsStandardURL::GetMutable(bool *value) +{ + *value = mMutable; + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::SetMutable(bool value) +{ + NS_ENSURE_ARG(mMutable || !value); + + mMutable = value; + return NS_OK; +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsISerializable +//---------------------------------------------------------------------------- + +NS_IMETHODIMP +nsStandardURL::Read(nsIObjectInputStream *stream) +{ + NS_PRECONDITION(!mHostA, "Shouldn't have cached ASCII host"); + NS_PRECONDITION(mSpecEncoding == eEncoding_Unknown, + "Shouldn't have spec encoding here"); + + 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: + NS_NOTREACHED("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; + + rv = NS_ReadOptionalCString(stream, mOriginCharset); + if (NS_FAILED(rv)) return rv; + + bool isMutable; + rv = stream->ReadBoolean(&isMutable); + if (NS_FAILED(rv)) return rv; + mMutable = isMutable; + + bool supportsFileURL; + rv = stream->ReadBoolean(&supportsFileURL); + if (NS_FAILED(rv)) return rv; + mSupportsFileURL = supportsFileURL; + + uint32_t hostEncoding; + rv = stream->Read32(&hostEncoding); + if (NS_FAILED(rv)) return rv; + if (hostEncoding != eEncoding_ASCII && hostEncoding != eEncoding_UTF8) { + NS_WARNING("Unexpected host encoding"); + return NS_ERROR_UNEXPECTED; + } + mHostEncoding = hostEncoding; + + // 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); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::Write(nsIObjectOutputStream *stream) +{ + MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(), + "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; + + rv = NS_WriteOptionalStringZ(stream, mOriginCharset.get()); + if (NS_FAILED(rv)) return rv; + + rv = stream->WriteBoolean(mMutable); + if (NS_FAILED(rv)) return rv; + + rv = stream->WriteBoolean(mSupportsFileURL); + if (NS_FAILED(rv)) return rv; + + rv = stream->Write32(mHostEncoding); + if (NS_FAILED(rv)) return rv; + + // mSpecEncoding and mHostA are just caches that can be recovered as needed. + + return NS_OK; +} + +//--------------------------------------------------------------------------- +// nsStandardURL::nsIIPCSerializableURI +//--------------------------------------------------------------------------- + +inline +ipc::StandardURLSegment +ToIPCSegment(const nsStandardURL::URLSegment& aSegment) +{ + return ipc::StandardURLSegment(aSegment.mPos, aSegment.mLen); +} + +inline +nsStandardURL::URLSegment +FromIPCSegment(const ipc::StandardURLSegment& aSegment) +{ + return nsStandardURL::URLSegment(aSegment.position(), aSegment.length()); +} + +void +nsStandardURL::Serialize(URIParams& aParams) +{ + MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(), + "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.originCharset() = mOriginCharset; + params.isMutable() = !!mMutable; + params.supportsFileURL() = !!mSupportsFileURL; + params.hostEncoding() = mHostEncoding; + // mSpecEncoding and mHostA are just caches that can be recovered as needed. + + aParams = params; +} + +bool +nsStandardURL::Deserialize(const URIParams& aParams) +{ + NS_PRECONDITION(!mHostA, "Shouldn't have cached ASCII host"); + NS_PRECONDITION(mSpecEncoding == eEncoding_Unknown, + "Shouldn't have spec encoding here"); + NS_PRECONDITION(!mFile, "Shouldn't have cached file"); + + if (aParams.type() != URIParams::TStandardURLParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + 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: + NS_NOTREACHED("bad urlType"); + return false; + } + + if (params.hostEncoding() != eEncoding_ASCII && + params.hostEncoding() != eEncoding_UTF8) { + NS_WARNING("Unexpected host encoding"); + return false; + } + + mPort = params.port(); + mDefaultPort = params.defaultPort(); + mSpec = params.spec(); + mScheme = FromIPCSegment(params.scheme()); + mAuthority = FromIPCSegment(params.authority()); + mUsername = FromIPCSegment(params.username()); + mPassword = FromIPCSegment(params.password()); + mHost = FromIPCSegment(params.host()); + mPath = FromIPCSegment(params.path()); + mFilepath = FromIPCSegment(params.filePath()); + mDirectory = FromIPCSegment(params.directory()); + mBasename = FromIPCSegment(params.baseName()); + mExtension = FromIPCSegment(params.extension()); + mQuery = FromIPCSegment(params.query()); + mRef = FromIPCSegment(params.ref()); + mOriginCharset = params.originCharset(); + mMutable = params.isMutable(); + mSupportsFileURL = params.supportsFileURL(); + mHostEncoding = params.hostEncoding(); + + // mSpecEncoding and mHostA are just caches that can be recovered as needed. + return true; +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsIClassInfo +//---------------------------------------------------------------------------- + +NS_IMETHODIMP +nsStandardURL::GetInterfaces(uint32_t *count, nsIID * **array) +{ + *count = 0; + *array = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetScriptableHelper(nsIXPCScriptable **_retval) +{ + *_retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetClassDescription(char * *aClassDescription) +{ + *aClassDescription = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetClassID(nsCID * *aClassID) +{ + *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID)); + if (!*aClassID) + return NS_ERROR_OUT_OF_MEMORY; + return GetClassIDNoAlloc(*aClassID); +} + +NS_IMETHODIMP +nsStandardURL::GetFlags(uint32_t *aFlags) +{ + *aFlags = nsIClassInfo::MAIN_THREAD_ONLY; + return NS_OK; +} + +NS_IMETHODIMP +nsStandardURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) +{ + *aClassIDNoAlloc = kStandardURLCID; + return NS_OK; +} + +//---------------------------------------------------------------------------- +// nsStandardURL::nsISizeOf +//---------------------------------------------------------------------------- + +size_t +nsStandardURL::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + return mSpec.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mOriginCharset.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + aMallocSizeOf(mHostA); + + // 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 diff --git a/netwerk/base/nsStandardURL.h b/netwerk/base/nsStandardURL.h new file mode 100644 index 000000000..90f7f7db2 --- /dev/null +++ b/netwerk/base/nsStandardURL.h @@ -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/. */ + +#ifndef nsStandardURL_h__ +#define nsStandardURL_h__ + +#include "nsString.h" +#include "nsISerializable.h" +#include "nsIFileURL.h" +#include "nsIStandardURL.h" +#include "nsIUnicodeEncoder.h" +#include "nsIObserver.h" +#include "nsCOMPtr.h" +#include "nsURLHelper.h" +#include "nsIClassInfo.h" +#include "nsISizeOf.h" +#include "prclist.h" +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" +#include "nsIIPCSerializableURI.h" +#include "nsISensitiveInfoHiddenURI.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 { +namespace net { + +//----------------------------------------------------------------------------- +// standard URL implementation +//----------------------------------------------------------------------------- + +class nsStandardURL : public nsIFileURL + , public nsIStandardURL + , public nsISerializable + , public nsIClassInfo + , public nsISizeOf + , public nsIIPCSerializableURI + , public nsISensitiveInfoHiddenURI +{ +protected: + virtual ~nsStandardURL(); + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSIURIWITHQUERY + NS_DECL_NSIURL + NS_DECL_NSIFILEURL + NS_DECL_NSISTANDARDURL + NS_DECL_NSISERIALIZABLE + NS_DECL_NSICLASSINFO + NS_DECL_NSIMUTABLE + NS_DECL_NSIIPCSERIALIZABLEURI + NS_DECL_NSISENSITIVEINFOHIDDENURI + + // nsISizeOf + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override; + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override; + + explicit nsStandardURL(bool aSupportsFileURL = false, bool aTrackURL = true); + + 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 + { + uint32_t mPos; + int32_t mLen; + + URLSegment() : mPos(0), mLen(-1) {} + URLSegment(uint32_t pos, int32_t len) : mPos(pos), mLen(len) {} + URLSegment(const URLSegment& aCopy) : mPos(aCopy.mPos), mLen(aCopy.mLen) {} + 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; + } + } + }; + + // + // Pref observer + // + class nsPrefObserver final : public nsIObserver + { + ~nsPrefObserver() {} + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsPrefObserver() { } + }; + friend class nsPrefObserver; + + // + // URL segment encoder : performs charset conversion and URL escaping. + // + class nsSegmentEncoder + { + public: + explicit nsSegmentEncoder(const char *charset); + + // Encode the given segment if necessary, and return the length of + // the encoded segment. The encoded segment is appended to |buf| + // if and only if encoding is required. + int32_t EncodeSegmentCount(const char *str, + const URLSegment &segment, + int16_t mask, + nsAFlatCString &buf, + bool& appended, + uint32_t extraLen = 0); + + // Encode the given string if necessary, and return a reference to + // the encoded string. Returns a reference to |buf| if encoding + // is required. Otherwise, a reference to |str| is returned. + const nsACString &EncodeSegment(const nsASingleFragmentCString &str, + int16_t mask, + nsAFlatCString &buf); + private: + bool InitUnicodeEncoder(); + + const char* mCharset; // Caller should keep this alive for + // the life of the segment encoder + nsCOMPtr<nsIUnicodeEncoder> mEncoder; + }; + friend class nsSegmentEncoder; + +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& newRef, + nsIURI** aClone); + // Helper method that copies member variables from the source StandardURL + // if copyCached = true, it will also copy mFile and mHostA + 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(); + +private: + 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 aLen); + static bool IsValidOfBase(unsigned char c, const uint32_t base); + static nsresult ParseIPv4Number(nsCString &input, uint32_t &number); + static nsresult NormalizeIPv4(const nsCSubstring &host, nsCString &result); + nsresult NormalizeIDN(const nsCSubstring &host, nsCString &result); + 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); + + bool SegmentIs(const URLSegment &s1, const char *val, bool ignoreCase = false); + bool SegmentIs(const char* spec, const URLSegment &s1, const char *val, bool ignoreCase = false); + bool SegmentIs(const URLSegment &s1, const char *val, const URLSegment &s2, 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 + const nsDependentCSubstring Segment(uint32_t pos, int32_t len); // see below + const nsDependentCSubstring Segment(const URLSegment &s) { return Segment(s.mPos, s.mLen); } + + // dependent substring getters + const nsDependentCSubstring Prepath(); // see below + const nsDependentCSubstring Scheme() { return Segment(mScheme); } + const nsDependentCSubstring Userpass(bool includeDelim = false); // see below + const nsDependentCSubstring Username() { return Segment(mUsername); } + const nsDependentCSubstring Password() { return Segment(mPassword); } + const nsDependentCSubstring Hostport(); // see below + const nsDependentCSubstring Host(); // see below + const nsDependentCSubstring Path() { return Segment(mPath); } + const nsDependentCSubstring Filepath() { return Segment(mFilepath); } + const nsDependentCSubstring Directory() { return Segment(mDirectory); } + const nsDependentCSubstring Filename(); // see below + const nsDependentCSubstring Basename() { return Segment(mBasename); } + const nsDependentCSubstring Extension() { return Segment(mExtension); } + const nsDependentCSubstring Query() { return Segment(mQuery); } + const 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 &); + + static void PrefsChanged(nsIPrefBranch *prefs, const char *pref); + + void FindHostLimit(nsACString::const_iterator& aStart, + nsACString::const_iterator& aEnd); + + // mSpec contains the normalized version of the URL spec (UTF-8 encoded). + nsCString mSpec; + int32_t mDefaultPort; + int32_t mPort; + + // 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; + + nsCString mOriginCharset; + nsCOMPtr<nsIURLParser> mParser; + + // mFile is protected so subclasses can access it directly +protected: + nsCOMPtr<nsIFile> mFile; // cached result for nsIFileURL::GetFile + +private: + char *mHostA; // cached result for nsIURI::GetHostA + + enum { + eEncoding_Unknown, + eEncoding_ASCII, + eEncoding_UTF8 + }; + + uint32_t mHostEncoding : 2; // eEncoding_xxx + uint32_t mSpecEncoding : 2; // eEncoding_xxx + uint32_t mURLType : 2; // nsIStandardURL::URLTYPE_xxx + uint32_t mMutable : 1; // nsIStandardURL::mutable + uint32_t mSupportsFileURL : 1; // QI to nsIFileURL? + + // global objects. don't use COMPtr as its destructor will cause a + // coredump if we leak it. + static nsIIDNService *gIDN; + static char gHostLimitDigits[]; + static bool gInitialized; + static bool gEscapeUTF8; + static bool gAlwaysEncodeInUTF8; + static bool gEncodeQueryInUTF8; + +public: +#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN + PRCList mDebugCList; + void PrintSpec() const { printf(" %s\n", mSpec.get()); } +#endif +}; + +#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 const nsDependentCSubstring +nsStandardURL::Segment(uint32_t pos, int32_t len) +{ + if (len < 0) { + pos = 0; + len = 0; + } + return Substring(mSpec, pos, uint32_t(len)); +} + +inline const nsDependentCSubstring +nsStandardURL::Prepath() +{ + uint32_t len = 0; + if (mAuthority.mLen >= 0) + len = mAuthority.mPos + mAuthority.mLen; + return Substring(mSpec, 0, len); +} + +inline const nsDependentCSubstring +nsStandardURL::Userpass(bool includeDelim) +{ + uint32_t pos=0, len=0; + // if there is no username, then there can be no password + if (mUsername.mLen > 0) { + pos = mUsername.mPos; + len = mUsername.mLen; + if (mPassword.mLen >= 0) + len += (mPassword.mLen + 1); + if (includeDelim) + len++; + } + return Substring(mSpec, pos, len); +} + +inline const 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 const 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 const 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 000000000..d88370b2b --- /dev/null +++ b/netwerk/base/nsStreamListenerTee.cpp @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(nsStreamListenerTee, + nsIStreamListener, + nsIRequestObserver, + nsIStreamListenerTee, + nsIThreadRetargetableStreamListener) + +NS_IMETHODIMP +nsStreamListenerTee::OnStartRequest(nsIRequest *request, + nsISupports *context) +{ + NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED); + nsresult rv1 = mListener->OnStartRequest(request, context); + nsresult rv2 = NS_OK; + if (mObserver) + rv2 = mObserver->OnStartRequest(request, context); + + // 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, + nsISupports *context, + 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; + } + + // release sink on the same thread where the data was written (bug 716293) + if (mEventTarget) { + NS_ProxyRelease(mEventTarget, mSink.forget()); + } + else { + mSink = nullptr; + } + + nsresult rv = mListener->OnStopRequest(request, context, status); + if (mObserver) + mObserver->OnStopRequest(request, context, status); + mObserver = nullptr; + return rv; +} + +NS_IMETHODIMP +nsStreamListenerTee::OnDataAvailable(nsIRequest *request, + nsISupports *context, + 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 = do_QueryInterface(mInputTee, &rv); + if (NS_FAILED(rv)) return rv; + } + + return mListener->OnDataAvailable(request, context, tee, offset, count); +} + +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::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 000000000..d6a6f23a6 --- /dev/null +++ b/netwerk/base/nsStreamListenerTee.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 nsStreamListenerTee_h__ +#define nsStreamListenerTee_h__ + +#include "nsIStreamListenerTee.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsIInputStreamTee.h" +#include "nsIOutputStream.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" + +namespace mozilla { +namespace net { + +class nsStreamListenerTee : public nsIStreamListenerTee + , public nsIThreadRetargetableStreamListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + NS_DECL_NSISTREAMLISTENERTEE + + nsStreamListenerTee() { } + +private: + virtual ~nsStreamListenerTee() { } + + nsCOMPtr<nsIInputStreamTee> mInputTee; + nsCOMPtr<nsIOutputStream> mSink; + nsCOMPtr<nsIStreamListener> mListener; + nsCOMPtr<nsIRequestObserver> mObserver; + nsCOMPtr<nsIEventTarget> mEventTarget; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/base/nsStreamListenerWrapper.cpp b/netwerk/base/nsStreamListenerWrapper.cpp new file mode 100644 index 000000000..9273e4558 --- /dev/null +++ b/netwerk/base/nsStreamListenerWrapper.cpp @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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, + nsIThreadRetargetableStreamListener) + +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; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsStreamListenerWrapper.h b/netwerk/base/nsStreamListenerWrapper.h new file mode 100644 index 000000000..36bb93dac --- /dev/null +++ b/netwerk/base/nsStreamListenerWrapper.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 nsStreamListenerWrapper_h__ +#define nsStreamListenerWrapper_h__ + +#include "nsCOMPtr.h" +#include "nsIStreamListener.h" +#include "nsIRequestObserver.h" +#include "nsIThreadRetargetableStreamListener.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 nsIStreamListener + , public nsIThreadRetargetableStreamListener +{ +public: + explicit nsStreamListenerWrapper(nsIStreamListener *listener) + : mListener(listener) + { + MOZ_ASSERT(mListener, "no stream listener specified"); + } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_SAFE_NSIREQUESTOBSERVER(mListener) + NS_FORWARD_SAFE_NSISTREAMLISTENER(mListener) + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + +private: + ~nsStreamListenerWrapper() {} + 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 000000000..a73b038a7 --- /dev/null +++ b/netwerk/base/nsStreamLoader.cpp @@ -0,0 +1,169 @@ +/* -*- 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 "GeckoProfiler.h" + +#include <limits> + +namespace mozilla { +namespace net { + +nsStreamLoader::nsStreamLoader() + : mData() +{ +} + +nsStreamLoader::~nsStreamLoader() +{ +} + +NS_IMETHODIMP +nsStreamLoader::Init(nsIStreamLoaderObserver* aStreamObserver, + nsIRequestObserver* aRequestObserver) +{ + NS_ENSURE_ARG_POINTER(aStreamObserver); + mObserver = aStreamObserver; + mRequestObserver = aRequestObserver; + return NS_OK; +} + +nsresult +nsStreamLoader::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + if (aOuter) return NS_ERROR_NO_AGGREGATION; + + nsStreamLoader* it = new nsStreamLoader(); + if (it == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(it); + nsresult rv = it->QueryInterface(aIID, aResult); + NS_RELEASE(it); + return rv; +} + +NS_IMPL_ISUPPORTS(nsStreamLoader, nsIStreamLoader, + nsIRequestObserver, nsIStreamListener, + nsIThreadRetargetableStreamListener) + +NS_IMETHODIMP +nsStreamLoader::GetNumBytesRead(uint32_t* aNumBytes) +{ + *aNumBytes = mData.length(); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamLoader::GetRequest(nsIRequest **aRequest) +{ + NS_IF_ADDREF(*aRequest = mRequest); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamLoader::OnStartRequest(nsIRequest* request, nsISupports *ctxt) +{ + nsCOMPtr<nsIChannel> chan( do_QueryInterface(request) ); + if (chan) { + int64_t contentLength = -1; + chan->GetContentLength(&contentLength); + if (contentLength >= 0) { + if (uint64_t(contentLength) > std::numeric_limits<size_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; + } + } + } + mContext = ctxt; + if (mRequestObserver) { + mRequestObserver->OnStartRequest(request, ctxt); + } + return NS_OK; +} + +NS_IMETHODIMP +nsStreamLoader::OnStopRequest(nsIRequest* request, nsISupports *ctxt, + nsresult aStatus) +{ + PROFILER_LABEL("nsStreamLoader", "OnStopRequest", + js::ProfileEntry::Category::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; + mContext = nullptr; + } + + if (mRequestObserver) { + mRequestObserver->OnStopRequest(request, ctxt, 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, nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, uint32_t count) +{ + uint32_t countRead; + return inStr->ReadSegments(WriteSegmentFun, this, count, &countRead); +} + +void +nsStreamLoader::ReleaseData() +{ + mData.clearAndFree(); +} + +NS_IMETHODIMP +nsStreamLoader::CheckListenerChain() +{ + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsStreamLoader.h b/netwerk/base/nsStreamLoader.h new file mode 100644 index 000000000..671fc441f --- /dev/null +++ b/netwerk/base/nsStreamLoader.h @@ -0,0 +1,58 @@ +/* -*- 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 nsIThreadRetargetableStreamListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTREAMLOADER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + + nsStreamLoader(); + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + ~nsStreamLoader(); + + 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; + + // 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 000000000..3461480b6 --- /dev/null +++ b/netwerk/base/nsStreamTransportService.cpp @@ -0,0 +1,563 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsXPCOMCIDInternal.h" +#include "nsNetSegmentUtils.h" +#include "nsTransportUtils.h" +#include "nsStreamUtils.h" +#include "nsError.h" +#include "nsNetCID.h" + +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsISeekableStream.h" +#include "nsIPipe.h" +#include "nsITransport.h" +#include "nsIObserverService.h" +#include "nsIThreadPool.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 nsIInputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORT + NS_DECL_NSIINPUTSTREAM + + nsInputStreamTransport(nsIInputStream *source, + uint64_t offset, + uint64_t limit, + bool closeWhenDone) + : mSource(source) + , mOffset(offset) + , mLimit(limit) + , mCloseWhenDone(closeWhenDone) + , mFirstTime(true) + , mInProgress(false) + { + } + +private: + virtual ~nsInputStreamTransport() + { + } + + nsCOMPtr<nsIAsyncInputStream> mPipeIn; + + // while the copy is active, these members may only be accessed from the + // nsIInputStream implementation. + nsCOMPtr<nsITransportEventSink> mEventSink; + nsCOMPtr<nsIInputStream> mSource; + int64_t mOffset; + int64_t mLimit; + bool mCloseWhenDone; + bool mFirstTime; + + // this variable serves as a lock to prevent the state of the transport + // from being modified once the copy is in progress. + bool mInProgress; +}; + +NS_IMPL_ISUPPORTS(nsInputStreamTransport, + nsITransport, + nsIInputStream) + +/** 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; + rv = NS_NewPipe2(getter_AddRefs(mPipeIn), + getter_AddRefs(pipeOut), + nonblocking, true, + segsize, segcount); + if (NS_FAILED(rv)) return rv; + + mInProgress = true; + + // startup async copy process... + rv = NS_AsyncCopy(this, pipeOut, target, + NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize); + if (NS_SUCCEEDED(rv)) + NS_ADDREF(*result = mPipeIn); + + return rv; +} + +NS_IMETHODIMP +nsInputStreamTransport::OpenOutputStream(uint32_t flags, + uint32_t segsize, + uint32_t segcount, + nsIOutputStream **result) +{ + // this transport only supports reading! + NS_NOTREACHED("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 = mLimit = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTransport::Available(uint64_t *result) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsInputStreamTransport::Read(char *buf, uint32_t count, uint32_t *result) +{ + if (mFirstTime) { + mFirstTime = false; + if (mOffset != 0) { + // read from current position if offset equal to max + if (mOffset != -1) { + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mSource); + if (seekable) + seekable->Seek(nsISeekableStream::NS_SEEK_SET, mOffset); + } + // reset offset to zero so we can use it to enforce limit + mOffset = 0; + } + } + + // limit amount read + uint64_t max = count; + if (mLimit != -1) { + max = mLimit - mOffset; + if (max == 0) { + *result = 0; + return NS_OK; + } + } + + if (count > max) + count = static_cast<uint32_t>(max); + + nsresult rv = mSource->Read(buf, count, result); + + if (NS_SUCCEEDED(rv)) { + mOffset += *result; + if (mEventSink) + mEventSink->OnTransportStatus(this, NS_NET_STATUS_READING, mOffset, + mLimit); + } + 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; +} + +//----------------------------------------------------------------------------- +// nsOutputStreamTransport +// +// Implements nsIOutputStream 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 nsOutputStreamTransport : public nsITransport + , public nsIOutputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORT + NS_DECL_NSIOUTPUTSTREAM + + nsOutputStreamTransport(nsIOutputStream *sink, + int64_t offset, + int64_t limit, + bool closeWhenDone) + : mSink(sink) + , mOffset(offset) + , mLimit(limit) + , mCloseWhenDone(closeWhenDone) + , mFirstTime(true) + , mInProgress(false) + { + } + +private: + virtual ~nsOutputStreamTransport() + { + } + + nsCOMPtr<nsIAsyncOutputStream> mPipeOut; + + // while the copy is active, these members may only be accessed from the + // nsIOutputStream implementation. + nsCOMPtr<nsITransportEventSink> mEventSink; + nsCOMPtr<nsIOutputStream> mSink; + int64_t mOffset; + int64_t mLimit; + bool mCloseWhenDone; + bool mFirstTime; + + // this variable serves as a lock to prevent the state of the transport + // from being modified once the copy is in progress. + bool mInProgress; +}; + +NS_IMPL_ISUPPORTS(nsOutputStreamTransport, + nsITransport, + nsIOutputStream) + +/** nsITransport **/ + +NS_IMETHODIMP +nsOutputStreamTransport::OpenInputStream(uint32_t flags, + uint32_t segsize, + uint32_t segcount, + nsIInputStream **result) +{ + // this transport only supports writing! + NS_NOTREACHED("nsOutputStreamTransport::OpenInputStream"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsOutputStreamTransport::OpenOutputStream(uint32_t flags, + uint32_t segsize, + uint32_t segcount, + nsIOutputStream **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 mSink; however, then we would + // not be writing to mSink on a background thread. is this ok? + + bool nonblocking = !(flags & OPEN_BLOCKING); + + net_ResolveSegmentParams(segsize, segcount); + + nsCOMPtr<nsIAsyncInputStream> pipeIn; + rv = NS_NewPipe2(getter_AddRefs(pipeIn), + getter_AddRefs(mPipeOut), + true, nonblocking, + segsize, segcount); + if (NS_FAILED(rv)) return rv; + + mInProgress = true; + + // startup async copy process... + rv = NS_AsyncCopy(pipeIn, this, target, + NS_ASYNCCOPY_VIA_READSEGMENTS, segsize); + if (NS_SUCCEEDED(rv)) + NS_ADDREF(*result = mPipeOut); + + return rv; +} + +NS_IMETHODIMP +nsOutputStreamTransport::Close(nsresult reason) +{ + if (NS_SUCCEEDED(reason)) + reason = NS_BASE_STREAM_CLOSED; + + return mPipeOut->CloseWithStatus(reason); +} + +NS_IMETHODIMP +nsOutputStreamTransport::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; +} + +/** nsIOutputStream **/ + +NS_IMETHODIMP +nsOutputStreamTransport::Close() +{ + if (mCloseWhenDone) + mSink->Close(); + + // make additional writes return early... + mOffset = mLimit = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsOutputStreamTransport::Flush() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsOutputStreamTransport::Write(const char *buf, uint32_t count, uint32_t *result) +{ + if (mFirstTime) { + mFirstTime = false; + if (mOffset != 0) { + // write to current position if offset equal to max + if (mOffset != -1) { + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mSink); + if (seekable) + seekable->Seek(nsISeekableStream::NS_SEEK_SET, mOffset); + } + // reset offset to zero so we can use it to enforce limit + mOffset = 0; + } + } + + // limit amount written + uint64_t max = count; + if (mLimit != -1) { + max = mLimit - mOffset; + if (max == 0) { + *result = 0; + return NS_OK; + } + } + + if (count > max) + count = static_cast<uint32_t>(max); + + nsresult rv = mSink->Write(buf, count, result); + + if (NS_SUCCEEDED(rv)) { + mOffset += *result; + if (mEventSink) + mEventSink->OnTransportStatus(this, NS_NET_STATUS_WRITING, mOffset, + mLimit); + } + return rv; +} + +NS_IMETHODIMP +nsOutputStreamTransport::WriteSegments(nsReadSegmentFun reader, void *closure, + uint32_t count, uint32_t *result) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsOutputStreamTransport::WriteFrom(nsIInputStream *in, uint32_t count, uint32_t *result) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsOutputStreamTransport::IsNonBlocking(bool *result) +{ + *result = false; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsStreamTransportService +//----------------------------------------------------------------------------- + +nsStreamTransportService::~nsStreamTransportService() +{ + NS_ASSERTION(!mPool, "thread pool wasn't shutdown"); +} + +nsresult +nsStreamTransportService::Init() +{ + mPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID); + NS_ENSURE_STATE(mPool); + + // Configure the pool + mPool->SetName(NS_LITERAL_CSTRING("StreamTrans")); + mPool->SetThreadLimit(25); + mPool->SetIdleThreadLimit(1); + mPool->SetIdleThreadTimeout(PR_SecondsToInterval(30)); + + 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>, uint32_t) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +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, + int64_t offset, + int64_t limit, + bool closeWhenDone, + nsITransport **result) +{ + nsInputStreamTransport *trans = + new nsInputStreamTransport(stream, offset, limit, closeWhenDone); + if (!trans) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*result = trans); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamTransportService::CreateOutputTransport(nsIOutputStream *stream, + int64_t offset, + int64_t limit, + bool closeWhenDone, + nsITransport **result) +{ + nsOutputStreamTransport *trans = + new nsOutputStreamTransport(stream, offset, limit, closeWhenDone); + if (!trans) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*result = trans); + 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"); + + { + mozilla::MutexAutoLock lock(mShutdownLock); + mIsShutdown = true; + } + + if (mPool) { + mPool->Shutdown(); + mPool = nullptr; + } + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/nsStreamTransportService.h b/netwerk/base/nsStreamTransportService.h new file mode 100644 index 000000000..7ffd1d1b9 --- /dev/null +++ b/netwerk/base/nsStreamTransportService.h @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsThreadUtils.h" +#include "mozilla/Attributes.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 + NS_DECL_NSIOBSERVER + using nsIEventTarget::Dispatch; + + nsresult Init(); + + nsStreamTransportService() : mShutdownLock("nsStreamTransportService.mShutdownLock"), + mIsShutdown(false) {} + +private: + ~nsStreamTransportService(); + + nsCOMPtr<nsIThreadPool> mPool; + + mozilla::Mutex mShutdownLock; + bool mIsShutdown; +}; + +} // namespace net +} // namespace mozilla +#endif diff --git a/netwerk/base/nsSyncStreamListener.cpp b/netwerk/base/nsSyncStreamListener.cpp new file mode 100644 index 000000000..e80e885c5 --- /dev/null +++ b/netwerk/base/nsSyncStreamListener.cpp @@ -0,0 +1,181 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsSyncStreamListener.h" +#include "nsIPipe.h" +#include "nsThreadUtils.h" +#include <algorithm> + +nsresult +nsSyncStreamListener::Init() +{ + return NS_NewPipe(getter_AddRefs(mPipeIn), + getter_AddRefs(mPipeOut), + nsIOService::gDefaultSegmentSize, + UINT32_MAX, // no size limit + false, + false); +} + +nsresult +nsSyncStreamListener::WaitForData() +{ + mKeepWaiting = true; + + while (mKeepWaiting) + NS_ENSURE_STATE(NS_ProcessNextEvent(NS_GetCurrentThread())); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsSyncStreamListener::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsSyncStreamListener, + nsIStreamListener, + nsIRequestObserver, + nsIInputStream, + nsISyncStreamListener) + +//----------------------------------------------------------------------------- +// nsSyncStreamListener::nsISyncStreamListener +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsSyncStreamListener::GetInputStream(nsIInputStream **result) +{ + NS_ADDREF(*result = this); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsSyncStreamListener::nsIStreamListener +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsSyncStreamListener::OnStartRequest(nsIRequest *request, + nsISupports *context) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSyncStreamListener::OnDataAvailable(nsIRequest *request, + nsISupports *context, + 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, + nsISupports *context, + 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) { + mStatus = WaitForData(); + if (NS_SUCCEEDED(mStatus)) + mStatus = mPipeIn->Available(result); + } + 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 000000000..9fa9c58c3 --- /dev/null +++ b/netwerk/base/nsSyncStreamListener.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 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 + + nsSyncStreamListener() + : mStatus(NS_OK) + , mKeepWaiting(false) + , mDone(false) {} + + nsresult Init(); + +private: + ~nsSyncStreamListener() {} + + nsresult WaitForData(); + + nsCOMPtr<nsIInputStream> mPipeIn; + nsCOMPtr<nsIOutputStream> mPipeOut; + nsresult mStatus; + bool mKeepWaiting; + bool mDone; +}; + +#endif // nsSyncStreamListener_h__ diff --git a/netwerk/base/nsTemporaryFileInputStream.cpp b/netwerk/base/nsTemporaryFileInputStream.cpp new file mode 100644 index 000000000..c7c5b0648 --- /dev/null +++ b/netwerk/base/nsTemporaryFileInputStream.cpp @@ -0,0 +1,252 @@ +/* -*- 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 "nsTemporaryFileInputStream.h" +#include "nsStreamUtils.h" +#include <algorithm> + +typedef mozilla::ipc::FileDescriptor::PlatformHandleType FileHandleType; + +NS_IMPL_ISUPPORTS(nsTemporaryFileInputStream, + nsIInputStream, + nsISeekableStream, + nsIIPCSerializableInputStream) + +nsTemporaryFileInputStream::nsTemporaryFileInputStream(FileDescOwner* aFileDescOwner, uint64_t aStartPos, uint64_t aEndPos) + : mFileDescOwner(aFileDescOwner), + mStartPos(aStartPos), + mCurPos(aStartPos), + mEndPos(aEndPos), + mClosed(false) +{ + NS_ASSERTION(aStartPos <= aEndPos, "StartPos should less equal than EndPos!"); +} + +nsTemporaryFileInputStream::nsTemporaryFileInputStream() + : mStartPos(0), + mCurPos(0), + mEndPos(0), + mClosed(false) +{ +} + +NS_IMETHODIMP +nsTemporaryFileInputStream::Close() +{ + mClosed = true; + return NS_OK; +} + +NS_IMETHODIMP +nsTemporaryFileInputStream::Available(uint64_t * bytesAvailable) +{ + if (mClosed) + return NS_BASE_STREAM_CLOSED; + + NS_ASSERTION(mCurPos <= mEndPos, "CurPos should less equal than EndPos!"); + + *bytesAvailable = mEndPos - mCurPos; + return NS_OK; +} + +NS_IMETHODIMP +nsTemporaryFileInputStream::Read(char* buffer, uint32_t count, uint32_t* bytesRead) +{ + return ReadSegments(NS_CopySegmentToBuffer, buffer, count, bytesRead); +} + +NS_IMETHODIMP +nsTemporaryFileInputStream::ReadSegments(nsWriteSegmentFun writer, + void * closure, + uint32_t count, + uint32_t * result) +{ + NS_ASSERTION(result, "null ptr"); + NS_ASSERTION(mCurPos <= mEndPos, "bad stream state"); + *result = 0; + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + mozilla::MutexAutoLock lock(mFileDescOwner->FileMutex()); + int64_t offset = PR_Seek64(mFileDescOwner->mFD, mCurPos, PR_SEEK_SET); + if (offset == -1) { + return NS_ErrorAccordingToNSPR(); + } + + // Limit requested count to the amount remaining in our section of the file. + count = std::min(count, uint32_t(mEndPos - mCurPos)); + + char buf[4096]; + while (*result < count) { + uint32_t bufCount = std::min(count - *result, (uint32_t) sizeof(buf)); + int32_t bytesRead = PR_Read(mFileDescOwner->mFD, buf, bufCount); + if (bytesRead == 0) { + mClosed = true; + return NS_OK; + } + + if (bytesRead < 0) { + return NS_ErrorAccordingToNSPR(); + } + + int32_t bytesWritten = 0; + while (bytesWritten < bytesRead) { + uint32_t writerCount = 0; + nsresult rv = writer(this, closure, buf + bytesWritten, *result, + bytesRead - bytesWritten, &writerCount); + if (NS_FAILED(rv) || writerCount == 0) { + // nsIInputStream::ReadSegments' contract specifies that errors + // from writer are not propagated to ReadSegments' caller. + // + // If writer fails, leaving bytes still in buf, that's okay: we + // only update mCurPos to reflect successful writes, so the call + // to PR_Seek64 at the top will restart us at the right spot. + return NS_OK; + } + NS_ASSERTION(writerCount <= (uint32_t) (bytesRead - bytesWritten), + "writer should not write more than we asked it to write"); + bytesWritten += writerCount; + *result += writerCount; + mCurPos += writerCount; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTemporaryFileInputStream::IsNonBlocking(bool * nonBlocking) +{ + *nonBlocking = false; + return NS_OK; +} + +NS_IMETHODIMP +nsTemporaryFileInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + switch (aWhence) { + case nsISeekableStream::NS_SEEK_SET: + aOffset += mStartPos; + break; + + case nsISeekableStream::NS_SEEK_CUR: + aOffset += mCurPos; + break; + + case nsISeekableStream::NS_SEEK_END: + aOffset += mEndPos; + break; + + default: + return NS_ERROR_FAILURE; + } + + if (aOffset < (int64_t)mStartPos || aOffset > (int64_t)mEndPos) { + return NS_ERROR_INVALID_ARG; + } + + mCurPos = aOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsTemporaryFileInputStream::Tell(int64_t* aPos) +{ + if (!aPos) { + return NS_ERROR_FAILURE; + } + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mStartPos <= mCurPos, "StartPos should less equal than CurPos!"); + *aPos = mCurPos - mStartPos; + return NS_OK; +} + +NS_IMETHODIMP +nsTemporaryFileInputStream::SetEOF() +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + return Close(); +} + +void +nsTemporaryFileInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + TemporaryFileInputStreamParams params; + + MutexAutoLock lock(mFileDescOwner->FileMutex()); + MOZ_ASSERT(mFileDescOwner->mFD); + if (!mClosed) { + FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFileDescOwner->mFD)); + NS_ASSERTION(fd, "This should never be null!"); + + DebugOnly<FileDescriptor*> dbgFD = aFileDescriptors.AppendElement(fd); + NS_ASSERTION(dbgFD->IsValid(), "Sending an invalid file descriptor!"); + + params.fileDescriptorIndex() = aFileDescriptors.Length() - 1; + + Close(); + } else { + NS_WARNING("The stream is already closed. " + "Sending an invalid file descriptor to the other process!"); + + params.fileDescriptorIndex() = UINT32_MAX; + } + params.startPos() = mCurPos; + params.endPos() = mEndPos; + aParams = params; +} + +bool +nsTemporaryFileInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) +{ + const TemporaryFileInputStreamParams& params = aParams.get_TemporaryFileInputStreamParams(); + + uint32_t fileDescriptorIndex = params.fileDescriptorIndex(); + FileDescriptor fd; + if (fileDescriptorIndex < aFileDescriptors.Length()) { + fd = aFileDescriptors[fileDescriptorIndex]; + NS_WARNING_ASSERTION(fd.IsValid(), + "Received an invalid file descriptor!"); + } else { + NS_WARNING("Received a bad file descriptor index!"); + } + + 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; + } + mFileDescOwner = new FileDescOwner(fileDesc); + } else { + mClosed = true; + } + + mStartPos = mCurPos = params.startPos(); + mEndPos = params.endPos(); + return true; +} + +Maybe<uint64_t> +nsTemporaryFileInputStream::ExpectedSerializedLength() +{ + return Nothing(); +} diff --git a/netwerk/base/nsTemporaryFileInputStream.h b/netwerk/base/nsTemporaryFileInputStream.h new file mode 100644 index 000000000..950b26c29 --- /dev/null +++ b/netwerk/base/nsTemporaryFileInputStream.h @@ -0,0 +1,64 @@ +/* -*- 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/. */ + +#ifndef nsTemporaryFileInputStream_h__ +#define nsTemporaryFileInputStream_h__ + +#include "mozilla/Mutex.h" +#include "nsAutoPtr.h" +#include "nsIInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" +#include "prio.h" + +class nsTemporaryFileInputStream : public nsIInputStream + , public nsISeekableStream + , public nsIIPCSerializableInputStream +{ +public: + //used to release a PRFileDesc + class FileDescOwner + { + friend class nsTemporaryFileInputStream; + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileDescOwner) + explicit FileDescOwner(PRFileDesc* aFD) + : mFD(aFD), + mMutex("FileDescOwner::mMutex") + { + MOZ_ASSERT(aFD); + } + private: + ~FileDescOwner() + { + PR_Close(mFD); + } + public: + mozilla::Mutex& FileMutex() { return mMutex; } + + private: + PRFileDesc* mFD; + mozilla::Mutex mMutex; + }; + + nsTemporaryFileInputStream(FileDescOwner* aFileDescOwner, uint64_t aStartPos, uint64_t aEndPos); + nsTemporaryFileInputStream(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + +private: + virtual ~nsTemporaryFileInputStream() { } + + RefPtr<FileDescOwner> mFileDescOwner; + uint64_t mStartPos; + uint64_t mCurPos; + uint64_t mEndPos; + bool mClosed; +}; + +#endif // nsTemporaryFileInputStream_h__ diff --git a/netwerk/base/nsTransportUtils.cpp b/netwerk/base/nsTransportUtils.cpp new file mode 100644 index 000000000..e29bbfdab --- /dev/null +++ b/netwerk/base/nsTransportUtils.cpp @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsTransportUtils.h" +#include "nsITransport.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.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(mTarget, dont_AddRef(mSink)); + } + +public: + nsITransportEventSink *mSink; + nsCOMPtr<nsIEventTarget> mTarget; + Mutex mLock; + nsTransportStatusEvent *mLastEvent; +}; + +class nsTransportStatusEvent : public Runnable +{ +public: + nsTransportStatusEvent(nsTransportEventSinkProxy *proxy, + nsITransport *transport, + nsresult status, + int64_t progress, + int64_t progressMax) + : mProxy(proxy) + , mTransport(transport) + , mStatus(status) + , mProgress(progress) + , mProgressMax(progressMax) + {} + + ~nsTransportStatusEvent() {} + + 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) +{ + *result = new nsTransportEventSinkProxy(sink, target); + if (!*result) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*result); + return NS_OK; +} diff --git a/netwerk/base/nsTransportUtils.h b/netwerk/base/nsTransportUtils.h new file mode 100644 index 000000000..4256bcad9 --- /dev/null +++ b/netwerk/base/nsTransportUtils.h @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 000000000..84f6b8ea5 --- /dev/null +++ b/netwerk/base/nsUDPSocket.cpp @@ -0,0 +1,1613 @@ +/* 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 "nsSocketTransport2.h" +#include "nsUDPSocket.h" +#include "nsProxyRelease.h" +#include "nsAutoPtr.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 "NetworkActivityMonitor.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "nsIPipe.h" +#include "prerror.h" +#include "nsThreadUtils.h" +#include "nsIDNSRecord.h" +#include "nsIDNSService.h" +#include "nsICancelable.h" + +#ifdef MOZ_WIDGET_GONK +#include "NetStatistics.h" +#endif + +namespace mozilla { +namespace net { + +static const uint32_t UDP_PACKET_CHUNK_SIZE = 1400; +static NS_DEFINE_CID(kSocketTransportServiceCID2, NS_SOCKETTRANSPORTSERVICE_CID); + +//----------------------------------------------------------------------------- + +typedef void (nsUDPSocket:: *nsUDPSocketFunc)(void); + +static nsresult +PostEvent(nsUDPSocket *s, nsUDPSocketFunc func) +{ + if (!gSocketTransportService) + return NS_ERROR_FAILURE; + + return gSocketTransportService->Dispatch(NewRunnableMethod(s, func), NS_DISPATCH_NORMAL); +} + +static nsresult +ResolveHost(const nsACString &host, 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->AsyncResolve(host, 0, listener, nullptr, + getter_AddRefs(tmpOutstanding)); + +} + +//----------------------------------------------------------------------------- + +class SetSocketOptionRunnable : public Runnable +{ +public: + SetSocketOptionRunnable(nsUDPSocket* aSocket, const PRSocketOptionData& aOpt) + : 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) +{ +} + +nsUDPOutputStream::~nsUDPOutputStream() +{ +} + +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::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_SCRIPT_OBJECTS +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) +{ + memcpy(&mAddr, aAddr, sizeof(NetAddr)); + aData.SwapElements(mData); +} + +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); + NS_IF_ADDREF(*aOutputStream = mOutputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsUDPMessage::GetRawData(JSContext* cx, + JS::MutableHandleValue aRawData) +{ + if(!mJsobj){ + mJsobj = dom::Uint8Array::Create(cx, nullptr, mData.Length(), mData.Elements()); + HoldJSObjects(this); + } + aRawData.setObject(*mJsobj); + return NS_OK; +} + +FallibleTArray<uint8_t>& +nsUDPMessage::GetDataAsTArray() +{ + return mData; +} + +//----------------------------------------------------------------------------- +// nsUDPSocket +//----------------------------------------------------------------------------- + +nsUDPSocket::nsUDPSocket() + : mLock("nsUDPSocket.mLock") + , mFD(nullptr) + , mAppId(NECKO_UNKNOWN_APP_ID) + , mIsInIsolatedMozBrowserElement(false) + , mAttached(false) + , mByteReadCount(0) + , mByteWriteCount(0) +{ + mAddr.raw.family = PR_AF_UNSPEC; + // 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(kSocketTransportServiceCID2); + } + + mSts = gSocketTransportService; + MOZ_COUNT_CTOR(nsUDPSocket); +} + +nsUDPSocket::~nsUDPSocket() +{ + CloseSocket(); + MOZ_COUNT_DTOR(nsUDPSocket); +} + +void +nsUDPSocket::AddOutputBytes(uint64_t aBytes) +{ + mByteWriteCount += aBytes; + SaveNetworkStats(false); +} + +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)) + { + NS_ASSERTION(!mAttached, "should not be attached already"); + OnSocketDetached(mFD); + } +} + +nsresult +nsUDPSocket::TryAttach() +{ + nsresult rv; + + if (!gSocketTransportService) + return NS_ERROR_FAILURE; + + if (gIOService->IsNetTearingDown()) { + 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(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) + { + memcpy(&mAddr, aAddr, sizeof(mAddr)); + aData.SwapElements(mData); + } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUDPMESSAGE + +private: + ~UDPMessageProxy() {} + + 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::MutableHandleValue aRawData) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +UDPMessageProxy::GetOutputStream(nsIOutputStream * *aOutputStream) +{ + NS_ENSURE_ARG_POINTER(aOutputStream); + NS_IF_ADDREF(*aOutputStream = mOutputStream); + return NS_OK; +} + +} //anonymous namespace + +//----------------------------------------------------------------------------- +// nsUDPSocket::nsASocketHandler +//----------------------------------------------------------------------------- + +void +nsUDPSocket::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; + } + + PRNetAddr prClientAddr; + uint32_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 < 1) { + NS_WARNING("error of recvfrom on UDP socket"); + mCondition = NS_ERROR_UNEXPECTED; + return; + } + mByteReadCount += count; + SaveNetworkStats(false); + + FallibleTArray<uint8_t> data; + if (!data.AppendElements(buff, count, fallible)) { + 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); + nsresult rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), + true, true, segsize, segcount); + + if (NS_FAILED(rv)) { + return; + } + + RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prClientAddr); + rv = NS_AsyncCopy(pipeIn, os, mSts, + NS_ASYNCCOPY_VIA_READSEGMENTS, UDP_PACKET_CHUNK_SIZE); + + if (NS_FAILED(rv)) { + return; + } + + NetAddr netAddr; + PRNetAddrToNetAddr(&prClientAddr, &netAddr); + nsCOMPtr<nsIUDPMessage> message = new UDPMessageProxy(&netAddr, pipeOut, data); + mListener->OnPacketReceived(this, message); +} + +void +nsUDPSocket::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"); + CloseSocket(); + } + SaveNetworkStats(true); + + if (mListener) + { + // need to atomically clear mListener. see our Close() method. + RefPtr<nsIUDPSocketListener> listener = nullptr; + { + MutexAutoLock lock(mLock); + listener = mListener.forget(); + } + + if (listener) { + listener->OnStopListening(this, mCondition); + NS_ProxyRelease(mListenerTarget, listener.forget()); + } + } +} + +void +nsUDPSocket::IsLocal(bool *aIsLocal) +{ + // If bound to loopback, this UDP socket only accepts local connections. + *aIsLocal = mAddr.raw.family == nsINetAddr::FAMILY_LOCAL; +} + +//----------------------------------------------------------------------------- +// 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; + } + + PRNetAddr prAddr; + if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + NetAddr addr; + + if (aPort < 0) + aPort = 0; + + addr.raw.family = AF_INET; + addr.inet.port = htons(aPort); + addr.inet.ip = prAddr.inet.ip; + + 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); + + if (gIOService->IsNetTearingDown()) { + return NS_ERROR_FAILURE; + } + + bool addressReuse = (aOptionalArgc == 1) ? aAddressReuse : true; + + // + // configure listening socket... + // + + mFD = PR_OpenUDPSocket(aAddr->raw.family); + if (!mFD) + { + NS_WARNING("unable to create UDP socket"); + return NS_ERROR_FAILURE; + } + + if (aPrincipal) { + mAppId = aPrincipal->GetAppId(); + mIsInIsolatedMozBrowserElement = + aPrincipal->GetIsInIsolatedMozBrowserElement(); + } + +#ifdef MOZ_WIDGET_GONK + if (mAppId != NECKO_UNKNOWN_APP_ID) { + nsCOMPtr<nsINetworkInfo> activeNetworkInfo; + GetActiveNetworkInfo(activeNetworkInfo); + mActiveNetworkInfo = + new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo); + } +#endif + + uint16_t port; + if (NS_FAILED(net::GetPort(aAddr, &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; + PR_InitializeNetAddr(PR_IpAddrAny, 0, &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 NetworkActivityMonitor + NetworkActivityMonitor::AttachIOLayer(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; + } + + bool onSTSThread = false; + mSts->IsOnCurrentThread(&onSTSThread); + NS_ASSERTION(onSTSThread, "NOT ON STS THREAD"); + if (!onSTSThread) { + return NS_ERROR_FAILURE; + } + + PRNetAddr prAddr; + NetAddrToPRNetAddr(aAddr, &prAddr); + + if (PR_Connect(mFD, &prAddr, PR_INTERVAL_NO_WAIT) != PR_SUCCESS) { + NS_WARNING("Cannot PR_Connect"); + return NS_ERROR_FAILURE; + } + + // 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) + { + // Here we want to go directly with closing the socket since some tests + // expects this happen synchronously. + CloseSocket(); + + SaveNetworkStats(true); + 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 = net::GetPort(&mAddr, &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::SaveNetworkStats(bool aEnforce) +{ +#ifdef MOZ_WIDGET_GONK + if (!mActiveNetworkInfo || mAppId == NECKO_UNKNOWN_APP_ID) { + return; + } + + if (mByteReadCount == 0 && mByteWriteCount == 0) { + return; + } + + uint64_t total = mByteReadCount + mByteWriteCount; + if (aEnforce || total > NETWORK_STATS_THRESHOLD) { + // Create the event to save the network statistics. + // the event is then dispathed to the main thread. + RefPtr<Runnable> event = + new SaveNetworkStatsEvent(mAppId, mIsInIsolatedMozBrowserElement, mActiveNetworkInfo, + mByteReadCount, mByteWriteCount, false); + NS_DispatchToMainThread(event); + + // Reset the counters after saving. + mByteReadCount = 0; + mByteWriteCount = 0; + } +#endif +} + +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() {} + +public: + explicit SocketListenerProxy(nsIUDPSocketListener* aListener) + : mListener(new nsMainThreadPtrHolder<nsIUDPSocketListener>(aListener)) + , mTargetThread(do_GetCurrentThread()) + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUDPSOCKETLISTENER + + class OnPacketReceivedRunnable : public Runnable + { + public: + OnPacketReceivedRunnable(const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener, + nsIUDPSocket* aSocket, + nsIUDPMessage* aMessage) + : 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) + : mListener(aListener) + , mSocket(aSocket) + , mStatus(aStatus) + { } + + NS_DECL_NSIRUNNABLE + + private: + nsMainThreadPtrHandle<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIUDPSocket> mSocket; + nsresult mStatus; + }; + +private: + nsMainThreadPtrHandle<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIEventTarget> mTargetThread; +}; + +NS_IMPL_ISUPPORTS(SocketListenerProxy, + nsIUDPSocketListener) + +NS_IMETHODIMP +SocketListenerProxy::OnPacketReceived(nsIUDPSocket* aSocket, + nsIUDPMessage* aMessage) +{ + RefPtr<OnPacketReceivedRunnable> r = + new OnPacketReceivedRunnable(mListener, aSocket, aMessage); + return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +SocketListenerProxy::OnStopListening(nsIUDPSocket* aSocket, + nsresult aStatus) +{ + RefPtr<OnStopListeningRunnable> r = + new OnStopListeningRunnable(mListener, aSocket, aStatus); + return mTargetThread->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, + 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() {} + +public: + explicit SocketListenerProxyBackground(nsIUDPSocketListener* aListener) + : mListener(aListener) + , mTargetThread(do_GetCurrentThread()) + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUDPSOCKETLISTENER + + class OnPacketReceivedRunnable : public Runnable + { + public: + OnPacketReceivedRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener, + nsIUDPSocket* aSocket, + nsIUDPMessage* aMessage) + : 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) + : mListener(aListener) + , mSocket(aSocket) + , mStatus(aStatus) + { } + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIUDPSocket> mSocket; + nsresult mStatus; + }; + +private: + nsCOMPtr<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIEventTarget> mTargetThread; +}; + +NS_IMPL_ISUPPORTS(SocketListenerProxyBackground, + nsIUDPSocketListener) + +NS_IMETHODIMP +SocketListenerProxyBackground::OnPacketReceived(nsIUDPSocket* aSocket, + nsIUDPMessage* aMessage) +{ + RefPtr<OnPacketReceivedRunnable> r = + new OnPacketReceivedRunnable(mListener, aSocket, aMessage); + return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +SocketListenerProxyBackground::OnStopListening(nsIUDPSocket* aSocket, + nsresult aStatus) +{ + RefPtr<OnStopListeningRunnable> r = + new OnStopListeningRunnable(mListener, aSocket, aStatus); + return mTargetThread->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 %u", __FUNCTION__, this, data.Length())); + nsCOMPtr<nsIUDPMessage> message = new UDPMessageProxy(&netAddr, + outputStream, + 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.SwapElements(aData); + } + +private: + virtual ~PendingSend() {} + + RefPtr<nsUDPSocket> mSocket; + uint16_t mPort; + FallibleTArray<uint8_t> mData; +}; + +NS_IMPL_ISUPPORTS(PendingSend, nsIDNSListener) + +NS_IMETHODIMP +PendingSend::OnLookupComplete(nsICancelable *request, + nsIDNSRecord *rec, + nsresult status) +{ + if (NS_FAILED(status)) { + NS_WARNING("Failed to send UDP packet due to DNS lookup failure"); + return NS_OK; + } + + 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() {} + + RefPtr<nsUDPSocket> mSocket; + uint16_t mPort; + nsCOMPtr<nsIInputStream> mStream; +}; + +NS_IMPL_ISUPPORTS(PendingSendStream, nsIDNSListener) + +NS_IMETHODIMP +PendingSendStream::OnLookupComplete(nsICancelable *request, + nsIDNSRecord *rec, + nsresult status) +{ + if (NS_FAILED(status)) { + NS_WARNING("Failed to send UDP packet due to DNS lookup failure"); + return NS_OK; + } + + 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) + : mSocket(aSocket) + , mAddr(aAddr) + , mData(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); + { + MutexAutoLock lock(mLock); + mListenerTarget = NS_GetCurrentThread(); + if (NS_IsMainThread()) { + // PNecko usage + mListener = new SocketListenerProxy(aListener); + } else { + // PBackground usage from media/mtransport + mListener = new SocketListenerProxyBackground(aListener); + } + } + return PostEvent(this, &nsUDPSocket::OnMsgAttach); +} + +NS_IMETHODIMP +nsUDPSocket::Send(const nsACString &aHost, uint16_t aPort, + const uint8_t *aData, uint32_t aDataLength, + uint32_t *_retval) +{ + NS_ENSURE_ARG(aData); + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = 0; + + FallibleTArray<uint8_t> fallibleArray; + if (!fallibleArray.InsertElementsAt(0, aData, aDataLength, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<nsIDNSListener> listener = new PendingSend(this, aPort, fallibleArray); + + nsresult rv = ResolveHost(aHost, listener); + NS_ENSURE_SUCCESS(rv, rv); + + *_retval = aDataLength; + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocket::SendWithAddr(nsINetAddr *aAddr, const uint8_t *aData, + uint32_t aDataLength, uint32_t *_retval) +{ + NS_ENSURE_ARG(aAddr); + NS_ENSURE_ARG(aData); + NS_ENSURE_ARG_POINTER(_retval); + + NetAddr netAddr; + aAddr->GetNetAddr(&netAddr); + return SendWithAddress(&netAddr, aData, aDataLength, _retval); +} + +NS_IMETHODIMP +nsUDPSocket::SendWithAddress(const NetAddr *aAddr, const uint8_t *aData, + uint32_t aDataLength, uint32_t *_retval) +{ + NS_ENSURE_ARG(aAddr); + NS_ENSURE_ARG(aData); + NS_ENSURE_ARG_POINTER(_retval); + + *_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, sizeof(uint8_t) *aDataLength, + 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.InsertElementsAt(0, aData, aDataLength, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = mSts->Dispatch( + new SendRequestRunnable(this, *aAddr, Move(fallibleArray)), + NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + *_retval = aDataLength; + } + 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, 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); +} + +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::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 000000000..4ddff4248 --- /dev/null +++ b/netwerk/base/nsUDPSocket.h @@ -0,0 +1,131 @@ +/* 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 "nsIOutputStream.h" +#include "nsAutoPtr.h" +#include "nsCycleCollectionParticipant.h" + +#ifdef MOZ_WIDGET_GONK +#include "nsINetworkInterface.h" +#include "nsProxyRelease.h" +#endif + +//----------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +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; + + uint64_t ByteCountSent() override { return mByteWriteCount; } + uint64_t ByteCountReceived() override { return mByteReadCount; } + + void AddOutputBytes(uint64_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 SaveNetworkStats(bool aEnforce); + + void CloseSocket(); + + // lock protects access to mListener; + // so mListener is not cleared while being used/locked. + Mutex mLock; + PRFileDesc *mFD; + NetAddr mAddr; + uint32_t mAppId; + bool mIsInIsolatedMozBrowserElement; + nsCOMPtr<nsIUDPSocketListener> mListener; + nsCOMPtr<nsIEventTarget> mListenerTarget; + bool mAttached; + RefPtr<nsSocketTransportService> mSts; + + uint64_t mByteReadCount; + uint64_t mByteWriteCount; +#ifdef MOZ_WIDGET_GONK + nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo; +#endif +}; + +//----------------------------------------------------------------------------- + +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(); + + 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 000000000..520d6c6fa --- /dev/null +++ b/netwerk/base/nsURIHashKey.h @@ -0,0 +1,61 @@ +/* -*- 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/. */ +#ifndef nsURIHashKey_h__ +#define nsURIHashKey_h__ + +#include "PLDHashTable.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" +#include "nsHashKeys.h" +#include "mozilla/Unused.h" + +/** + * Hashtable key class to use with nsTHashtable/nsBaseHashtable + */ +class nsURIHashKey : public PLDHashEntryHdr +{ +public: + typedef nsIURI* KeyType; + typedef const nsIURI* KeyTypePointer; + + explicit nsURIHashKey(const nsIURI* aKey) : + mKey(const_cast<nsIURI*>(aKey)) { MOZ_COUNT_CTOR(nsURIHashKey); } + nsURIHashKey(const nsURIHashKey& toCopy) : + mKey(toCopy.mKey) { MOZ_COUNT_CTOR(nsURIHashKey); } + ~nsURIHashKey() { MOZ_COUNT_DTOR(nsURIHashKey); } + + 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(EmptyCString()); + } + 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 000000000..8def697da --- /dev/null +++ b/netwerk/base/nsURLHelper.cpp @@ -0,0 +1,1168 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=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 "mozilla/RangedPtr.h" + +#include <algorithm> +#include <iterator> + +#include "nsURLHelper.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/Tokenizer.h" + +using namespace mozilla; + +//---------------------------------------------------------------------------- +// Init/Shutdown +//---------------------------------------------------------------------------- + +static bool gInitialized = false; +static nsIURLParser *gNoAuthURLParser = nullptr; +static nsIURLParser *gAuthURLParser = nullptr; +static nsIURLParser *gStdURLParser = nullptr; +static int32_t gMaxLength = 1048576; // Default: 1MB + +static void +InitGlobals() +{ + nsCOMPtr<nsIURLParser> parser; + + parser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID); + NS_ASSERTION(parser, "failed getting 'noauth' url parser"); + if (parser) { + gNoAuthURLParser = parser.get(); + NS_ADDREF(gNoAuthURLParser); + } + + parser = do_GetService(NS_AUTHURLPARSER_CONTRACTID); + NS_ASSERTION(parser, "failed getting 'auth' url parser"); + if (parser) { + gAuthURLParser = parser.get(); + NS_ADDREF(gAuthURLParser); + } + + parser = do_GetService(NS_STDURLPARSER_CONTRACTID); + NS_ASSERTION(parser, "failed getting 'std' url parser"); + if (parser) { + gStdURLParser = parser.get(); + NS_ADDREF(gStdURLParser); + } + + gInitialized = true; + Preferences::AddIntVarCache(&gMaxLength, + "network.standard-url.max-length", 1048576); +} + +void +net_ShutdownURLHelper() +{ + if (gInitialized) { + NS_IF_RELEASE(gNoAuthURLParser); + NS_IF_RELEASE(gAuthURLParser); + NS_IF_RELEASE(gStdURLParser); + gInitialized = false; + } +} + +int32_t net_GetURLMaxLength() +{ + return gMaxLength; +} + +//---------------------------------------------------------------------------- +// 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) gMaxLength) { + 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 (nsCRT::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 +} + +nsresult +net_ResolveRelativePath(const nsACString &relativePath, + const nsACString &basePath, + nsACString &result) +{ + nsAutoCString name; + nsAutoCString path(basePath); + bool needsDelim = false; + + if ( !path.IsEmpty() ) { + char16_t last = path.Last(); + needsDelim = !(last == '/'); + } + + nsACString::const_iterator beg, end; + relativePath.BeginReading(beg); + relativePath.EndReading(end); + + bool stop = false; + char c; + for (; !stop; ++beg) { + c = (beg == end) ? '\0' : *beg; + //printf("%c [name=%s] [path=%s]\n", c, name.get(), path.get()); + switch (c) { + case '\0': + case '#': + case '?': + stop = true; + MOZ_FALLTHROUGH; + case '/': + // delimiter found + if (name.EqualsLiteral("..")) { + // pop path + // If we already have the delim at end, then + // skip over that when searching for next one to the left + int32_t offset = path.Length() - (needsDelim ? 1 : 2); + // First check for errors + if (offset < 0 ) + return NS_ERROR_MALFORMED_URI; + int32_t pos = path.RFind("/", false, offset); + if (pos >= 0) + path.Truncate(pos + 1); + else + path.Truncate(); + } + else if (name.IsEmpty() || name.EqualsLiteral(".")) { + // do nothing + } + else { + // append name to path + if (needsDelim) + path += '/'; + path += name; + needsDelim = true; + } + name.Truncate(); + break; + + default: + // append char to name + name += c; + } + } + // append anything left on relativePath (e.g. #..., ;..., ?...) + if (c != '\0') + path += Substring(--beg, end); + + result = path; + return NS_OK; +} + +//---------------------------------------------------------------------------- +// scheme fu +//---------------------------------------------------------------------------- + +static bool isAsciiAlpha(char c) { + return nsCRT::IsAsciiAlpha(c); +} + +static bool +net_IsValidSchemeChar(const char aChar) +{ + if (nsCRT::IsAsciiAlpha(aChar) || nsCRT::IsAsciiDigit(aChar) || + aChar == '+' || aChar == '.' || aChar == '-') { + return true; + } + return false; +} + +/* 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.StripChars("\r\n\t"); + return NS_OK; +} + +bool +net_IsValidScheme(const char *scheme, uint32_t schemeLen) +{ + // first char must be alpha + if (!nsCRT::IsAsciiAlpha(*scheme)) + return false; + + // nsCStrings may have embedded nulls -- reject those too + for (; schemeLen; ++scheme, --schemeLen) { + if (!(nsCRT::IsAsciiAlpha(*scheme) || + nsCRT::IsAsciiDigit(*scheme) || + *scheme == '+' || + *scheme == '.' || + *scheme == '-')) + return false; + } + + return true; +} + +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) +{ + const char kCharsToStrip[] = "\r\n\t"; + + result.Truncate(); + + auto start = input.BeginReading(); + auto end = input.EndReading(); + + // Trim off leading and trailing invalid chars. + auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; }; + auto newStart = std::find_if(start, end, charFilter); + 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. + auto itr = std::find_first_of( + newStart, newEnd, std::begin(kCharsToStrip), std::end(kCharsToStrip)); + const bool needsStrip = itr != newEnd; + + // 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.StripChars(kCharsToStrip); + } +} + + +#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 (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); + + NS_NOTREACHED("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); + + NS_NOTREACHED("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. We include '(' in + // the trailing trim set to catch media-type comments, which are not at all + // standard, but may occur in rare cases. + 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 (PL_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. We include + // '(' in the trailing trim set to catch media-type comments, which are + // not at all standard, but may occur in rare cases. + 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 nsCSubstring &host) +{ + 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 + nsAutoCString strhost(host); + PRNetAddr addr; + return PR_StringToNetAddr(strhost.get(), &addr) == PR_SUCCESS; +} + +bool +net_IsValidIPv4Addr(const char *addr, int32_t addrLen) +{ + RangedPtr<const char> p(addr, addrLen); + + int32_t octet = -1; // means no digit yet + int32_t dotCount = 0; // number of dots in the address + + for (; addrLen; ++p, --addrLen) { + if (*p == '.') { + dotCount++; + if (octet == -1) { + // invalid octet + return false; + } + octet = -1; + } else if (*p >= '0' && *p <='9') { + if (octet == 0) { + // leading 0 is not allowed + return false; + } else if (octet == -1) { + octet = *p - '0'; + } else { + octet *= 10; + octet += *p - '0'; + if (octet > 255) + return false; + } + } else { + // invalid character + return false; + } + } + + return (dotCount == 3 && octet != -1); +} + +bool +net_IsValidIPv6Addr(const char *addr, int32_t addrLen) +{ + RangedPtr<const char> p(addr, addrLen); + + int32_t digits = 0; // number of digits in current block + int32_t colons = 0; // number of colons in a row during parsing + int32_t blocks = 0; // number of hexadecimal blocks + bool haveZeros = false; // true if double colon is present in the address + + for (; addrLen; ++p, --addrLen) { + if (*p == ':') { + if (colons == 0) { + if (digits != 0) { + digits = 0; + blocks++; + } + } else if (colons == 1) { + if (haveZeros) + return false; // only one occurrence is allowed + haveZeros = true; + } else { + // too many colons in a row + return false; + } + colons++; + } else if ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || + (*p >= 'A' && *p <= 'F')) { + if (colons == 1 && blocks == 0) // starts with a single colon + return false; + if (digits == 4) // too many digits + return false; + colons = 0; + digits++; + } else if (*p == '.') { + // check valid IPv4 from the beginning of the last block + if (!net_IsValidIPv4Addr(p.get() - digits, addrLen + digits)) + return false; + return (haveZeros && blocks < 6) || (!haveZeros && blocks == 6); + } else { + // invalid character + return false; + } + } + + if (colons == 1) // ends with a single colon + return false; + + if (digits) // there is a block at the end + blocks++; + + return (haveZeros && blocks < 8) || (!haveZeros && blocks == 8); +} diff --git a/netwerk/base/nsURLHelper.h b/netwerk/base/nsURLHelper.h new file mode 100644 index 000000000..f30814c25 --- /dev/null +++ b/netwerk/base/nsURLHelper.h @@ -0,0 +1,250 @@ +/* -*- 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/. */ + +#ifndef nsURLHelper_h__ +#define nsURLHelper_h__ + +#include "nsString.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); + +/** + * Resolves a relative path string containing "." and ".." + * with respect to a base path (assumed to already be resolved). + * For example, resolving "../../foo/./bar/../baz.html" w.r.t. + * "/a/b/c/d/e/" yields "/a/b/c/foo/baz.html". Attempting to + * ascend above the base results in the NS_ERROR_MALFORMED_URI + * exception. If basePath is null, it treats it as "/". + * + * @param relativePath a relative URI + * @param basePath a base URI + * + * @return a new string, representing canonical uri + */ +nsresult net_ResolveRelativePath(const nsACString &relativePath, + const nsACString &basePath, + nsACString &result); + +/** + * 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& inURL); + +/** + * Extract URI-Scheme if possible + * + * @param inURI URI spec + * @param scheme scheme copied to this buffer on return (may be null) + */ +nsresult net_ExtractURLScheme(const nsACString &inURI, + nsACString &scheme); + +/* check that the given scheme conforms to RFC 2396 */ +bool net_IsValidScheme(const char *scheme, uint32_t schemeLen); + +inline bool net_IsValidScheme(const nsAFlatCString &scheme) +{ + return net_IsValidScheme(scheme.get(), scheme.Length()); +} + +/** + * 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); + +#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 *str, const char *end, 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 *str, const char *end, 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 *str, const char *end, 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*)0)-1) + +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 nsCSubstring &host); + +/** + * Checks whether the IPv4 address is valid according to RFC 3986 section 3.2.2. + */ +bool net_IsValidIPv4Addr(const char *addr, int32_t addrLen); + +/** + * Checks whether the IPv6 address is valid according to RFC 3986 section 3.2.2. + */ +bool net_IsValidIPv6Addr(const char *addr, int32_t addrLen); + + +/** + * Returns the max length of a URL. The default is 1048576 (1 MB). + * Can be changed by pref "network.standard-url.max-length" + */ +int32_t net_GetURLMaxLength(); + +#endif // !nsURLHelper_h__ diff --git a/netwerk/base/nsURLHelperOSX.cpp b/netwerk/base/nsURLHelperOSX.cpp new file mode 100644 index 000000000..bcc0b257f --- /dev/null +++ b/netwerk/base/nsURLHelperOSX.cpp @@ -0,0 +1,216 @@ +/* -*- 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; + NS_NAMED_LITERAL_CSTRING(prefix, "file://"); + + // 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(EmptyCString(), 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.Insert("/", 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 000000000..5b36770d7 --- /dev/null +++ b/netwerk/base/nsURLHelperUnix.cpp @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* Unix-specific local file uri parsing */ +#include "nsURLHelper.h" +#include "nsEscape.h" +#include "nsIFile.h" +#include "nsNativeCharsetUtils.h" + +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; + NS_NAMED_LITERAL_CSTRING(prefix, "file://"); + + // 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(EmptyCString(), 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 000000000..4ec46db4e --- /dev/null +++ b/netwerk/base/nsURLHelperWin.cpp @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* Windows-specific local file uri parsing */ +#include "nsURLHelper.h" +#include "nsEscape.h" +#include "nsIFile.h" +#include <windows.h> + +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 + NS_NAMED_LITERAL_CSTRING(prefix, "file:///"); + + // 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() > (uint32_t) net_GetURLMaxLength()) { + 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; + } + + localFile->SetFollowLinks(true); + + 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 000000000..b75ee0c4d --- /dev/null +++ b/netwerk/base/nsURLParsers.cpp @@ -0,0 +1,702 @@ +/* -*- 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 <string.h> + +#include "mozilla/RangedPtr.h" + +#include "nsURLParsers.h" +#include "nsURLHelper.h" +#include "nsString.h" +#include "nsCRT.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(spec, colon - spec) || (*(colon+1) == ':')) { + 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 = 0, *query_end = 0; + const char *ref_beg = 0; + const char *p = 0; + 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) +{ + NS_NOTREACHED("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) +{ + NS_PRECONDITION(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 defined(XP_WIN) + // 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] == '|') && + nsCRT::IsAsciiAlpha(spec[2]) && + ((specLen == 4) || (spec[4] == '/') || (spec[4] == '\\'))) { + pos = 1; + break; + } +#endif + // 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]=='|') && nsCRT::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) { + continue; + } + 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> + if (p == userinfo) { + // must have a username! + return NS_ERROR_MALFORMED_URI; + } + 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(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) +{ + NS_PRECONDITION(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) +{ + NS_PRECONDITION(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 000000000..34de99a37 --- /dev/null +++ b/netwerk/base/nsURLParsers.h @@ -0,0 +1,124 @@ +/* -*- 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/. */ + +#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() { } + +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() {} + +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() {} + +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() {} + +public: + void ParseAfterScheme(const char *spec, int32_t specLen, + uint32_t *authPos, int32_t *authLen, + uint32_t *pathPos, int32_t *pathLen); +}; + +#endif // nsURLParsers_h__ diff --git a/netwerk/base/nsUnicharStreamLoader.cpp b/netwerk/base/nsUnicharStreamLoader.cpp new file mode 100644 index 000000000..115acf9ae --- /dev/null +++ b/netwerk/base/nsUnicharStreamLoader.cpp @@ -0,0 +1,250 @@ +/* -*- 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" + +#include "nsUnicharStreamLoader.h" +#include "nsIInputStream.h" +#include <algorithm> +#include "mozilla/dom/EncodingUtils.h" + +// 1024 bytes is specified in +// http://www.whatwg.org/specs/web-apps/current-work/#charset for HTML; for +// other resource types (e.g. CSS) typically fewer bytes are fine too, since +// they only look at things right at the beginning of the data. +#define SNIFFING_BUFFER_SIZE 1024 + +using namespace mozilla; +using mozilla::dom::EncodingUtils; + +NS_IMETHODIMP +nsUnicharStreamLoader::Init(nsIUnicharStreamLoaderObserver *aObserver) +{ + NS_ENSURE_ARG_POINTER(aObserver); + + mObserver = aObserver; + + if (!mRawData.SetCapacity(SNIFFING_BUFFER_SIZE, fallible)) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + +nsresult +nsUnicharStreamLoader::Create(nsISupports *aOuter, + REFNSIID aIID, + void **aResult) +{ + if (aOuter) return NS_ERROR_NO_AGGREGATION; + + nsUnicharStreamLoader* it = new nsUnicharStreamLoader(); + NS_ADDREF(it); + nsresult rv = it->QueryInterface(aIID, aResult); + NS_RELEASE(it); + return rv; +} + +NS_IMPL_ISUPPORTS(nsUnicharStreamLoader, nsIUnicharStreamLoader, + nsIRequestObserver, nsIStreamListener) + +NS_IMETHODIMP +nsUnicharStreamLoader::GetChannel(nsIChannel **aChannel) +{ + NS_IF_ADDREF(*aChannel = mChannel); + return NS_OK; +} + +NS_IMETHODIMP +nsUnicharStreamLoader::GetCharset(nsACString& aCharset) +{ + aCharset = mCharset; + return NS_OK; +} + +/* nsIRequestObserver implementation */ +NS_IMETHODIMP +nsUnicharStreamLoader::OnStartRequest(nsIRequest*, nsISupports*) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsUnicharStreamLoader::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatus) +{ + if (!mObserver) { + NS_ERROR("nsUnicharStreamLoader::OnStopRequest called before ::Init"); + return NS_ERROR_UNEXPECTED; + } + + mContext = aContext; + mChannel = do_QueryInterface(aRequest); + + nsresult rv = NS_OK; + if (mRawData.Length() > 0 && NS_SUCCEEDED(aStatus)) { + MOZ_ASSERT(mBuffer.Length() == 0, + "should not have both decoded and raw data"); + rv = DetermineCharset(); + } + + if (NS_FAILED(rv)) { + // Call the observer but pass it no data. + mObserver->OnStreamComplete(this, mContext, rv, EmptyString()); + } else { + mObserver->OnStreamComplete(this, mContext, aStatus, mBuffer); + } + + mObserver = nullptr; + mDecoder = nullptr; + mContext = nullptr; + mChannel = nullptr; + mCharset.Truncate(); + mRawData.Truncate(); + mRawBuffer.Truncate(); + mBuffer.Truncate(); + return rv; +} + +NS_IMETHODIMP +nsUnicharStreamLoader::GetRawBuffer(nsACString& aRawBuffer) +{ + aRawBuffer = mRawBuffer; + return NS_OK; +} + +/* nsIStreamListener implementation */ +NS_IMETHODIMP +nsUnicharStreamLoader::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aInputStream, + uint64_t aSourceOffset, + uint32_t aCount) +{ + if (!mObserver) { + NS_ERROR("nsUnicharStreamLoader::OnDataAvailable called before ::Init"); + return NS_ERROR_UNEXPECTED; + } + + mContext = aContext; + mChannel = do_QueryInterface(aRequest); + + nsresult rv = NS_OK; + if (mDecoder) { + // process everything we've got + uint32_t dummy; + aInputStream->ReadSegments(WriteSegmentFun, this, aCount, &dummy); + } else { + // no decoder yet. Read up to SNIFFING_BUFFER_SIZE octets into + // mRawData (this is the cutoff specified in + // draft-abarth-mime-sniff-06). If we can get that much, then go + // ahead and fire charset detection and read the rest. Otherwise + // wait for more data. + + uint32_t haveRead = mRawData.Length(); + uint32_t toRead = std::min(SNIFFING_BUFFER_SIZE - haveRead, aCount); + uint32_t n; + char *here = mRawData.BeginWriting() + haveRead; + + rv = aInputStream->Read(here, toRead, &n); + if (NS_SUCCEEDED(rv)) { + mRawData.SetLength(haveRead + n); + if (mRawData.Length() == SNIFFING_BUFFER_SIZE) { + rv = DetermineCharset(); + if (NS_SUCCEEDED(rv)) { + // process what's left + uint32_t dummy; + aInputStream->ReadSegments(WriteSegmentFun, this, aCount - n, &dummy); + } + } else { + MOZ_ASSERT(n == aCount, "didn't read as much as was available"); + } + } + } + + mContext = nullptr; + mChannel = nullptr; + return rv; +} + +nsresult +nsUnicharStreamLoader::DetermineCharset() +{ + nsresult rv = mObserver->OnDetermineCharset(this, mContext, + mRawData, mCharset); + if (NS_FAILED(rv) || mCharset.IsEmpty()) { + // The observer told us nothing useful + mCharset.AssignLiteral("UTF-8"); + } + + // Sadly, nsIUnicharStreamLoader is exposed to extensions, so we can't + // assume mozilla::css::Loader to be the only caller. Special-casing + // replacement, since it's not invariant under a second label resolution + // operation. + if (mCharset.EqualsLiteral("replacement")) { + mDecoder = EncodingUtils::DecoderForEncoding(mCharset); + } else { + nsAutoCString charset; + if (!EncodingUtils::FindEncodingForLabelNoReplacement(mCharset, charset)) { + // If we got replacement here, the caller was not mozilla::css::Loader + // but an extension. + return NS_ERROR_UCONV_NOCONV; + } + mDecoder = EncodingUtils::DecoderForEncoding(charset); + } + + // Process the data into mBuffer + uint32_t dummy; + rv = WriteSegmentFun(nullptr, this, + mRawData.BeginReading(), + 0, mRawData.Length(), + &dummy); + mRawData.Truncate(); + return rv; +} + +nsresult +nsUnicharStreamLoader::WriteSegmentFun(nsIInputStream *, + void *aClosure, + const char *aSegment, + uint32_t, + uint32_t aCount, + uint32_t *aWriteCount) +{ + nsUnicharStreamLoader* self = static_cast<nsUnicharStreamLoader*>(aClosure); + + uint32_t haveRead = self->mBuffer.Length(); + int32_t srcLen = aCount; + int32_t dstLen; + + nsresult rv = self->mDecoder->GetMaxLength(aSegment, srcLen, &dstLen); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint32_t capacity = haveRead + dstLen; + if (!self->mBuffer.SetCapacity(capacity, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!self->mRawBuffer.Append(aSegment, aCount, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = self->mDecoder->Convert(aSegment, + &srcLen, + self->mBuffer.BeginWriting() + haveRead, + &dstLen); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(srcLen == static_cast<int32_t>(aCount)); + haveRead += dstLen; + + self->mBuffer.SetLength(haveRead); + *aWriteCount = aCount; + return NS_OK; +} diff --git a/netwerk/base/nsUnicharStreamLoader.h b/netwerk/base/nsUnicharStreamLoader.h new file mode 100644 index 000000000..298fb9e11 --- /dev/null +++ b/netwerk/base/nsUnicharStreamLoader.h @@ -0,0 +1,60 @@ +/* -*- 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 nsUnicharStreamLoader_h__ +#define nsUnicharStreamLoader_h__ + +#include "nsIChannel.h" +#include "nsIUnicharStreamLoader.h" +#include "nsIUnicodeDecoder.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +class nsIInputStream; + +class nsUnicharStreamLoader : public nsIUnicharStreamLoader +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIUNICHARSTREAMLOADER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsUnicharStreamLoader() {} + + static nsresult Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + virtual ~nsUnicharStreamLoader() {} + + nsresult DetermineCharset(); + + /** + * callback method used for ReadSegments + */ + static nsresult WriteSegmentFun(nsIInputStream *, void *, const char *, + uint32_t, uint32_t, uint32_t *); + + nsCOMPtr<nsIUnicharStreamLoaderObserver> mObserver; + nsCOMPtr<nsIUnicodeDecoder> mDecoder; + nsCOMPtr<nsISupports> mContext; + nsCOMPtr<nsIChannel> mChannel; + nsCString mCharset; + + // This holds the first up-to-512 bytes of the raw stream. + // It will be passed to the OnDetermineCharset callback. + nsCString mRawData; + + // Holds complete raw bytes as received so that SRI checks can be + // calculated on the raw data prior to character conversion. + nsCString mRawBuffer; + + // This holds the complete contents of the stream so far, after + // decoding to UTF-16. It will be passed to the OnStreamComplete + // callback. + nsString mBuffer; +}; + +#endif // nsUnicharStreamLoader_h__ diff --git a/netwerk/base/rust-url-capi/.gitignore b/netwerk/base/rust-url-capi/.gitignore new file mode 100644 index 000000000..4fffb2f89 --- /dev/null +++ b/netwerk/base/rust-url-capi/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/netwerk/base/rust-url-capi/Cargo.toml b/netwerk/base/rust-url-capi/Cargo.toml new file mode 100644 index 000000000..ecdb53058 --- /dev/null +++ b/netwerk/base/rust-url-capi/Cargo.toml @@ -0,0 +1,19 @@ +[package] + +name = "rust_url_capi" +version = "0.0.1" +authors = ["Valentin Gosu <valentin.gosu@gmail.com>"] + +[profile.dev] +opt-level = 3 +debug = true +rpath = true +lto = true + +[lib] +name = "rust_url_capi" + + +[dependencies] +libc = "0.2.0" +url = "1.2.1" diff --git a/netwerk/base/rust-url-capi/src/error_mapping.rs b/netwerk/base/rust-url-capi/src/error_mapping.rs new file mode 100644 index 000000000..f20afb263 --- /dev/null +++ b/netwerk/base/rust-url-capi/src/error_mapping.rs @@ -0,0 +1,68 @@ +use url::ParseError; + +pub trait ErrorCode { + fn error_code(&self) -> i32; +} + +impl<T: ErrorCode> ErrorCode for Result<(), T> { + fn error_code(&self) -> i32 { + match *self { + Ok(_) => 0, + Err(ref error) => error.error_code(), + } + } +} + +impl ErrorCode for () { + fn error_code(&self) -> i32 { + return -1; + } +} +impl ErrorCode for ParseError { + fn error_code(&self) -> i32 { + return -1; +// match *self { +// ParseError::EmptyHost => -1, +// ParseError::InvalidScheme => -2, +// ParseError::InvalidPort => -3, +// ParseError::InvalidIpv6Address => -4, +// ParseError::InvalidDomainCharacter => -5, +// ParseError::InvalidCharacter => -6, +// ParseError::InvalidBackslash => -7, +// ParseError::InvalidPercentEncoded => -8, +// ParseError::InvalidAtSymbolInUser => -9, +// ParseError::ExpectedTwoSlashes => -10, +// ParseError::ExpectedInitialSlash => -11, +// ParseError::NonUrlCodePoint => -12, +// ParseError::RelativeUrlWithScheme => -13, +// ParseError::RelativeUrlWithoutBase => -14, +// ParseError::RelativeUrlWithNonRelativeBase => -15, +// ParseError::NonAsciiDomainsNotSupportedYet => -16, +// ParseError::CannotSetJavascriptFragment => -17, +// ParseError::CannotSetPortWithFileLikeScheme => -18, +// ParseError::CannotSetUsernameWithNonRelativeScheme => -19, +// ParseError::CannotSetPasswordWithNonRelativeScheme => -20, +// ParseError::CannotSetHostPortWithNonRelativeScheme => -21, +// ParseError::CannotSetHostWithNonRelativeScheme => -22, +// ParseError::CannotSetPortWithNonRelativeScheme => -23, +// ParseError::CannotSetPathWithNonRelativeScheme => -24, +// } + } +} + +pub enum NSError { + OK, + InvalidArg, + Failure, +} + +impl ErrorCode for NSError { + #[allow(overflowing_literals)] + fn error_code(&self) -> i32 { + match *self { + NSError::OK => 0, + NSError::InvalidArg => 0x80070057, + NSError::Failure => 0x80004005 + } + } +} diff --git a/netwerk/base/rust-url-capi/src/lib.rs b/netwerk/base/rust-url-capi/src/lib.rs new file mode 100644 index 000000000..e2997ce46 --- /dev/null +++ b/netwerk/base/rust-url-capi/src/lib.rs @@ -0,0 +1,477 @@ +extern crate url; +use url::{Url, ParseError, ParseOptions}; +use url::quirks; +extern crate libc; +use libc::size_t; + + +use std::mem; +use std::str; + +#[allow(non_camel_case_types)] +pub type rusturl_ptr = *const libc::c_void; + +mod string_utils; +pub use string_utils::*; + +mod error_mapping; +use error_mapping::*; + +fn parser<'a>() -> ParseOptions<'a> { + Url::options() +} + +fn default_port(scheme: &str) -> Option<u32> { + match scheme { + "ftp" => Some(21), + "gopher" => Some(70), + "http" => Some(80), + "https" => Some(443), + "ws" => Some(80), + "wss" => Some(443), + "rtsp" => Some(443), + "moz-anno" => Some(443), + "android" => Some(443), + _ => None, + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_new(spec: *mut libc::c_char, len: size_t) -> rusturl_ptr { + let slice = std::slice::from_raw_parts(spec as *const libc::c_uchar, len as usize); + let url_spec = match str::from_utf8(slice) { + Ok(spec) => spec, + Err(_) => return 0 as rusturl_ptr + }; + + let url = match parser().parse(url_spec) { + Ok(url) => url, + Err(_) => return 0 as rusturl_ptr + }; + + let url = Box::new(url); + Box::into_raw(url) as rusturl_ptr +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_free(urlptr: rusturl_ptr) { + if urlptr.is_null() { + return (); + } + let url: Box<Url> = Box::from_raw(urlptr as *mut url::Url); + drop(url); +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_get_spec(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &Url = mem::transmute(urlptr); + cont.assign(&url.to_string()) +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_get_scheme(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &Url = mem::transmute(urlptr); + cont.assign(&url.scheme()) +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_get_username(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &Url = mem::transmute(urlptr); + if url.cannot_be_a_base() { + cont.set_size(0) + } else { + cont.assign(url.username()) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_get_password(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &Url = mem::transmute(urlptr); + match url.password() { + Some(p) => cont.assign(&p.to_string()), + None => cont.set_size(0) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_get_host(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &Url = mem::transmute(urlptr); + + match url.host() { + Some(h) => cont.assign(&h.to_string()), + None => cont.set_size(0) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_get_port(urlptr: rusturl_ptr) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &Url = mem::transmute(urlptr); + + match url.port() { + Some(port) => port as i32, + None => -1 + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_get_path(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &Url = mem::transmute(urlptr); + if url.cannot_be_a_base() { + cont.set_size(0) + } else { + cont.assign(url.path()) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_get_query(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &Url = mem::transmute(urlptr); + match url.query() { + Some(ref s) => cont.assign(s), + None => cont.set_size(0) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_get_fragment(urlptr: rusturl_ptr, cont: *mut libc::c_void) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &Url = mem::transmute(urlptr); + + match url.fragment() { + Some(ref fragment) => cont.assign(fragment), + None => cont.set_size(0) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_has_fragment(urlptr: rusturl_ptr) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &Url = mem::transmute(urlptr); + + match url.fragment() { + Some(_) => return 1, + None => return 0 + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_set_scheme(urlptr: rusturl_ptr, scheme: *mut libc::c_char, len: size_t) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let mut url: &mut Url = mem::transmute(urlptr); + let slice = std::slice::from_raw_parts(scheme as *const libc::c_uchar, len as usize); + + let scheme_ = match str::from_utf8(slice).ok() { + Some(p) => p, + None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed + }; + + quirks::set_protocol(url, scheme_).error_code() +} + + +#[no_mangle] +pub unsafe extern "C" fn rusturl_set_username(urlptr: rusturl_ptr, username: *mut libc::c_char, len: size_t) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let mut url: &mut Url = mem::transmute(urlptr); + let slice = std::slice::from_raw_parts(username as *const libc::c_uchar, len as usize); + + let username_ = match str::from_utf8(slice).ok() { + Some(p) => p, + None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed + }; + + quirks::set_username(url, username_).error_code() +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_set_password(urlptr: rusturl_ptr, password: *mut libc::c_char, len: size_t) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let mut url: &mut Url = mem::transmute(urlptr); + let slice = std::slice::from_raw_parts(password as *const libc::c_uchar, len as usize); + + let password_ = match str::from_utf8(slice).ok() { + Some(p) => p, + None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed + }; + + quirks::set_password(url, password_).error_code() +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_set_host_and_port(urlptr: rusturl_ptr, host_and_port: *mut libc::c_char, len: size_t) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let mut url: &mut Url = mem::transmute(urlptr); + let slice = std::slice::from_raw_parts(host_and_port as *const libc::c_uchar, len as usize); + + let host_and_port_ = match str::from_utf8(slice).ok() { + Some(p) => p, + None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed + }; + + quirks::set_host(url, host_and_port_).error_code() +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_set_host(urlptr: rusturl_ptr, host: *mut libc::c_char, len: size_t) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let mut url: &mut Url = mem::transmute(urlptr); + let slice = std::slice::from_raw_parts(host as *const libc::c_uchar, len as usize); + + let hostname = match str::from_utf8(slice).ok() { + Some(h) => h, + None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed + }; + + quirks::set_hostname(url, hostname).error_code() +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_set_port(urlptr: rusturl_ptr, port: *mut libc::c_char, len: size_t) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let mut url: &mut Url = mem::transmute(urlptr); + let slice = std::slice::from_raw_parts(port as *const libc::c_uchar, len as usize); + + let port_ = match str::from_utf8(slice).ok() { + Some(p) => p, + None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed + }; + + quirks::set_port(url, port_).error_code() +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_set_port_no(urlptr: rusturl_ptr, new_port: i32) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let mut url: &mut Url = mem::transmute(urlptr); + if url.cannot_be_a_base() { + -100 + } else { + if url.scheme() == "file" { + return -100; + } + match default_port(url.scheme()) { + Some(def_port) => if new_port == def_port as i32 { + let _ = url.set_port(None); + return NSError::OK.error_code(); + }, + None => {} + }; + if new_port > std::u16::MAX as i32 || new_port < 0 { + let _ = url.set_port(None); + } else { + let _ = url.set_port(Some(new_port as u16)); + } + NSError::OK.error_code() + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_set_path(urlptr: rusturl_ptr, path: *mut libc::c_char, len: size_t) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let mut url: &mut Url = mem::transmute(urlptr); + let slice = std::slice::from_raw_parts(path as *const libc::c_uchar, len as usize); + + let path_ = match str::from_utf8(slice).ok() { + Some(p) => p, + None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed + }; + + quirks::set_pathname(url, path_).error_code() +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_set_query(urlptr: rusturl_ptr, query: *mut libc::c_char, len: size_t) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let mut url: &mut Url = mem::transmute(urlptr); + let slice = std::slice::from_raw_parts(query as *const libc::c_uchar, len as usize); + + let query_ = match str::from_utf8(slice).ok() { + Some(p) => p, + None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed + }; + + quirks::set_search(url, query_).error_code() +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_set_fragment(urlptr: rusturl_ptr, fragment: *mut libc::c_char, len: size_t) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let mut url: &mut Url = mem::transmute(urlptr); + let slice = std::slice::from_raw_parts(fragment as *const libc::c_uchar, len as usize); + + let fragment_ = match str::from_utf8(slice).ok() { + Some(p) => p, + None => return ParseError::InvalidDomainCharacter.error_code() // utf-8 failed + }; + + quirks::set_hash(url, fragment_).error_code() +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_resolve(urlptr: rusturl_ptr, resolve: *mut libc::c_char, len: size_t, cont: *mut libc::c_void) -> i32 { + if urlptr.is_null() { + return NSError::InvalidArg.error_code(); + } + let url: &mut Url = mem::transmute(urlptr); + + let slice = std::slice::from_raw_parts(resolve as *const libc::c_uchar, len as usize); + + let resolve_ = match str::from_utf8(slice).ok() { + Some(p) => p, + None => return NSError::Failure.error_code() + }; + + match parser().base_url(Some(&url)).parse(resolve_).ok() { + Some(u) => cont.assign(&u.to_string()), + None => cont.set_size(0) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_common_base_spec(urlptr1: rusturl_ptr, urlptr2: rusturl_ptr, cont: *mut libc::c_void) -> i32 { + if urlptr1.is_null() || urlptr2.is_null() { + return NSError::InvalidArg.error_code(); + } + let url1: &Url = mem::transmute(urlptr1); + let url2: &Url = mem::transmute(urlptr2); + + if url1 == url2 { + return cont.assign(&url1.to_string()); + } + + if url1.scheme() != url2.scheme() || + url1.host() != url2.host() || + url1.username() != url2.username() || + url1.password() != url2.password() || + url1.port() != url2.port() { + return cont.set_size(0); + } + + let path1 = match url1.path_segments() { + Some(path) => path, + None => return cont.set_size(0) + }; + let path2 = match url2.path_segments() { + Some(path) => path, + None => return cont.set_size(0) + }; + + let mut url = url1.clone(); + url.set_query(None); + let _ = url.set_host(None); + { + let mut new_segments = if let Ok(segments) = url.path_segments_mut() { + segments + } else { + return cont.set_size(0) + }; + + for (p1, p2) in path1.zip(path2) { + if p1 != p2 { + break; + } else { + new_segments.push(p1); + } + } + } + + cont.assign(&url.to_string()) +} + +#[no_mangle] +pub unsafe extern "C" fn rusturl_relative_spec(urlptr1: rusturl_ptr, urlptr2: rusturl_ptr, cont: *mut libc::c_void) -> i32 { + if urlptr1.is_null() || urlptr2.is_null() { + return NSError::InvalidArg.error_code(); + } + let url1: &Url = mem::transmute(urlptr1); + let url2: &Url = mem::transmute(urlptr2); + + if url1 == url2 { + return cont.set_size(0); + } + + if url1.scheme() != url2.scheme() || + url1.host() != url2.host() || + url1.username() != url2.username() || + url1.password() != url2.password() || + url1.port() != url2.port() { + return cont.assign(&url2.to_string()); + } + + let mut path1 = match url1.path_segments() { + Some(path) => path, + None => return cont.assign(&url2.to_string()) + }; + let mut path2 = match url2.path_segments() { + Some(path) => path, + None => return cont.assign(&url2.to_string()) + }; + + // TODO: file:// on WIN? + + // Exhaust the part of the iterators that match + while let (Some(ref p1), Some(ref p2)) = (path1.next(), path2.next()) { + if p1 != p2 { + break; + } + } + + let mut buffer: String = "".to_string(); + for _ in path1 { + buffer = buffer + "../"; + } + for p2 in path2 { + buffer = buffer + p2 + "/"; + } + + return cont.assign(&buffer); +} + diff --git a/netwerk/base/rust-url-capi/src/rust-url-capi.h b/netwerk/base/rust-url-capi/src/rust-url-capi.h new file mode 100644 index 000000000..8d7a05aed --- /dev/null +++ b/netwerk/base/rust-url-capi/src/rust-url-capi.h @@ -0,0 +1,45 @@ +#ifndef __RUST_URL_CAPI +#define __RUST_URL_CAPI +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct rusturl; +typedef struct rusturl* rusturl_ptr; + +rusturl_ptr rusturl_new(const char *spec, size_t src_len); +void rusturl_free(rusturl_ptr url); + +int32_t rusturl_get_spec(rusturl_ptr url, void*); +int32_t rusturl_get_scheme(rusturl_ptr url, void*); +int32_t rusturl_get_username(rusturl_ptr url, void*); +int32_t rusturl_get_password(rusturl_ptr url, void*); +int32_t rusturl_get_host(rusturl_ptr url, void*); +int32_t rusturl_get_port(rusturl_ptr url); // returns port or -1 +int32_t rusturl_get_path(rusturl_ptr url, void*); +int32_t rusturl_get_query(rusturl_ptr url, void*); +int32_t rusturl_get_fragment(rusturl_ptr url, void*); +int32_t rusturl_has_fragment(rusturl_ptr url); // 1 true, 0 false, < 0 error + +int32_t rusturl_set_scheme(rusturl_ptr url, const char *scheme, size_t len); +int32_t rusturl_set_username(rusturl_ptr url, const char *user, size_t len); +int32_t rusturl_set_password(rusturl_ptr url, const char *pass, size_t len); +int32_t rusturl_set_host_and_port(rusturl_ptr url, const char *hostport, size_t len); +int32_t rusturl_set_host(rusturl_ptr url, const char *host, size_t len); +int32_t rusturl_set_port(rusturl_ptr url, const char *port, size_t len); +int32_t rusturl_set_port_no(rusturl_ptr url, const int32_t port); +int32_t rusturl_set_path(rusturl_ptr url, const char *path, size_t len); +int32_t rusturl_set_query(rusturl_ptr url, const char *path, size_t len); +int32_t rusturl_set_fragment(rusturl_ptr url, const char *path, size_t len); + +int32_t rusturl_resolve(rusturl_ptr url, const char *relative, size_t len, void*); +int32_t rusturl_common_base_spec(rusturl_ptr url1, rusturl_ptr url2, void*); +int32_t rusturl_relative_spec(rusturl_ptr url1, rusturl_ptr url2, void*); + +#ifdef __cplusplus +} +#endif + +#endif // __RUST_URL_CAPI
\ No newline at end of file diff --git a/netwerk/base/rust-url-capi/src/string_utils.rs b/netwerk/base/rust-url-capi/src/string_utils.rs new file mode 100644 index 000000000..ae68a60dc --- /dev/null +++ b/netwerk/base/rust-url-capi/src/string_utils.rs @@ -0,0 +1,57 @@ +extern crate libc; +use libc::size_t; + +extern crate std; +use std::ptr; + +use error_mapping::*; + +extern "C" { + fn c_fn_set_size(user: *mut libc::c_void, size: size_t) -> i32; + fn c_fn_get_buffer(user: *mut libc::c_void) -> *mut libc::c_char; +} + +pub trait StringContainer { + fn set_size(&self, size_t) -> i32; + fn get_buffer(&self) -> *mut libc::c_char; + fn assign(&self, content: &str) -> i32; +} + +impl StringContainer for *mut libc::c_void { + fn set_size(&self, size: size_t) -> i32 { + if (*self).is_null() { + return NSError::InvalidArg.error_code(); + } + unsafe { + c_fn_set_size(*self, size); + } + + return NSError::OK.error_code(); + } + fn get_buffer(&self) -> *mut libc::c_char { + if (*self).is_null() { + return 0 as *mut libc::c_char; + } + unsafe { + c_fn_get_buffer(*self) + } + } + fn assign(&self, content: &str) -> i32 { + if (*self).is_null() { + return NSError::InvalidArg.error_code(); + } + + unsafe { + let slice = content.as_bytes(); + c_fn_set_size(*self, slice.len()); + let buf = c_fn_get_buffer(*self); + if buf.is_null() { + return NSError::Failure.error_code(); + } + + ptr::copy(slice.as_ptr(), buf as *mut u8, slice.len()); + } + + NSError::OK.error_code() + } +} diff --git a/netwerk/base/rust-url-capi/test/Makefile b/netwerk/base/rust-url-capi/test/Makefile new file mode 100644 index 000000000..a4e2fd0cf --- /dev/null +++ b/netwerk/base/rust-url-capi/test/Makefile @@ -0,0 +1,4 @@ +all: + cd .. && cargo build + g++ -Wall -o test test.cpp ../target/debug/librust*.a -ldl -lpthread -lrt -lgcc_s -lpthread -lc -lm -std=c++0x + ./test diff --git a/netwerk/base/rust-url-capi/test/test.cpp b/netwerk/base/rust-url-capi/test/test.cpp new file mode 100644 index 000000000..6e90ea43b --- /dev/null +++ b/netwerk/base/rust-url-capi/test/test.cpp @@ -0,0 +1,141 @@ +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include "../src/rust-url-capi.h" + +class StringContainer +{ +public: + StringContainer() + { + mBuffer = nullptr; + mLength = 0; + } + + ~StringContainer() + { + free(mBuffer); + mBuffer = nullptr; + } + + void SetSize(size_t size) + { + mLength = size; + if (mBuffer) { + mBuffer = (char *)realloc(mBuffer, size); + return; + } + mBuffer = (char *)malloc(size); + } + + char * GetBuffer() + { + return mBuffer; + } + + void CheckEquals(const char * ref) { + int32_t refLen = strlen(ref); + printf("CheckEquals: %s (len:%d)\n", ref, refLen); + if (refLen != mLength || strncmp(mBuffer, ref, mLength)) { + printf("\t--- ERROR ---\n"); + printf("Got : "); + fwrite(mBuffer, mLength, 1, stdout); + printf(" (len:%d)\n", mLength); + exit(-1); + } + printf("-> OK\n"); + } +private: + int32_t mLength; + char * mBuffer; +}; + +extern "C" int32_t c_fn_set_size(void * container, size_t size) +{ + ((StringContainer *) container)->SetSize(size); + return 0; +} + +extern "C" char * c_fn_get_buffer(void * container) +{ + return ((StringContainer *) container)->GetBuffer(); +} + +#define TEST_CALL(func, expected) \ +{ \ + int32_t code = func; \ + printf("%s -> code %d\n", #func, code); \ + assert(code == expected); \ + printf("-> OK\n"); \ +} \ + + +int main() { + // Create URL + rusturl_ptr url = rusturl_new("http://example.com/path/some/file.txt", + strlen("http://example.com/path/some/file.txt")); + assert(url); // Check we have a URL + + StringContainer container; + + TEST_CALL(rusturl_get_spec(url, &container), 0); + container.CheckEquals("http://example.com/path/some/file.txt"); + TEST_CALL(rusturl_set_host(url, "test.com", strlen("test.com")), 0); + TEST_CALL(rusturl_get_host(url, &container), 0); + container.CheckEquals("test.com"); + TEST_CALL(rusturl_get_path(url, &container), 0); + container.CheckEquals("/path/some/file.txt"); + TEST_CALL(rusturl_set_path(url, "hello/../else.txt", strlen("hello/../else.txt")), 0); + TEST_CALL(rusturl_get_path(url, &container), 0); + container.CheckEquals("/else.txt"); + TEST_CALL(rusturl_resolve(url, "./bla/file.txt", strlen("./bla/file.txt"), &container), 0); + container.CheckEquals("http://test.com/bla/file.txt"); + TEST_CALL(rusturl_get_scheme(url, &container), 0); + container.CheckEquals("http"); + TEST_CALL(rusturl_set_username(url, "user", strlen("user")), 0); + TEST_CALL(rusturl_get_username(url, &container), 0); + container.CheckEquals("user"); + TEST_CALL(rusturl_get_spec(url, &container), 0); + container.CheckEquals("http://user@test.com/else.txt"); + TEST_CALL(rusturl_set_password(url, "pass", strlen("pass")), 0); + TEST_CALL(rusturl_get_password(url, &container), 0); + container.CheckEquals("pass"); + TEST_CALL(rusturl_get_spec(url, &container), 0); + container.CheckEquals("http://user:pass@test.com/else.txt"); + TEST_CALL(rusturl_set_username(url, "", strlen("")), 0); + TEST_CALL(rusturl_set_password(url, "", strlen("")), 0); + TEST_CALL(rusturl_get_spec(url, &container), 0); + container.CheckEquals("http://test.com/else.txt"); + TEST_CALL(rusturl_set_host_and_port(url, "example.org:1234", strlen("example.org:1234")), 0); + TEST_CALL(rusturl_get_host(url, &container), 0); + container.CheckEquals("example.org"); + assert(rusturl_get_port(url) == 1234); + TEST_CALL(rusturl_set_port(url, "9090", strlen("9090")), 0); + assert(rusturl_get_port(url) == 9090); + TEST_CALL(rusturl_set_query(url, "x=1", strlen("x=1")), 0); + TEST_CALL(rusturl_get_query(url, &container), 0); + container.CheckEquals("x=1"); + TEST_CALL(rusturl_set_fragment(url, "fragment", strlen("fragment")), 0); + TEST_CALL(rusturl_get_fragment(url, &container), 0); + container.CheckEquals("fragment"); + TEST_CALL(rusturl_get_spec(url, &container), 0); + container.CheckEquals("http://example.org:9090/else.txt?x=1#fragment"); + + // Free the URL + rusturl_free(url); + + url = rusturl_new("http://example.com/#", + strlen("http://example.com/#")); + assert(url); // Check we have a URL + + assert(rusturl_has_fragment(url) == 1); + TEST_CALL(rusturl_set_fragment(url, "", 0), 0); + assert(rusturl_has_fragment(url) == 0); + TEST_CALL(rusturl_get_spec(url, &container), 0); + container.CheckEquals("http://example.com/"); + + rusturl_free(url); + + printf("SUCCESS\n"); + return 0; +}
\ No newline at end of file diff --git a/netwerk/base/security-prefs.js b/netwerk/base/security-prefs.js new file mode 100644 index 000000000..9f42745f7 --- /dev/null +++ b/netwerk/base/security-prefs.js @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pref("security.tls.version.min", 1); +pref("security.tls.version.max", 3); +pref("security.tls.version.fallback-limit", 3); +pref("security.tls.insecure_fallback_hosts", ""); +pref("security.tls.unrestricted_rc4_fallback", false); +pref("security.tls.enable_0rtt_data", false); + +pref("security.ssl.treat_unsafe_negotiation_as_broken", false); +pref("security.ssl.require_safe_negotiation", false); +pref("security.ssl.enable_ocsp_stapling", true); +pref("security.ssl.enable_false_start", true); +pref("security.ssl.false_start.require-npn", false); +pref("security.ssl.enable_npn", true); +pref("security.ssl.enable_alpn", true); + +pref("security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", true); +pref("security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256", true); +pref("security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256", true); +pref("security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256", true); +pref("security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384", true); +pref("security.ssl3.ecdhe_rsa_aes_256_gcm_sha384", true); +pref("security.ssl3.ecdhe_rsa_aes_128_sha", true); +pref("security.ssl3.ecdhe_ecdsa_aes_128_sha", true); +pref("security.ssl3.ecdhe_rsa_aes_256_sha", true); +pref("security.ssl3.ecdhe_ecdsa_aes_256_sha", true); +pref("security.ssl3.dhe_rsa_aes_128_sha", true); +pref("security.ssl3.dhe_rsa_aes_256_sha", true); +pref("security.ssl3.rsa_aes_128_sha", true); +pref("security.ssl3.rsa_aes_256_sha", true); +pref("security.ssl3.rsa_des_ede3_sha", true); + +pref("security.content.signature.root_hash", + "97:E8:BA:9C:F1:2F:B3:DE:53:CC:42:A4:E6:57:7E:D6:4D:F4:93:C2:47:B4:14:FE:A0:36:81:8D:38:23:56:0E"); + +pref("security.default_personal_cert", "Ask Every Time"); +pref("security.remember_cert_checkbox_default_setting", true); +pref("security.ask_for_password", 0); +pref("security.password_lifetime", 30); + +// The supported values of this pref are: +// 0: disable detecting Family Safety mode and importing the root +// 1: only attempt to detect Family Safety mode (don't import the root) +// 2: detect Family Safety mode and import the root +// (This is only relevant to Windows 8.1) +pref("security.family_safety.mode", 2); + +pref("security.enterprise_roots.enabled", false); + +pref("security.OCSP.enabled", 1); +pref("security.OCSP.require", false); +pref("security.OCSP.GET.enabled", false); + +pref("security.pki.cert_short_lifetime_in_days", 10); +// NB: Changes to this pref affect CERT_CHAIN_SHA1_POLICY_STATUS telemetry. +// See the comment in CertVerifier.cpp. +// 3 = only allow SHA-1 for certificates issued by an imported root. +pref("security.pki.sha1_enforcement_level", 3); + +// security.pki.name_matching_mode controls how the platform matches hostnames +// to name information in TLS certificates. The possible values are: +// 0: always fall back to the subject common name if necessary (as in, if the +// subject alternative name extension is either not present or does not +// contain any DNS names or IP addresses) +// 1: fall back to the subject common name for certificates valid before 23 +// August 2016 if necessary +// 2: fall back to the subject common name for certificates valid before 23 +// August 2015 if necessary +// 3: only use name information from the subject alternative name extension +#ifdef RELEASE_OR_BETA +pref("security.pki.name_matching_mode", 1); +#else +pref("security.pki.name_matching_mode", 2); +#endif + +// security.pki.netscape_step_up_policy controls how the platform handles the +// id-Netscape-stepUp OID in extended key usage extensions of CA certificates. +// 0: id-Netscape-stepUp is always considered equivalent to id-kp-serverAuth +// 1: it is considered equivalent when the notBefore is before 23 August 2016 +// 2: similarly, but for 23 August 2015 +// 3: it is never considered equivalent +#ifdef RELEASE_OR_BETA +pref("security.pki.netscape_step_up_policy", 1); +#else +pref("security.pki.netscape_step_up_policy", 2); +#endif + +// Configures Certificate Transparency support mode: +// 0: Fully disabled. +// 1: Only collect telemetry. CT qualification checks are not performed. +pref("security.pki.certificate_transparency.mode", 0); + +pref("security.webauth.u2f", false); +pref("security.webauth.u2f_enable_softtoken", false); +pref("security.webauth.u2f_enable_usbtoken", false); + +pref("security.ssl.errorReporting.enabled", true); +pref("security.ssl.errorReporting.url", "https://incoming.telemetry.mozilla.org/submit/sslreports/"); +pref("security.ssl.errorReporting.automatic", false); + +// Impose a maximum age on HPKP headers, to avoid sites getting permanently +// blacking themselves out by setting a bad pin. (60 days by default) +// https://tools.ietf.org/html/rfc7469#section-4.1 +pref("security.cert_pinning.max_max_age_seconds", 5184000); + +// If a request is mixed-content, send an HSTS priming request to attempt to +// see if it is available over HTTPS. +pref("security.mixed_content.send_hsts_priming", true); +#ifdef RELEASE_OR_BETA +// Don't change the order of evaluation of mixed-content and HSTS upgrades +pref("security.mixed_content.use_hsts", false); +#else +// Change the order of evaluation so HSTS upgrades happen before +// mixed-content blocking +pref("security.mixed_content.use_hsts", true); +#endif |