summaryrefslogtreecommitdiffstats
path: root/netwerk/base
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /netwerk/base
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/base')
-rw-r--r--netwerk/base/ARefBase.h31
-rw-r--r--netwerk/base/ArrayBufferInputStream.cpp139
-rw-r--r--netwerk/base/ArrayBufferInputStream.h40
-rw-r--r--netwerk/base/AutoClose.h62
-rw-r--r--netwerk/base/BackgroundFileSaver.cpp1121
-rw-r--r--netwerk/base/BackgroundFileSaver.h397
-rw-r--r--netwerk/base/CacheInfoIPCTypes.h24
-rw-r--r--netwerk/base/CaptivePortalService.cpp428
-rw-r--r--netwerk/base/CaptivePortalService.h79
-rw-r--r--netwerk/base/Dashboard.cpp1182
-rw-r--r--netwerk/base/Dashboard.h91
-rw-r--r--netwerk/base/DashboardTypes.h175
-rw-r--r--netwerk/base/DefaultURI.cpp572
-rw-r--r--netwerk/base/DefaultURI.h59
-rw-r--r--netwerk/base/EventTokenBucket.cpp410
-rw-r--r--netwerk/base/EventTokenBucket.h155
-rw-r--r--netwerk/base/FuzzyLayer.cpp407
-rw-r--r--netwerk/base/FuzzyLayer.h29
-rw-r--r--netwerk/base/FuzzySecurityInfo.cpp177
-rw-r--r--netwerk/base/FuzzySecurityInfo.h30
-rw-r--r--netwerk/base/FuzzySocketControl.cpp192
-rw-r--r--netwerk/base/FuzzySocketControl.h29
-rw-r--r--netwerk/base/IOActivityMonitor.cpp495
-rw-r--r--netwerk/base/IOActivityMonitor.h83
-rw-r--r--netwerk/base/IPv6Utils.h50
-rw-r--r--netwerk/base/InterceptionInfo.cpp63
-rw-r--r--netwerk/base/InterceptionInfo.h44
-rw-r--r--netwerk/base/LoadContextInfo.cpp168
-rw-r--r--netwerk/base/LoadContextInfo.h54
-rw-r--r--netwerk/base/LoadInfo.cpp2372
-rw-r--r--netwerk/base/LoadInfo.h413
-rw-r--r--netwerk/base/LoadTainting.h31
-rw-r--r--netwerk/base/MemoryDownloader.cpp71
-rw-r--r--netwerk/base/MemoryDownloader.h59
-rw-r--r--netwerk/base/NetUtil.sys.mjs453
-rw-r--r--netwerk/base/NetworkConnectivityService.cpp554
-rw-r--r--netwerk/base/NetworkConnectivityService.h76
-rw-r--r--netwerk/base/NetworkDataCountLayer.cpp138
-rw-r--r--netwerk/base/NetworkDataCountLayer.h28
-rw-r--r--netwerk/base/NetworkInfoServiceImpl.h18
-rw-r--r--netwerk/base/PollableEvent.cpp399
-rw-r--r--netwerk/base/PollableEvent.h67
-rw-r--r--netwerk/base/Predictor.cpp2438
-rw-r--r--netwerk/base/Predictor.h458
-rw-r--r--netwerk/base/PrivateBrowsingChannel.h118
-rw-r--r--netwerk/base/ProtocolHandlerInfo.cpp86
-rw-r--r--netwerk/base/ProtocolHandlerInfo.h67
-rw-r--r--netwerk/base/ProxyAutoConfig.cpp943
-rw-r--r--netwerk/base/ProxyAutoConfig.h155
-rw-r--r--netwerk/base/ProxyConfig.h199
-rw-r--r--netwerk/base/RedirectChannelRegistrar.cpp86
-rw-r--r--netwerk/base/RedirectChannelRegistrar.h47
-rw-r--r--netwerk/base/RequestContextService.cpp622
-rw-r--r--netwerk/base/RequestContextService.h44
-rw-r--r--netwerk/base/SSLTokensCache.cpp568
-rw-r--r--netwerk/base/SSLTokensCache.h123
-rw-r--r--netwerk/base/ShutdownLayer.cpp76
-rw-r--r--netwerk/base/ShutdownLayer.h23
-rw-r--r--netwerk/base/SimpleBuffer.cpp85
-rw-r--r--netwerk/base/SimpleBuffer.h57
-rw-r--r--netwerk/base/SimpleChannel.cpp137
-rw-r--r--netwerk/base/SimpleChannel.h152
-rw-r--r--netwerk/base/SimpleChannelParent.cpp97
-rw-r--r--netwerk/base/SimpleChannelParent.h40
-rw-r--r--netwerk/base/TLSServerSocket.cpp446
-rw-r--r--netwerk/base/TLSServerSocket.h81
-rw-r--r--netwerk/base/TRRLoadInfo.cpp865
-rw-r--r--netwerk/base/TRRLoadInfo.h54
-rw-r--r--netwerk/base/ThrottleQueue.cpp410
-rw-r--r--netwerk/base/ThrottleQueue.h66
-rw-r--r--netwerk/base/Tickler.cpp249
-rw-r--r--netwerk/base/Tickler.h132
-rw-r--r--netwerk/base/ascii_pac_utils.js256
-rw-r--r--netwerk/base/http-sfv/Cargo.toml15
-rw-r--r--netwerk/base/http-sfv/SFVService.cpp39
-rw-r--r--netwerk/base/http-sfv/SFVService.h14
-rw-r--r--netwerk/base/http-sfv/moz.build17
-rw-r--r--netwerk/base/http-sfv/nsIStructuredFieldValues.idl290
-rw-r--r--netwerk/base/http-sfv/src/lib.rs873
-rw-r--r--netwerk/base/makecppstring.py17
-rw-r--r--netwerk/base/moz.build319
-rw-r--r--netwerk/base/mozIThirdPartyUtil.idl231
-rw-r--r--netwerk/base/mozurl/.gitignore2
-rw-r--r--netwerk/base/mozurl/Cargo.toml12
-rw-r--r--netwerk/base/mozurl/MozURL.cpp12
-rw-r--r--netwerk/base/mozurl/MozURL.h233
-rw-r--r--netwerk/base/mozurl/cbindgen.toml61
-rw-r--r--netwerk/base/mozurl/moz.build22
-rw-r--r--netwerk/base/mozurl/src/lib.rs574
-rw-r--r--netwerk/base/netCore.h14
-rw-r--r--netwerk/base/nsASocketHandler.h102
-rw-r--r--netwerk/base/nsAsyncRedirectVerifyHelper.cpp279
-rw-r--r--netwerk/base/nsAsyncRedirectVerifyHelper.h121
-rw-r--r--netwerk/base/nsAsyncStreamCopier.cpp405
-rw-r--r--netwerk/base/nsAsyncStreamCopier.h76
-rw-r--r--netwerk/base/nsAuthInformationHolder.cpp61
-rw-r--r--netwerk/base/nsAuthInformationHolder.h44
-rw-r--r--netwerk/base/nsBase64Encoder.cpp54
-rw-r--r--netwerk/base/nsBase64Encoder.h33
-rw-r--r--netwerk/base/nsBaseChannel.cpp1024
-rw-r--r--netwerk/base/nsBaseChannel.h348
-rw-r--r--netwerk/base/nsBaseContentStream.cpp127
-rw-r--r--netwerk/base/nsBaseContentStream.h79
-rw-r--r--netwerk/base/nsBufferedStreams.cpp1197
-rw-r--r--netwerk/base/nsBufferedStreams.h165
-rw-r--r--netwerk/base/nsDNSPrefetch.cpp164
-rw-r--r--netwerk/base/nsDNSPrefetch.h72
-rw-r--r--netwerk/base/nsDirectoryIndexStream.cpp296
-rw-r--r--netwerk/base/nsDirectoryIndexStream.h46
-rw-r--r--netwerk/base/nsDownloader.cpp98
-rw-r--r--netwerk/base/nsDownloader.h36
-rw-r--r--netwerk/base/nsFileStreams.cpp1031
-rw-r--r--netwerk/base/nsFileStreams.h292
-rw-r--r--netwerk/base/nsIArrayBufferInputStream.idl40
-rw-r--r--netwerk/base/nsIAsyncStreamCopier.idl64
-rw-r--r--netwerk/base/nsIAsyncStreamCopier2.idl59
-rw-r--r--netwerk/base/nsIAsyncVerifyRedirectCallback.idl19
-rw-r--r--netwerk/base/nsIAuthInformation.idl117
-rw-r--r--netwerk/base/nsIAuthModule.idl146
-rw-r--r--netwerk/base/nsIAuthPrompt.idl121
-rw-r--r--netwerk/base/nsIAuthPrompt2.idl104
-rw-r--r--netwerk/base/nsIAuthPromptAdapterFactory.idl22
-rw-r--r--netwerk/base/nsIAuthPromptCallback.idl43
-rw-r--r--netwerk/base/nsIAuthPromptProvider.idl34
-rw-r--r--netwerk/base/nsIBackgroundFileSaver.idl183
-rw-r--r--netwerk/base/nsIBufferedStreams.idl47
-rw-r--r--netwerk/base/nsIByteRangeRequest.idl27
-rw-r--r--netwerk/base/nsICacheInfoChannel.idl207
-rw-r--r--netwerk/base/nsICachingChannel.idl110
-rw-r--r--netwerk/base/nsICancelable.idl24
-rw-r--r--netwerk/base/nsICaptivePortalService.idl83
-rw-r--r--netwerk/base/nsIChannel.idl405
-rw-r--r--netwerk/base/nsIChannelEventSink.idl112
-rw-r--r--netwerk/base/nsIChildChannel.idl34
-rw-r--r--netwerk/base/nsIClassOfService.idl103
-rw-r--r--netwerk/base/nsIClassifiedChannel.idl201
-rw-r--r--netwerk/base/nsIContentSniffer.idl35
-rw-r--r--netwerk/base/nsIDHCPClient.idl19
-rw-r--r--netwerk/base/nsIDashboard.idl64
-rw-r--r--netwerk/base/nsIDashboardEventNotifier.idl23
-rw-r--r--netwerk/base/nsIDownloader.idl50
-rw-r--r--netwerk/base/nsIEncodedChannel.idl60
-rw-r--r--netwerk/base/nsIExternalProtocolHandler.idl17
-rw-r--r--netwerk/base/nsIFileStreams.idl239
-rw-r--r--netwerk/base/nsIFileURL.idl42
-rw-r--r--netwerk/base/nsIForcePendingChannel.idl22
-rw-r--r--netwerk/base/nsIFormPOSTActionChannel.idl17
-rw-r--r--netwerk/base/nsIHttpAuthenticatorCallback.idl30
-rw-r--r--netwerk/base/nsIHttpPushListener.idl36
-rw-r--r--netwerk/base/nsIIOService.idl362
-rw-r--r--netwerk/base/nsIIncrementalDownload.idl109
-rw-r--r--netwerk/base/nsIIncrementalStreamLoader.idl100
-rw-r--r--netwerk/base/nsIInputStreamChannel.idl64
-rw-r--r--netwerk/base/nsIInputStreamPump.idl66
-rw-r--r--netwerk/base/nsIInterceptionInfo.idl73
-rw-r--r--netwerk/base/nsILoadContextInfo.idl86
-rw-r--r--netwerk/base/nsILoadGroup.idl105
-rw-r--r--netwerk/base/nsILoadGroupChild.idl41
-rw-r--r--netwerk/base/nsILoadInfo.idl1535
-rw-r--r--netwerk/base/nsIMIMEInputStream.idl48
-rw-r--r--netwerk/base/nsIMultiPartChannel.idl51
-rw-r--r--netwerk/base/nsINestedURI.idl75
-rw-r--r--netwerk/base/nsINetAddr.idl88
-rw-r--r--netwerk/base/nsINetUtil.idl223
-rw-r--r--netwerk/base/nsINetworkConnectivityService.idl44
-rw-r--r--netwerk/base/nsINetworkInfoService.idl56
-rw-r--r--netwerk/base/nsINetworkInterceptController.idl251
-rw-r--r--netwerk/base/nsINetworkLinkService.idl154
-rw-r--r--netwerk/base/nsINetworkPredictor.idl194
-rw-r--r--netwerk/base/nsINetworkPredictorVerifier.idl40
-rw-r--r--netwerk/base/nsINullChannel.idl16
-rw-r--r--netwerk/base/nsIOService.cpp2194
-rw-r--r--netwerk/base/nsIOService.h279
-rw-r--r--netwerk/base/nsIParentChannel.idl76
-rw-r--r--netwerk/base/nsIParentRedirectingChannel.idl68
-rw-r--r--netwerk/base/nsIPermission.idl82
-rw-r--r--netwerk/base/nsIPermissionManager.idl242
-rw-r--r--netwerk/base/nsIPrivateBrowsingChannel.idl58
-rw-r--r--netwerk/base/nsIProgressEventSink.idl72
-rw-r--r--netwerk/base/nsIPrompt.idl103
-rw-r--r--netwerk/base/nsIProtocolHandler.idl317
-rw-r--r--netwerk/base/nsIProtocolProxyCallback.idl42
-rw-r--r--netwerk/base/nsIProtocolProxyFilter.idl97
-rw-r--r--netwerk/base/nsIProtocolProxyService.idl330
-rw-r--r--netwerk/base/nsIProtocolProxyService2.idl32
-rw-r--r--netwerk/base/nsIProxiedChannel.idl36
-rw-r--r--netwerk/base/nsIProxiedProtocolHandler.idl36
-rw-r--r--netwerk/base/nsIProxyInfo.idl106
-rw-r--r--netwerk/base/nsIRandomGenerator.idl46
-rw-r--r--netwerk/base/nsIRedirectChannelRegistrar.idl71
-rw-r--r--netwerk/base/nsIRedirectHistoryEntry.idl34
-rw-r--r--netwerk/base/nsIRedirectResultListener.idl22
-rw-r--r--netwerk/base/nsIRequest.idl332
-rw-r--r--netwerk/base/nsIRequestContext.idl158
-rw-r--r--netwerk/base/nsIRequestObserver.idl43
-rw-r--r--netwerk/base/nsIRequestObserverProxy.idl31
-rw-r--r--netwerk/base/nsIResumableChannel.idl39
-rw-r--r--netwerk/base/nsISecCheckWrapChannel.idl24
-rw-r--r--netwerk/base/nsISecureBrowserUI.idl21
-rw-r--r--netwerk/base/nsISensitiveInfoHiddenURI.idl17
-rw-r--r--netwerk/base/nsISerializationHelper.idl29
-rw-r--r--netwerk/base/nsIServerSocket.idl269
-rw-r--r--netwerk/base/nsISimpleStreamListener.idl25
-rw-r--r--netwerk/base/nsISimpleURIMutator.idl15
-rw-r--r--netwerk/base/nsISocketFilter.idl53
-rw-r--r--netwerk/base/nsISocketTransport.idl382
-rw-r--r--netwerk/base/nsISocketTransportService.idl162
-rw-r--r--netwerk/base/nsISpeculativeConnect.idl98
-rw-r--r--netwerk/base/nsIStandardURL.idl85
-rw-r--r--netwerk/base/nsIStreamListener.idl38
-rw-r--r--netwerk/base/nsIStreamListenerTee.idl50
-rw-r--r--netwerk/base/nsIStreamLoader.idl77
-rw-r--r--netwerk/base/nsIStreamTransportService.idl49
-rw-r--r--netwerk/base/nsISyncStreamListener.idl19
-rw-r--r--netwerk/base/nsISystemProxySettings.idl42
-rw-r--r--netwerk/base/nsITLSServerSocket.idl183
-rw-r--r--netwerk/base/nsIThreadRetargetableRequest.idl43
-rw-r--r--netwerk/base/nsIThreadRetargetableStreamListener.idl49
-rw-r--r--netwerk/base/nsIThrottledInputChannel.idl87
-rw-r--r--netwerk/base/nsITimedChannel.idl128
-rw-r--r--netwerk/base/nsITraceableChannel.idl37
-rw-r--r--netwerk/base/nsITransport.idl162
-rw-r--r--netwerk/base/nsIUDPSocket.idl407
-rw-r--r--netwerk/base/nsIURI.idl332
-rw-r--r--netwerk/base/nsIURIMutator.idl540
-rw-r--r--netwerk/base/nsIURIMutatorUtils.cpp27
-rw-r--r--netwerk/base/nsIURIWithSpecialOrigin.idl20
-rw-r--r--netwerk/base/nsIURL.idl130
-rw-r--r--netwerk/base/nsIURLParser.idl98
-rw-r--r--netwerk/base/nsIUploadChannel.idl56
-rw-r--r--netwerk/base/nsIUploadChannel2.idl57
-rw-r--r--netwerk/base/nsIncrementalDownload.cpp878
-rw-r--r--netwerk/base/nsIncrementalStreamLoader.cpp188
-rw-r--r--netwerk/base/nsIncrementalStreamLoader.h52
-rw-r--r--netwerk/base/nsInputStreamChannel.cpp101
-rw-r--r--netwerk/base/nsInputStreamChannel.h43
-rw-r--r--netwerk/base/nsInputStreamPump.cpp795
-rw-r--r--netwerk/base/nsInputStreamPump.h129
-rw-r--r--netwerk/base/nsLoadGroup.cpp1158
-rw-r--r--netwerk/base/nsLoadGroup.h111
-rw-r--r--netwerk/base/nsMIMEInputStream.cpp500
-rw-r--r--netwerk/base/nsMIMEInputStream.h27
-rw-r--r--netwerk/base/nsMediaFragmentURIParser.cpp354
-rw-r--r--netwerk/base/nsMediaFragmentURIParser.h99
-rw-r--r--netwerk/base/nsNetAddr.cpp138
-rw-r--r--netwerk/base/nsNetAddr.h30
-rw-r--r--netwerk/base/nsNetSegmentUtils.h20
-rw-r--r--netwerk/base/nsNetUtil.cpp4088
-rw-r--r--netwerk/base/nsNetUtil.h1105
-rw-r--r--netwerk/base/nsPACMan.cpp1009
-rw-r--r--netwerk/base/nsPACMan.h295
-rw-r--r--netwerk/base/nsPISocketTransportService.idl59
-rw-r--r--netwerk/base/nsPreloadedStream.cpp148
-rw-r--r--netwerk/base/nsPreloadedStream.h57
-rw-r--r--netwerk/base/nsProtocolProxyService.cpp2459
-rw-r--r--netwerk/base/nsProtocolProxyService.h420
-rw-r--r--netwerk/base/nsProxyInfo.cpp216
-rw-r--r--netwerk/base/nsProxyInfo.h100
-rw-r--r--netwerk/base/nsReadLine.h131
-rw-r--r--netwerk/base/nsRedirectHistoryEntry.cpp43
-rw-r--r--netwerk/base/nsRedirectHistoryEntry.h37
-rw-r--r--netwerk/base/nsRequestObserverProxy.cpp175
-rw-r--r--netwerk/base/nsRequestObserverProxy.h56
-rw-r--r--netwerk/base/nsSerializationHelper.cpp54
-rw-r--r--netwerk/base/nsSerializationHelper.h35
-rw-r--r--netwerk/base/nsServerSocket.cpp582
-rw-r--r--netwerk/base/nsServerSocket.h70
-rw-r--r--netwerk/base/nsSimpleNestedURI.cpp228
-rw-r--r--netwerk/base/nsSimpleNestedURI.h112
-rw-r--r--netwerk/base/nsSimpleStreamListener.cpp69
-rw-r--r--netwerk/base/nsSimpleStreamListener.h35
-rw-r--r--netwerk/base/nsSimpleURI.cpp794
-rw-r--r--netwerk/base/nsSimpleURI.h164
-rw-r--r--netwerk/base/nsSocketTransport2.cpp3411
-rw-r--r--netwerk/base/nsSocketTransport2.h510
-rw-r--r--netwerk/base/nsSocketTransportService2.cpp1899
-rw-r--r--netwerk/base/nsSocketTransportService2.h355
-rw-r--r--netwerk/base/nsStandardURL.cpp4034
-rw-r--r--netwerk/base/nsStandardURL.h626
-rw-r--r--netwerk/base/nsStreamListenerTee.cpp185
-rw-r--r--netwerk/base/nsStreamListenerTee.h45
-rw-r--r--netwerk/base/nsStreamListenerWrapper.cpp55
-rw-r--r--netwerk/base/nsStreamListenerWrapper.h65
-rw-r--r--netwerk/base/nsStreamLoader.cpp140
-rw-r--r--netwerk/base/nsStreamLoader.h57
-rw-r--r--netwerk/base/nsStreamTransportService.cpp429
-rw-r--r--netwerk/base/nsStreamTransportService.h47
-rw-r--r--netwerk/base/nsSyncStreamListener.cpp169
-rw-r--r--netwerk/base/nsSyncStreamListener.h43
-rw-r--r--netwerk/base/nsTransportUtils.cpp133
-rw-r--r--netwerk/base/nsTransportUtils.h25
-rw-r--r--netwerk/base/nsUDPSocket.cpp1553
-rw-r--r--netwerk/base/nsUDPSocket.h118
-rw-r--r--netwerk/base/nsURIHashKey.h71
-rw-r--r--netwerk/base/nsURLHelper.cpp1356
-rw-r--r--netwerk/base/nsURLHelper.h372
-rw-r--r--netwerk/base/nsURLHelperOSX.cpp205
-rw-r--r--netwerk/base/nsURLHelperUnix.cpp109
-rw-r--r--netwerk/base/nsURLHelperWin.cpp111
-rw-r--r--netwerk/base/nsURLParsers.cpp644
-rw-r--r--netwerk/base/nsURLParsers.h119
-rw-r--r--netwerk/base/rust-helper/Cargo.toml10
-rw-r--r--netwerk/base/rust-helper/cbindgen.toml18
-rw-r--r--netwerk/base/rust-helper/moz.build12
-rw-r--r--netwerk/base/rust-helper/src/lib.rs360
305 files changed, 79487 insertions, 0 deletions
diff --git a/netwerk/base/ARefBase.h b/netwerk/base/ARefBase.h
new file mode 100644
index 0000000000..207b5fe03c
--- /dev/null
+++ b/netwerk/base/ARefBase.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ARefBase_h
+#define mozilla_net_ARefBase_h
+
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace net {
+
+// This is an abstract class that can be pointed to by either
+// nsCOMPtr or nsRefPtr. nsHttpConnectionMgr uses it for generic
+// objects that need to be reference counted - similiar to nsISupports
+// but it may or may not be xpcom.
+
+class ARefBase {
+ public:
+ ARefBase() = default;
+ virtual ~ARefBase() = default;
+
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/ArrayBufferInputStream.cpp b/netwerk/base/ArrayBufferInputStream.cpp
new file mode 100644
index 0000000000..b8c26d03ef
--- /dev/null
+++ b/netwerk/base/ArrayBufferInputStream.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include "ArrayBufferInputStream.h"
+#include "nsStreamUtils.h"
+#include "js/ArrayBuffer.h" // JS::{GetArrayBuffer{ByteLength,Data},IsArrayBufferObject}
+#include "js/RootingAPI.h" // JS::{Handle,Rooted}
+#include "js/Value.h" // JS::Value
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+using mozilla::dom::RootingCx;
+
+NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream,
+ nsIInputStream);
+
+NS_IMETHODIMP
+ArrayBufferInputStream::SetDataFromJS(JS::Handle<JS::Value> aBuffer,
+ uint64_t aByteOffset, uint64_t aLength) {
+ NS_ASSERT_OWNINGTHREAD(ArrayBufferInputStream);
+
+ if (!aBuffer.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::Rooted<JSObject*> arrayBuffer(RootingCx(), &aBuffer.toObject());
+ if (!JS::IsArrayBufferObject(arrayBuffer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t buflen = JS::GetArrayBufferByteLength(arrayBuffer);
+ uint64_t offset = std::min(buflen, aByteOffset);
+ uint64_t bufferLength = std::min(buflen - offset, aLength);
+
+ // Prevent truncation.
+ if (bufferLength > UINT32_MAX) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mArrayBuffer = mozilla::MakeUniqueFallible<uint8_t[]>(bufferLength);
+ if (!mArrayBuffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mBufferLength = bufferLength;
+
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ uint8_t* src = JS::GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset;
+ memcpy(&mArrayBuffer[0], src, mBufferLength);
+ return NS_OK;
+}
+
+nsresult ArrayBufferInputStream::SetData(mozilla::UniquePtr<uint8_t[]> aBytes,
+ uint64_t aByteLen) {
+ mArrayBuffer = std::move(aBytes);
+ mBufferLength = aByteLen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Close() {
+ mClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Available(uint64_t* aCount) {
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ if (mArrayBuffer) {
+ *aCount = mBufferLength ? mBufferLength - mPos : 0;
+ } else {
+ *aCount = 0;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::StreamStatus() {
+ return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aReadCount) {
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t aCount, uint32_t* result) {
+ NS_ASSERTION(result, "null ptr");
+ NS_ASSERTION(mBufferLength >= mPos, "bad stream state");
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength),
+ "stream inited incorrectly");
+
+ *result = 0;
+ while (mPos < mBufferLength) {
+ uint32_t remaining = mBufferLength - mPos;
+ MOZ_ASSERT(mArrayBuffer);
+
+ uint32_t count = std::min(aCount, remaining);
+ if (count == 0) {
+ break;
+ }
+
+ uint32_t written;
+ nsresult rv = writer(this, closure, (char*)&mArrayBuffer[0] + mPos, *result,
+ count, &written);
+ if (NS_FAILED(rv)) {
+ // InputStreams do not propagate errors to caller.
+ return NS_OK;
+ }
+
+ NS_ASSERTION(written <= count,
+ "writer should not write more than we asked it to write");
+ mPos += written;
+ *result += written;
+ aCount -= written;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = true;
+ return NS_OK;
+}
diff --git a/netwerk/base/ArrayBufferInputStream.h b/netwerk/base/ArrayBufferInputStream.h
new file mode 100644
index 0000000000..e3d4ea62f0
--- /dev/null
+++ b/netwerk/base/ArrayBufferInputStream.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ArrayBufferInputStream_h
+#define ArrayBufferInputStream_h
+
+#include "nsIArrayBufferInputStream.h"
+#include "js/Value.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "nsISupportsImpl.h"
+
+#define NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID \
+ "@mozilla.org/io/arraybuffer-input-stream;1"
+#define NS_ARRAYBUFFERINPUTSTREAM_CID \
+ { /* 3014dde6-aa1c-41db-87d0-48764a3710f6 */ \
+ 0x3014dde6, 0xaa1c, 0x41db, { \
+ 0x87, 0xd0, 0x48, 0x76, 0x4a, 0x37, 0x10, 0xf6 \
+ } \
+ }
+
+class ArrayBufferInputStream : public nsIArrayBufferInputStream {
+ public:
+ ArrayBufferInputStream() = default;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIARRAYBUFFERINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAM
+
+ private:
+ virtual ~ArrayBufferInputStream() = default;
+ mozilla::UniquePtr<uint8_t[]> mArrayBuffer;
+ uint32_t mBufferLength{0};
+ uint32_t mPos{0};
+ bool mClosed{false};
+};
+
+#endif // ArrayBufferInputStream_h
diff --git a/netwerk/base/AutoClose.h b/netwerk/base/AutoClose.h
new file mode 100644
index 0000000000..a0e3a48e17
--- /dev/null
+++ b/netwerk/base/AutoClose.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_AutoClose_h
+#define mozilla_net_AutoClose_h
+
+#include "nsCOMPtr.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+// A container for XPCOM streams (e.g. nsIAsyncInputStream) and other
+// refcounted classes that need to have the Close() method called explicitly
+// before they are destroyed.
+template <typename T>
+class AutoClose {
+ public:
+ AutoClose() : mMutex("net::AutoClose.mMutex") {}
+ ~AutoClose() { CloseAndRelease(); }
+
+ explicit operator bool() {
+ MutexAutoLock lock(mMutex);
+ return mPtr;
+ }
+
+ already_AddRefed<T> forget() {
+ MutexAutoLock lock(mMutex);
+ return mPtr.forget();
+ }
+
+ void takeOver(nsCOMPtr<T>& rhs) { TakeOverInternal(rhs.forget()); }
+
+ void CloseAndRelease() { TakeOverInternal(nullptr); }
+
+ private:
+ void TakeOverInternal(already_AddRefed<T>&& aOther) {
+ nsCOMPtr<T> ptr(std::move(aOther));
+ {
+ MutexAutoLock lock(mMutex);
+ ptr.swap(mPtr);
+ }
+
+ if (ptr) {
+ ptr->Close();
+ }
+ }
+
+ void operator=(const AutoClose<T>&) = delete;
+ AutoClose(const AutoClose<T>&) = delete;
+
+ nsCOMPtr<T> mPtr;
+ Mutex mMutex MOZ_UNANNOTATED;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AutoClose_h
diff --git a/netwerk/base/BackgroundFileSaver.cpp b/netwerk/base/BackgroundFileSaver.cpp
new file mode 100644
index 0000000000..3af6b8eb26
--- /dev/null
+++ b/netwerk/base/BackgroundFileSaver.cpp
@@ -0,0 +1,1121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BackgroundFileSaver.h"
+
+#include "ScopedNSSTypes.h"
+#include "mozilla/ArrayAlgorithm.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Telemetry.h"
+#include "nsCOMArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDependentSubstring.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIFile.h"
+#include "nsIMutableArray.h"
+#include "nsIPipe.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "pk11pub.h"
+#include "secoidt.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+# include <softpub.h>
+# include <wintrust.h>
+#endif // XP_WIN
+
+namespace mozilla {
+namespace net {
+
+// MOZ_LOG=BackgroundFileSaver:5
+static LazyLogModule prlog("BackgroundFileSaver");
+#define LOG(args) MOZ_LOG(prlog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(prlog, mozilla::LogLevel::Debug)
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+/**
+ * Buffer size for writing to the output file or reading from the input file.
+ */
+#define BUFFERED_IO_SIZE (1024 * 32)
+
+/**
+ * When this upper limit is reached, the original request is suspended.
+ */
+#define REQUEST_SUSPEND_AT (1024 * 1024 * 4)
+
+/**
+ * When this lower limit is reached, the original request is resumed.
+ */
+#define REQUEST_RESUME_AT (1024 * 1024 * 2)
+
+////////////////////////////////////////////////////////////////////////////////
+//// NotifyTargetChangeRunnable
+
+/**
+ * Runnable object used to notify the control thread that file contents will now
+ * be saved to the specified file.
+ */
+class NotifyTargetChangeRunnable final : public Runnable {
+ public:
+ NotifyTargetChangeRunnable(BackgroundFileSaver* aSaver, nsIFile* aTarget)
+ : Runnable("net::NotifyTargetChangeRunnable"),
+ mSaver(aSaver),
+ mTarget(aTarget) {}
+
+ NS_IMETHOD Run() override { return mSaver->NotifyTargetChange(mTarget); }
+
+ private:
+ RefPtr<BackgroundFileSaver> mSaver;
+ nsCOMPtr<nsIFile> mTarget;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaver
+
+uint32_t BackgroundFileSaver::sThreadCount = 0;
+uint32_t BackgroundFileSaver::sTelemetryMaxThreadCount = 0;
+
+BackgroundFileSaver::BackgroundFileSaver() {
+ LOG(("Created BackgroundFileSaver [this = %p]", this));
+}
+
+BackgroundFileSaver::~BackgroundFileSaver() {
+ LOG(("Destroying BackgroundFileSaver [this = %p]", this));
+}
+
+// Called on the control thread.
+nsresult BackgroundFileSaver::Init() {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ NS_NewPipe2(getter_AddRefs(mPipeInputStream),
+ getter_AddRefs(mPipeOutputStream), true, true, 0,
+ HasInfiniteBuffer() ? UINT32_MAX : 0);
+
+ mControlEventTarget = GetCurrentSerialEventTarget();
+ NS_ENSURE_TRUE(mControlEventTarget, NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv = NS_CreateBackgroundTaskQueue("BgFileSaver",
+ getter_AddRefs(mBackgroundET));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ sThreadCount++;
+ if (sThreadCount > sTelemetryMaxThreadCount) {
+ sTelemetryMaxThreadCount = sThreadCount;
+ }
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver** aObserver) {
+ NS_ENSURE_ARG_POINTER(aObserver);
+ *aObserver = do_AddRef(mObserver).take();
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver* aObserver) {
+ mObserver = aObserver;
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::EnableAppend() {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ MutexAutoLock lock(mLock);
+ mAppend = true;
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::SetTarget(nsIFile* aTarget, bool aKeepPartial) {
+ NS_ENSURE_ARG(aTarget);
+ {
+ MutexAutoLock lock(mLock);
+ if (!mInitialTarget) {
+ aTarget->Clone(getter_AddRefs(mInitialTarget));
+ mInitialTargetKeepPartial = aKeepPartial;
+ } else {
+ aTarget->Clone(getter_AddRefs(mRenamedTarget));
+ mRenamedTargetKeepPartial = aKeepPartial;
+ }
+ }
+
+ // After the worker thread wakes up because attention is requested, it will
+ // rename or create the target file as requested, and start copying data.
+ return GetWorkerThreadAttention(true);
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::Finish(nsresult aStatus) {
+ nsresult rv;
+
+ // This will cause the NS_AsyncCopy operation, if it's in progress, to consume
+ // all the data that is still in the pipe, and then finish.
+ rv = mPipeOutputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure that, when we get attention from the worker thread, if no pending
+ // rename operation is waiting, the operation will complete.
+ {
+ MutexAutoLock lock(mLock);
+ mFinishRequested = true;
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+ }
+
+ // After the worker thread wakes up because attention is requested, it will
+ // process the completion conditions, detect that completion is requested, and
+ // notify the main thread of the completion. If this function was called with
+ // a success code, we wait for the copy to finish before processing the
+ // completion conditions, otherwise we interrupt the copy immediately.
+ return GetWorkerThreadAttention(NS_FAILED(aStatus));
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::EnableSha256() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can't enable sha256 or initialize NSS off the main thread");
+ // Ensure Personal Security Manager is initialized. This is required for
+ // PK11_* operations to work.
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MutexAutoLock lock(mLock);
+ mSha256Enabled = true; // this will be read by the worker thread
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::GetSha256Hash(nsACString& aHash) {
+ MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread");
+ // We acquire a lock because mSha256 is written on the worker thread.
+ MutexAutoLock lock(mLock);
+ if (mSha256.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aHash = mSha256;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::EnableSignatureInfo() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can't enable signature extraction off the main thread");
+ // Ensure Personal Security Manager is initialized.
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MutexAutoLock lock(mLock);
+ mSignatureInfoEnabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::GetSignatureInfo(
+ nsTArray<nsTArray<nsTArray<uint8_t>>>& aSignatureInfo) {
+ MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread");
+ // We acquire a lock because mSignatureInfo is written on the worker thread.
+ MutexAutoLock lock(mLock);
+ if (!mComplete || !mSignatureInfoEnabled) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ for (const auto& signatureChain : mSignatureInfo) {
+ aSignatureInfo.AppendElement(TransformIntoNewArray(
+ signatureChain, [](const auto& element) { return element.Clone(); }));
+ }
+ return NS_OK;
+}
+
+// Called on the control thread.
+nsresult BackgroundFileSaver::GetWorkerThreadAttention(
+ bool aShouldInterruptCopy) {
+ nsresult rv;
+
+ MutexAutoLock lock(mLock);
+
+ // We only require attention one time. If this function is called two times
+ // before the worker thread wakes up, and the first has aShouldInterruptCopy
+ // false and the second true, we won't forcibly interrupt the copy from the
+ // control thread. However, that never happens, because calling Finish with a
+ // success code is the only case that may result in aShouldInterruptCopy being
+ // false. In that case, we won't call this function again, because consumers
+ // should not invoke other methods on the control thread after calling Finish.
+ // And in any case, Finish already closes one end of the pipe, causing the
+ // copy to finish properly on its own.
+ if (mWorkerThreadAttentionRequested) {
+ return NS_OK;
+ }
+
+ if (!mAsyncCopyContext) {
+ // Background event queues are not shutdown and could be called after
+ // the queue is reset to null. To match the behavior of nsIThread
+ // return NS_ERROR_UNEXPECTED
+ if (!mBackgroundET) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Copy is not in progress, post an event to handle the change manually.
+ rv = mBackgroundET->Dispatch(
+ NewRunnableMethod("net::BackgroundFileSaver::ProcessAttention", this,
+ &BackgroundFileSaver::ProcessAttention),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ } else if (aShouldInterruptCopy) {
+ // Interrupt the copy. The copy will be resumed, if needed, by the
+ // ProcessAttention function, invoked by the AsyncCopyCallback function.
+ NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
+ }
+
+ // Indicate that attention has been requested successfully, there is no need
+ // to post another event until the worker thread processes the current one.
+ mWorkerThreadAttentionRequested = true;
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+// static
+void BackgroundFileSaver::AsyncCopyCallback(void* aClosure, nsresult aStatus) {
+ // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object
+ // alive even if other references disappeared. At the end of this method,
+ // we've finished using the object and can safely release our reference.
+ RefPtr<BackgroundFileSaver> self =
+ dont_AddRef((BackgroundFileSaver*)aClosure);
+ {
+ MutexAutoLock lock(self->mLock);
+
+ // Now that the copy was interrupted or terminated, any notification from
+ // the control thread requires an event to be posted to the worker thread.
+ self->mAsyncCopyContext = nullptr;
+
+ // When detecting failures, ignore the status code we use to interrupt.
+ if (NS_FAILED(aStatus) && aStatus != NS_ERROR_ABORT &&
+ NS_SUCCEEDED(self->mStatus)) {
+ self->mStatus = aStatus;
+ }
+ }
+
+ (void)self->ProcessAttention();
+}
+
+// Called on the worker thread.
+nsresult BackgroundFileSaver::ProcessAttention() {
+ nsresult rv;
+
+ // This function is called whenever the attention of the worker thread has
+ // been requested. This may happen in these cases:
+ // * We are about to start the copy for the first time. In this case, we are
+ // called from an event posted on the worker thread from the control thread
+ // by GetWorkerThreadAttention, and mAsyncCopyContext is null.
+ // * We have interrupted the copy for some reason. In this case, we are
+ // called by AsyncCopyCallback, and mAsyncCopyContext is null.
+ // * We are currently executing ProcessStateChange, and attention is requested
+ // by the control thread, for example because SetTarget or Finish have been
+ // called. In this case, we are called from from an event posted through
+ // GetWorkerThreadAttention. While mAsyncCopyContext was always null when
+ // the event was posted, at this point mAsyncCopyContext may not be null
+ // anymore, because ProcessStateChange may have started the copy before the
+ // event that called this function was processed on the worker thread.
+ // If mAsyncCopyContext is not null, we interrupt the copy and re-enter
+ // through AsyncCopyCallback. This allows us to check if, for instance, we
+ // should rename the target file. We will then restart the copy if needed.
+
+ // mAsyncCopyContext is only written on the worker thread (which we are on)
+ MOZ_ASSERT(!NS_IsMainThread());
+ {
+ // Even though we're the only thread that writes this, we have to take the
+ // lock
+ MutexAutoLock lock(mLock);
+ if (mAsyncCopyContext) {
+ NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT);
+ return NS_OK;
+ }
+ }
+ // Use the current shared state to determine the next operation to execute.
+ rv = ProcessStateChange();
+ if (NS_FAILED(rv)) {
+ // If something failed while processing, terminate the operation now.
+ {
+ MutexAutoLock lock(mLock);
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+ }
+ // Ensure we notify completion now that the operation failed.
+ CheckCompletion();
+ }
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+nsresult BackgroundFileSaver::ProcessStateChange() {
+ nsresult rv;
+
+ // We might have been notified because the operation is complete, verify.
+ if (CheckCompletion()) {
+ return NS_OK;
+ }
+
+ // Get a copy of the current shared state for the worker thread.
+ nsCOMPtr<nsIFile> initialTarget;
+ bool initialTargetKeepPartial;
+ nsCOMPtr<nsIFile> renamedTarget;
+ bool renamedTargetKeepPartial;
+ bool sha256Enabled;
+ bool append;
+ {
+ MutexAutoLock lock(mLock);
+
+ initialTarget = mInitialTarget;
+ initialTargetKeepPartial = mInitialTargetKeepPartial;
+ renamedTarget = mRenamedTarget;
+ renamedTargetKeepPartial = mRenamedTargetKeepPartial;
+ sha256Enabled = mSha256Enabled;
+ append = mAppend;
+
+ // From now on, another attention event needs to be posted if state changes.
+ mWorkerThreadAttentionRequested = false;
+ }
+
+ // The initial target can only be null if it has never been assigned. In this
+ // case, there is nothing to do since we never created any output file.
+ if (!initialTarget) {
+ return NS_OK;
+ }
+
+ // Determine if we are processing the attention request for the first time.
+ bool isContinuation = !!mActualTarget;
+ if (!isContinuation) {
+ // Assign the target file for the first time.
+ mActualTarget = initialTarget;
+ mActualTargetKeepPartial = initialTargetKeepPartial;
+ }
+
+ // Verify whether we have actually been instructed to use a different file.
+ // This may happen the first time this function is executed, if SetTarget was
+ // called two times before the worker thread processed the attention request.
+ bool equalToCurrent = false;
+ if (renamedTarget) {
+ rv = mActualTarget->Equals(renamedTarget, &equalToCurrent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!equalToCurrent) {
+ // If we were asked to rename the file but the initial file did not exist,
+ // we simply create the file in the renamed location. We avoid this check
+ // if we have already started writing the output file ourselves.
+ bool exists = true;
+ if (!isContinuation) {
+ rv = mActualTarget->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (exists) {
+ // We are moving the previous target file to a different location.
+ nsCOMPtr<nsIFile> renamedTargetParentDir;
+ rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString renamedTargetName;
+ rv = renamedTarget->GetLeafName(renamedTargetName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We must delete any existing target file before moving the current
+ // one.
+ rv = renamedTarget->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ rv = renamedTarget->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Move the file. If this fails, we still reference the original file
+ // in mActualTarget, so that it is deleted if requested. If this
+ // succeeds, the nsIFile instance referenced by mActualTarget mutates
+ // and starts pointing to the new file, but we'll discard the reference.
+ rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We should not only update the mActualTarget with renameTarget when
+ // they point to the different files.
+ // In this way, if mActualTarget and renamedTarget point to the same file
+ // with different addresses, "CheckCompletion()" will return false
+ // forever.
+ }
+
+ // Update mActualTarget with renameTarget,
+ // even if they point to the same file.
+ mActualTarget = renamedTarget;
+ mActualTargetKeepPartial = renamedTargetKeepPartial;
+ }
+
+ // Notify if the target file name actually changed.
+ if (!equalToCurrent) {
+ // We must clone the nsIFile instance because mActualTarget is not
+ // immutable, it may change if the target is renamed later.
+ nsCOMPtr<nsIFile> actualTargetToNotify;
+ rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<NotifyTargetChangeRunnable> event =
+ new NotifyTargetChangeRunnable(this, actualTargetToNotify);
+ NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
+
+ rv = mControlEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (isContinuation) {
+ // The pending rename operation might be the last task before finishing. We
+ // may return here only if we have already created the target file.
+ if (CheckCompletion()) {
+ return NS_OK;
+ }
+
+ // Even if the operation did not complete, the pipe input stream may be
+ // empty and may have been closed already. We detect this case using the
+ // Available property, because it never returns an error if there is more
+ // data to be consumed. If the pipe input stream is closed, we just exit
+ // and wait for more calls like SetTarget or Finish to be invoked on the
+ // control thread. However, we still truncate the file or create the
+ // initial digest context if we are expected to do that.
+ uint64_t available;
+ rv = mPipeInputStream->Available(&available);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+ }
+
+ // Create the digest if requested and NSS hasn't been shut down.
+ if (sha256Enabled && mDigest.isNothing()) {
+ mDigest.emplace(Digest());
+ mDigest->Begin(SEC_OID_SHA256);
+ }
+
+ // When we are requested to append to an existing file, we should read the
+ // existing data and ensure we include it as part of the final hash.
+ if (mDigest.isSome() && append && !isContinuation) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mActualTarget,
+ PR_RDONLY | nsIFile::OS_READAHEAD);
+ if (rv != NS_ERROR_FILE_NOT_FOUND) {
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Try to clean up the inputStream if an error occurs.
+ auto closeGuard =
+ mozilla::MakeScopeExit([&] { Unused << inputStream->Close(); });
+
+ char buffer[BUFFERED_IO_SIZE];
+ while (true) {
+ uint32_t count;
+ rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count == 0) {
+ // We reached the end of the file.
+ break;
+ }
+
+ rv = mDigest->Update(BitwiseCast<unsigned char*, char*>(buffer), count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The pending resume operation may have been cancelled by the control
+ // thread while the worker thread was reading in the existing file.
+ // Abort reading in the original file in that case, as the digest will
+ // be discarded anyway.
+ MutexAutoLock lock(mLock);
+ if (NS_FAILED(mStatus)) {
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ // Close explicitly to handle any errors.
+ closeGuard.release();
+ rv = inputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // We will append to the initial target file only if it was requested by the
+ // caller, but we'll always append on subsequent accesses to the target file.
+ int32_t creationIoFlags;
+ if (isContinuation) {
+ creationIoFlags = PR_APPEND;
+ } else {
+ creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE;
+ }
+
+ // Create the target file, or append to it if we already started writing it.
+ // The 0600 permissions are used while the file is being downloaded, and for
+ // interrupted downloads. Those may be located in the system temporary
+ // directory, as well as the target directory, and generally have a ".part"
+ // extension. Those part files should never be group or world-writable even
+ // if the umask allows it.
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mActualTarget,
+ PR_WRONLY | creationIoFlags, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> bufferedStream;
+ rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedStream),
+ outputStream.forget(), BUFFERED_IO_SIZE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ outputStream = bufferedStream;
+
+ // Wrap the output stream so that it feeds the digest if needed.
+ if (mDigest.isSome()) {
+ // Constructing the DigestOutputStream cannot fail. Passing mDigest
+ // to DigestOutputStream is safe, because BackgroundFileSaver always
+ // outlives the outputStream. BackgroundFileSaver is reference-counted
+ // before the call to AsyncCopy, and mDigest is never destroyed
+ // before AsyncCopyCallback.
+ outputStream = new DigestOutputStream(outputStream, mDigest.ref());
+ }
+
+ // Start copying our input to the target file. No errors can be raised past
+ // this point if the copy starts, since they should be handled by the thread.
+ {
+ MutexAutoLock lock(mLock);
+
+ rv = NS_AsyncCopy(mPipeInputStream, outputStream, mBackgroundET,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback,
+ this, false, true, getter_AddRefs(mAsyncCopyContext),
+ GetProgressCallback());
+ if (NS_FAILED(rv)) {
+ NS_WARNING("NS_AsyncCopy failed.");
+ mAsyncCopyContext = nullptr;
+ return rv;
+ }
+ }
+
+ // If the operation succeeded, we must ensure that we keep this object alive
+ // for the entire duration of the copy, since only the raw pointer will be
+ // provided as the argument of the AsyncCopyCallback function. We can add the
+ // reference now, after NS_AsyncCopy returned, because it always starts
+ // processing asynchronously, and there is no risk that the callback is
+ // invoked before we reach this point. If the operation failed instead, then
+ // AsyncCopyCallback will never be called.
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+bool BackgroundFileSaver::CheckCompletion() {
+ nsresult rv;
+
+ bool failed = true;
+ {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(!mAsyncCopyContext,
+ "Should not be copying when checking completion conditions.");
+
+ if (mComplete) {
+ return true;
+ }
+
+ // If an error occurred, we don't need to do the checks in this code block,
+ // and the operation can be completed immediately with a failure code.
+ if (NS_SUCCEEDED(mStatus)) {
+ failed = false;
+
+ // We did not incur in an error, so we must determine if we can stop now.
+ // If the Finish method has not been called, we can just continue now.
+ if (!mFinishRequested) {
+ return false;
+ }
+
+ // We can only stop when all the operations requested by the control
+ // thread have been processed. First, we check whether we have processed
+ // the first SetTarget call, if any. Then, we check whether we have
+ // processed any rename requested by subsequent SetTarget calls.
+ if ((mInitialTarget && !mActualTarget) ||
+ (mRenamedTarget && mRenamedTarget != mActualTarget)) {
+ return false;
+ }
+
+ // If we still have data to write to the output file, allow the copy
+ // operation to resume. The Available getter may return an error if one
+ // of the pipe's streams has been already closed.
+ uint64_t available;
+ rv = mPipeInputStream->Available(&available);
+ if (NS_SUCCEEDED(rv) && available != 0) {
+ return false;
+ }
+ }
+
+ mComplete = true;
+ }
+
+ // Ensure we notify completion now that the operation finished.
+ // Do a best-effort attempt to remove the file if required.
+ if (failed && mActualTarget && !mActualTargetKeepPartial) {
+ (void)mActualTarget->Remove(false);
+ }
+
+ // Finish computing the hash
+ if (!failed && mDigest.isSome()) {
+ nsTArray<uint8_t> outArray;
+ rv = mDigest->End(outArray);
+ if (NS_SUCCEEDED(rv)) {
+ MutexAutoLock lock(mLock);
+ mSha256 = nsDependentCSubstring(
+ BitwiseCast<char*, uint8_t*>(outArray.Elements()), outArray.Length());
+ }
+ }
+
+ // Compute the signature of the binary. ExtractSignatureInfo doesn't do
+ // anything on non-Windows platforms except return an empty nsIArray.
+ if (!failed && mActualTarget) {
+ nsString filePath;
+ mActualTarget->GetTarget(filePath);
+ nsresult rv = ExtractSignatureInfo(filePath);
+ if (NS_FAILED(rv)) {
+ LOG(("Unable to extract signature information [this = %p].", this));
+ } else {
+ LOG(("Signature extraction success! [this = %p]", this));
+ }
+ }
+
+ // Post an event to notify that the operation completed.
+ if (NS_FAILED(mControlEventTarget->Dispatch(
+ NewRunnableMethod("BackgroundFileSaver::NotifySaveComplete", this,
+ &BackgroundFileSaver::NotifySaveComplete),
+ NS_DISPATCH_NORMAL))) {
+ NS_WARNING("Unable to post completion event to the control thread.");
+ }
+
+ return true;
+}
+
+// Called on the control thread.
+nsresult BackgroundFileSaver::NotifyTargetChange(nsIFile* aTarget) {
+ if (mObserver) {
+ (void)mObserver->OnTargetChange(this, aTarget);
+ }
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+nsresult BackgroundFileSaver::NotifySaveComplete() {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ nsresult status;
+ {
+ MutexAutoLock lock(mLock);
+ status = mStatus;
+ }
+
+ if (mObserver) {
+ (void)mObserver->OnSaveComplete(this, status);
+ // If mObserver keeps alive an enclosure that captures `this`, we'll have a
+ // cycle that won't be caught by the cycle-collector, so we need to break it
+ // when we're done here (see bug 1444265).
+ mObserver = nullptr;
+ }
+
+ // At this point, the worker thread will not process any more events, and we
+ // can shut it down. Shutting down a thread may re-enter the event loop on
+ // this thread. This is not a problem in this case, since this function is
+ // called by a top-level event itself, and we have already invoked the
+ // completion observer callback. Re-entering the loop can only delay the
+ // final release and destruction of this saver object, since we are keeping a
+ // reference to it through the event object.
+ mBackgroundET = nullptr;
+
+ sThreadCount--;
+
+ // When there are no more active downloads, we consider the download session
+ // finished. We record the maximum number of concurrent downloads reached
+ // during the session in a telemetry histogram, and we reset the maximum
+ // thread counter for the next download session
+ if (sThreadCount == 0) {
+ Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT,
+ sTelemetryMaxThreadCount);
+ sTelemetryMaxThreadCount = 0;
+ }
+
+ return NS_OK;
+}
+
+nsresult BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath) {
+ MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread");
+ {
+ MutexAutoLock lock(mLock);
+ if (!mSignatureInfoEnabled) {
+ return NS_OK;
+ }
+ }
+#ifdef XP_WIN
+ // Setup the file to check.
+ WINTRUST_FILE_INFO fileToCheck = {0};
+ fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO);
+ fileToCheck.pcwszFilePath = filePath.Data();
+ fileToCheck.hFile = nullptr;
+ fileToCheck.pgKnownSubject = nullptr;
+
+ // We want to check it is signed and trusted.
+ WINTRUST_DATA trustData = {0};
+ trustData.cbStruct = sizeof(trustData);
+ trustData.pPolicyCallbackData = nullptr;
+ trustData.pSIPClientData = nullptr;
+ trustData.dwUIChoice = WTD_UI_NONE;
+ trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
+ trustData.dwUnionChoice = WTD_CHOICE_FILE;
+ trustData.dwStateAction = WTD_STATEACTION_VERIFY;
+ trustData.hWVTStateData = nullptr;
+ trustData.pwszURLReference = nullptr;
+ // Disallow revocation checks over the network
+ trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
+ // no UI
+ trustData.dwUIContext = 0;
+ trustData.pFile = &fileToCheck;
+
+ // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate
+ // chains up to a trusted root CA and has appropriate permissions to sign
+ // code.
+ GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+ // Check if the file is signed by something that is trusted. If the file is
+ // not signed, this is a no-op.
+ LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData);
+ CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr;
+ // According to the Windows documentation, we should check against 0 instead
+ // of ERROR_SUCCESS, which is an HRESULT.
+ if (ret == 0) {
+ cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData);
+ }
+ if (cryptoProviderData) {
+ // Lock because signature information is read on the main thread.
+ MutexAutoLock lock(mLock);
+ LOG(("Downloaded trusted and signed file [this = %p].", this));
+ // A binary may have multiple signers. Each signer may have multiple certs
+ // in the chain.
+ for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) {
+ const CERT_CHAIN_CONTEXT* certChainContext =
+ cryptoProviderData->pasSigners[i].pChainContext;
+ if (!certChainContext) {
+ break;
+ }
+ for (DWORD j = 0; j < certChainContext->cChain; ++j) {
+ const CERT_SIMPLE_CHAIN* certSimpleChain =
+ certChainContext->rgpChain[j];
+ if (!certSimpleChain) {
+ break;
+ }
+
+ nsTArray<nsTArray<uint8_t>> certList;
+ bool extractionSuccess = true;
+ for (DWORD k = 0; k < certSimpleChain->cElement; ++k) {
+ CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k];
+ if (certChainElement->pCertContext->dwCertEncodingType !=
+ X509_ASN_ENCODING) {
+ continue;
+ }
+ nsTArray<uint8_t> cert;
+ cert.AppendElements(certChainElement->pCertContext->pbCertEncoded,
+ certChainElement->pCertContext->cbCertEncoded);
+ certList.AppendElement(std::move(cert));
+ }
+ if (extractionSuccess) {
+ mSignatureInfo.AppendElement(std::move(certList));
+ }
+ }
+ }
+ // Free the provider data if cryptoProviderData is not null.
+ trustData.dwStateAction = WTD_STATEACTION_CLOSE;
+ WinVerifyTrust(nullptr, &policyGUID, &trustData);
+ } else {
+ LOG(("Downloaded unsigned or untrusted file [this = %p].", this));
+ }
+#endif
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverOutputStream
+
+NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream, nsIBackgroundFileSaver,
+ nsIOutputStream, nsIAsyncOutputStream,
+ nsIOutputStreamCallback)
+
+BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream()
+ : mAsyncWaitCallback(nullptr) {}
+
+bool BackgroundFileSaverOutputStream::HasInfiniteBuffer() { return false; }
+
+nsAsyncCopyProgressFun BackgroundFileSaverOutputStream::GetProgressCallback() {
+ return nullptr;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Close() { return mPipeOutputStream->Close(); }
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Flush() { return mPipeOutputStream->Flush(); }
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::StreamStatus() {
+ return mPipeOutputStream->StreamStatus();
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ return mPipeOutputStream->Write(aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount, uint32_t* _retval) {
+ return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::IsNonBlocking(bool* _retval) {
+ return mPipeOutputStream->IsNonBlocking(_retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason) {
+ return mPipeOutputStream->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ NS_ENSURE_STATE(!mAsyncWaitCallback);
+
+ mAsyncWaitCallback = aCallback;
+
+ return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount,
+ aEventTarget);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::OnOutputStreamReady(
+ nsIAsyncOutputStream* aStream) {
+ NS_ENSURE_STATE(mAsyncWaitCallback);
+
+ nsCOMPtr<nsIOutputStreamCallback> asyncWaitCallback = nullptr;
+ asyncWaitCallback.swap(mAsyncWaitCallback);
+
+ return asyncWaitCallback->OnOutputStreamReady(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverStreamListener
+
+NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener, nsIBackgroundFileSaver,
+ nsIRequestObserver, nsIStreamListener)
+
+bool BackgroundFileSaverStreamListener::HasInfiniteBuffer() { return true; }
+
+nsAsyncCopyProgressFun
+BackgroundFileSaverStreamListener::GetProgressCallback() {
+ return AsyncCopyProgressCallback;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest* aRequest) {
+ NS_ENSURE_ARG(aRequest);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ // If an error occurred, cancel the operation immediately. On success, wait
+ // until the caller has determined whether the file should be renamed.
+ if (NS_FAILED(aStatusCode)) {
+ Finish(aStatusCode);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount) {
+ nsresult rv;
+
+ NS_ENSURE_ARG(aRequest);
+
+ // Read the requested data. Since the pipe has an infinite buffer, we don't
+ // expect any write error to occur here.
+ uint32_t writeCount;
+ rv = mPipeOutputStream->WriteFrom(aInputStream, aCount, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If reading from the input stream fails for any reason, the pipe will return
+ // a success code, but without reading all the data. Since we should be able
+ // to read the requested data when OnDataAvailable is called, raise an error.
+ if (writeCount < aCount) {
+ NS_WARNING("Reading from the input stream should not have failed.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool stateChanged = false;
+ {
+ MutexAutoLock lock(mSuspensionLock);
+
+ if (!mReceivedTooMuchData) {
+ uint64_t available;
+ nsresult rv = mPipeInputStream->Available(&available);
+ if (NS_SUCCEEDED(rv) && available > REQUEST_SUSPEND_AT) {
+ mReceivedTooMuchData = true;
+ mRequest = aRequest;
+ stateChanged = true;
+ }
+ }
+ }
+
+ if (stateChanged) {
+ NotifySuspendOrResume();
+ }
+
+ return NS_OK;
+}
+
+// Called on the worker thread.
+// static
+void BackgroundFileSaverStreamListener::AsyncCopyProgressCallback(
+ void* aClosure, uint32_t aCount) {
+ BackgroundFileSaverStreamListener* self =
+ (BackgroundFileSaverStreamListener*)aClosure;
+
+ // Wait if the control thread is in the process of suspending or resuming.
+ MutexAutoLock lock(self->mSuspensionLock);
+
+ // This function is called when some bytes are consumed by NS_AsyncCopy. Each
+ // time this happens, verify if a suspended request should be resumed, because
+ // we have now consumed enough data.
+ if (self->mReceivedTooMuchData) {
+ uint64_t available;
+ nsresult rv = self->mPipeInputStream->Available(&available);
+ if (NS_FAILED(rv) || available < REQUEST_RESUME_AT) {
+ self->mReceivedTooMuchData = false;
+
+ // Post an event to verify if the request should be resumed.
+ if (NS_FAILED(self->mControlEventTarget->Dispatch(
+ NewRunnableMethod(
+ "BackgroundFileSaverStreamListener::NotifySuspendOrResume",
+ self,
+ &BackgroundFileSaverStreamListener::NotifySuspendOrResume),
+ NS_DISPATCH_NORMAL))) {
+ NS_WARNING("Unable to post resume event to the control thread.");
+ }
+ }
+ }
+}
+
+// Called on the control thread.
+nsresult BackgroundFileSaverStreamListener::NotifySuspendOrResume() {
+ // Prevent the worker thread from changing state while processing.
+ MutexAutoLock lock(mSuspensionLock);
+
+ if (mReceivedTooMuchData) {
+ if (!mRequestSuspended) {
+ // Try to suspend the request. If this fails, don't try to resume later.
+ if (NS_SUCCEEDED(mRequest->Suspend())) {
+ mRequestSuspended = true;
+ } else {
+ NS_WARNING("Unable to suspend the request.");
+ }
+ }
+ } else {
+ if (mRequestSuspended) {
+ // Resume the request only if we succeeded in suspending it.
+ if (NS_SUCCEEDED(mRequest->Resume())) {
+ mRequestSuspended = false;
+ } else {
+ NS_WARNING("Unable to resume the request.");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// DigestOutputStream
+NS_IMPL_ISUPPORTS(DigestOutputStream, nsIOutputStream)
+
+DigestOutputStream::DigestOutputStream(nsIOutputStream* aStream,
+ Digest& aDigest)
+ : mOutputStream(aStream), mDigest(aDigest) {
+ MOZ_ASSERT(mOutputStream, "Can't have null output stream");
+}
+
+NS_IMETHODIMP
+DigestOutputStream::Close() { return mOutputStream->Close(); }
+
+NS_IMETHODIMP
+DigestOutputStream::Flush() { return mOutputStream->Flush(); }
+
+NS_IMETHODIMP
+DigestOutputStream::StreamStatus() { return mOutputStream->StreamStatus(); }
+
+NS_IMETHODIMP
+DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval) {
+ nsresult rv = mDigest.Update(
+ BitwiseCast<const unsigned char*, const char*>(aBuf), aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mOutputStream->Write(aBuf, aCount, retval);
+}
+
+NS_IMETHODIMP
+DigestOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* retval) {
+ // Not supported. We could read the stream to a buf, call DigestOp on the
+ // result, seek back and pass the stream on, but it's not worth it since our
+ // application (NS_AsyncCopy) doesn't invoke this on the sink.
+ MOZ_CRASH("DigestOutputStream::WriteFrom not implemented");
+}
+
+NS_IMETHODIMP
+DigestOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* retval) {
+ MOZ_CRASH("DigestOutputStream::WriteSegments not implemented");
+}
+
+NS_IMETHODIMP
+DigestOutputStream::IsNonBlocking(bool* retval) {
+ return mOutputStream->IsNonBlocking(retval);
+}
+
+#undef LOG_ENABLED
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/BackgroundFileSaver.h b/netwerk/base/BackgroundFileSaver.h
new file mode 100644
index 0000000000..214aa31d10
--- /dev/null
+++ b/netwerk/base/BackgroundFileSaver.h
@@ -0,0 +1,397 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file defines two implementations of the nsIBackgroundFileSaver
+ * interface. See the "test_backgroundfilesaver.js" file for usage examples.
+ */
+
+#ifndef BackgroundFileSaver_h__
+#define BackgroundFileSaver_h__
+
+#include "ScopedNSSTypes.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIBackgroundFileSaver.h"
+#include "nsIStreamListener.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+
+class nsIAsyncInputStream;
+class nsISerialEventTarget;
+
+namespace mozilla {
+namespace net {
+
+class DigestOutputStream;
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaver
+
+class BackgroundFileSaver : public nsIBackgroundFileSaver {
+ public:
+ NS_DECL_NSIBACKGROUNDFILESAVER
+
+ BackgroundFileSaver();
+
+ /**
+ * Initializes the pipe and the worker thread on XPCOM construction.
+ *
+ * This is called automatically by the XPCOM infrastructure, and if this
+ * fails, the factory will delete this object without returning a reference.
+ */
+ nsresult Init();
+
+ /**
+ * Number of worker threads that are currently running.
+ */
+ static uint32_t sThreadCount;
+
+ /**
+ * Maximum number of worker threads reached during the current download
+ * session, used for telemetry.
+ *
+ * When there are no more worker threads running, we consider the download
+ * session finished, and this counter is reset.
+ */
+ static uint32_t sTelemetryMaxThreadCount;
+
+ protected:
+ virtual ~BackgroundFileSaver();
+
+ /**
+ * Thread that constructed this object.
+ */
+ nsCOMPtr<nsIEventTarget> mControlEventTarget;
+
+ /**
+ * Thread to which the actual input/output is delegated.
+ */
+ nsCOMPtr<nsISerialEventTarget> mBackgroundET;
+
+ /**
+ * Stream that receives data from derived classes. The received data will be
+ * available to the worker thread through mPipeInputStream. This is an
+ * instance of nsPipeOutputStream, not BackgroundFileSaverOutputStream.
+ */
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOutputStream;
+
+ /**
+ * Used during initialization, determines if the pipe is created with an
+ * infinite buffer. An infinite buffer is required if the derived class
+ * implements nsIStreamListener, because this interface requires all the
+ * provided data to be consumed synchronously.
+ */
+ virtual bool HasInfiniteBuffer() = 0;
+
+ /**
+ * Used by derived classes if they need to be called back while copying.
+ */
+ virtual nsAsyncCopyProgressFun GetProgressCallback() = 0;
+
+ /**
+ * Stream used by the worker thread to read the data to be saved.
+ */
+ nsCOMPtr<nsIAsyncInputStream> mPipeInputStream;
+
+ private:
+ friend class NotifyTargetChangeRunnable;
+
+ /**
+ * Matches the nsIBackgroundFileSaver::observer property.
+ *
+ * @remarks This is a strong reference so that JavaScript callers don't need
+ * to worry about keeping another reference to the observer.
+ */
+ nsCOMPtr<nsIBackgroundFileSaverObserver> mObserver;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Shared state between control and worker threads
+
+ /**
+ * Protects the shared state between control and worker threads. This mutex
+ * is always locked for a very short time, never during input/output.
+ */
+ mozilla::Mutex mLock{"BackgroundFileSaver.mLock"};
+
+ /**
+ * True if the worker thread is already waiting to process a change in state.
+ */
+ bool mWorkerThreadAttentionRequested MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * True if the operation should finish as soon as possibile.
+ */
+ bool mFinishRequested MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * True if the operation completed, with either success or failure.
+ */
+ bool mComplete MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * Holds the current file saver status. This is a success status while the
+ * object is working correctly, and remains such if the operation completes
+ * successfully. This becomes an error status when an error occurs on the
+ * worker thread, or when the operation is canceled.
+ */
+ nsresult mStatus MOZ_GUARDED_BY(mLock){NS_OK};
+
+ /**
+ * True if we should append data to the initial target file, instead of
+ * overwriting it.
+ */
+ bool mAppend MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * This is set by the first SetTarget call on the control thread, and contains
+ * the target file name that will be used by the worker thread, as soon as it
+ * is possible to update mActualTarget and open the file. This is null if no
+ * target was ever assigned to this object.
+ */
+ nsCOMPtr<nsIFile> mInitialTarget MOZ_GUARDED_BY(mLock);
+
+ /**
+ * This is set by the first SetTarget call on the control thread, and
+ * indicates whether mInitialTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mInitialTargetKeepPartial MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * This is set by subsequent SetTarget calls on the control thread, and
+ * contains the new target file name to which the worker thread will move the
+ * target file, as soon as it can be done. This is null if SetTarget was
+ * called only once, or no target was ever assigned to this object.
+ *
+ * The target file can be renamed multiple times, though only the most recent
+ * rename is guaranteed to be processed by the worker thread.
+ */
+ nsCOMPtr<nsIFile> mRenamedTarget MOZ_GUARDED_BY(mLock);
+
+ /**
+ * This is set by subsequent SetTarget calls on the control thread, and
+ * indicates whether mRenamedTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mRenamedTargetKeepPartial MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise.
+ * This is read by both threads but only written by the worker thread.
+ */
+ nsCOMPtr<nsISupports> mAsyncCopyContext MOZ_GUARDED_BY(mLock);
+
+ /**
+ * The SHA 256 hash in raw bytes of the downloaded file. This is written
+ * by the worker thread but can be read on the main thread.
+ */
+ nsCString mSha256 MOZ_GUARDED_BY(mLock);
+
+ /**
+ * Whether or not to compute the hash. Must be set on the main thread before
+ * setTarget is called.
+ */
+ bool mSha256Enabled MOZ_GUARDED_BY(mLock){false};
+
+ /**
+ * Store the signature info.
+ */
+ nsTArray<nsTArray<nsTArray<uint8_t>>> mSignatureInfo MOZ_GUARDED_BY(mLock);
+
+ /**
+ * Whether or not to extract the signature. Must be set on the main thread
+ * before setTarget is called.
+ */
+ bool mSignatureInfoEnabled MOZ_GUARDED_BY(mLock){false};
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// State handled exclusively by the worker thread
+
+ /**
+ * Current target file associated to the input and output streams.
+ */
+ nsCOMPtr<nsIFile> mActualTarget;
+
+ /**
+ * Indicates whether mActualTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mActualTargetKeepPartial{false};
+
+ /**
+ * Used to calculate the file hash. This keeps state across file renames and
+ * is lazily initialized in ProcessStateChange.
+ */
+ Maybe<Digest> mDigest;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Private methods
+
+ /**
+ * Called when NS_AsyncCopy completes.
+ *
+ * @param aClosure
+ * Populated with a raw pointer to the BackgroundFileSaver object.
+ * @param aStatus
+ * Success or failure status specified when the copy was interrupted.
+ */
+ static void AsyncCopyCallback(void* aClosure, nsresult aStatus);
+
+ /**
+ * Called on the control thread after state changes, to ensure that the worker
+ * thread will process the state change appropriately.
+ *
+ * @param aShouldInterruptCopy
+ * If true, the current NS_AsyncCopy, if any, is canceled.
+ */
+ nsresult GetWorkerThreadAttention(bool aShouldInterruptCopy);
+
+ /**
+ * Event called on the worker thread to begin processing a state change.
+ */
+ nsresult ProcessAttention();
+
+ /**
+ * Called by ProcessAttention to execute the operations corresponding to the
+ * state change. If this results in an error, ProcessAttention will force the
+ * entire operation to be aborted.
+ */
+ nsresult ProcessStateChange();
+
+ /**
+ * Returns true if completion conditions are met on the worker thread. The
+ * first time this happens, posts the completion event to the control thread.
+ */
+ bool CheckCompletion();
+
+ /**
+ * Event called on the control thread to indicate that file contents will now
+ * be saved to the specified file.
+ */
+ nsresult NotifyTargetChange(nsIFile* aTarget);
+
+ /**
+ * Event called on the control thread to send the final notification.
+ */
+ nsresult NotifySaveComplete();
+
+ /**
+ * Verifies the signature of the binary at the specified file path and stores
+ * the signature data in mSignatureInfo. We extract only X.509 certificates,
+ * since that is what Google's Safebrowsing protocol specifies.
+ */
+ nsresult ExtractSignatureInfo(const nsAString& filePath);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverOutputStream
+
+class BackgroundFileSaverOutputStream : public BackgroundFileSaver,
+ public nsIAsyncOutputStream,
+ public nsIOutputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ BackgroundFileSaverOutputStream();
+
+ protected:
+ virtual bool HasInfiniteBuffer() override;
+ virtual nsAsyncCopyProgressFun GetProgressCallback() override;
+
+ private:
+ ~BackgroundFileSaverOutputStream() = default;
+
+ /**
+ * Original callback provided to our AsyncWait wrapper.
+ */
+ nsCOMPtr<nsIOutputStreamCallback> mAsyncWaitCallback;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverStreamListener. This class is instantiated by
+// nsExternalHelperAppService, DownloadCore.sys.mjs, and possibly others.
+
+class BackgroundFileSaverStreamListener final : public BackgroundFileSaver,
+ public nsIStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ BackgroundFileSaverStreamListener() = default;
+
+ protected:
+ virtual bool HasInfiniteBuffer() override;
+ virtual nsAsyncCopyProgressFun GetProgressCallback() override;
+
+ private:
+ ~BackgroundFileSaverStreamListener() = default;
+
+ /**
+ * Protects the state related to whether the request should be suspended.
+ */
+ mozilla::Mutex mSuspensionLock{
+ "BackgroundFileSaverStreamListener.mSuspensionLock"};
+
+ /**
+ * Whether we should suspend the request because we received too much data.
+ */
+ bool mReceivedTooMuchData MOZ_GUARDED_BY(mSuspensionLock){false};
+
+ /**
+ * Request for which we received too much data. This is populated when
+ * mReceivedTooMuchData becomes true for the first time.
+ */
+ nsCOMPtr<nsIRequest> mRequest MOZ_GUARDED_BY(mSuspensionLock);
+
+ /**
+ * Whether mRequest is currently suspended.
+ */
+ bool mRequestSuspended MOZ_GUARDED_BY(mSuspensionLock){false};
+
+ /**
+ * Called while NS_AsyncCopy is copying data.
+ */
+ static void AsyncCopyProgressCallback(void* aClosure, uint32_t aCount);
+
+ /**
+ * Called on the control thread to suspend or resume the request.
+ */
+ nsresult NotifySuspendOrResume();
+};
+
+// A wrapper around nsIOutputStream, so that we can compute hashes on the
+// stream without copying and without polluting pristine NSS code with XPCOM
+// interfaces.
+class DigestOutputStream : public nsIOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ // Constructor. Neither parameter may be null. The caller owns both.
+ DigestOutputStream(nsIOutputStream* aStream, Digest& aDigest);
+
+ private:
+ virtual ~DigestOutputStream() = default;
+
+ // Calls to write are passed to this stream.
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ // Digest used to compute the hash, owned by the caller.
+ Digest& mDigest;
+
+ // Don't accidentally copy construct.
+ DigestOutputStream(const DigestOutputStream& d) = delete;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/CacheInfoIPCTypes.h b/netwerk/base/CacheInfoIPCTypes.h
new file mode 100644
index 0000000000..b3d2277335
--- /dev/null
+++ b/netwerk/base/CacheInfoIPCTypes.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_CacheInfoIPCTypes_h_
+#define mozilla_net_CacheInfoIPCTypes_h_
+
+#include "ipc/IPCMessageUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsICacheInfoChannel.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<nsICacheInfoChannel::PreferredAlternativeDataDeliveryType>
+ : public ContiguousEnumSerializerInclusive<
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType,
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::NONE,
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::
+ SERIALIZE> {};
+
+} // namespace IPC
+
+#endif // mozilla_net_CacheInfoIPCTypes_h_
diff --git a/netwerk/base/CaptivePortalService.cpp b/netwerk/base/CaptivePortalService.cpp
new file mode 100644
index 0000000000..97a1dde929
--- /dev/null
+++ b/netwerk/base/CaptivePortalService.cpp
@@ -0,0 +1,428 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/CaptivePortalService.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXULAppAPI.h"
+#include "xpcpublic.h"
+#include "xpcprivate.h"
+
+static constexpr auto kInterfaceName = u"captive-portal-inteface"_ns;
+
+static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login";
+static const char kAbortCaptivePortalLoginEvent[] =
+ "captive-portal-login-abort";
+static const char kCaptivePortalLoginSuccessEvent[] =
+ "captive-portal-login-success";
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gCaptivePortalLog("CaptivePortalService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver,
+ nsISupportsWeakReference, nsITimerCallback,
+ nsICaptivePortalCallback, nsINamed)
+
+static StaticRefPtr<CaptivePortalService> gCPService;
+
+// static
+already_AddRefed<nsICaptivePortalService> CaptivePortalService::GetSingleton() {
+ if (gCPService) {
+ return do_AddRef(gCPService);
+ }
+
+ gCPService = new CaptivePortalService();
+ ClearOnShutdown(&gCPService);
+ return do_AddRef(gCPService);
+}
+
+CaptivePortalService::CaptivePortalService() {
+ mLastChecked = TimeStamp::Now();
+}
+
+CaptivePortalService::~CaptivePortalService() {
+ LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n",
+ XRE_GetProcessType() == GeckoProcessType_Default));
+}
+
+nsresult CaptivePortalService::PerformCheck() {
+ LOG(
+ ("CaptivePortalService::PerformCheck mRequestInProgress:%d "
+ "mInitialized:%d mStarted:%d\n",
+ mRequestInProgress, mInitialized, mStarted));
+ // Don't issue another request if last one didn't complete
+ if (mRequestInProgress || !mInitialized || !mStarted) {
+ return NS_OK;
+ }
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+ // Instantiating CaptiveDetect.sys.mjs before the JS engine is ready will
+ // lead to a crash (see bug 1800603)
+ // We can remove this restriction when we rewrite the detector in
+ // C++ or rust (bug 1809886).
+ if (!XPCJSRuntime::Get()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ nsresult rv;
+ if (!mCaptivePortalDetector) {
+ mCaptivePortalDetector =
+ do_CreateInstance("@mozilla.org/toolkit/captive-detector;1", &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("Unable to get a captive portal detector\n"));
+ return rv;
+ }
+ }
+
+ LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n"));
+ mRequestInProgress = true;
+ mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this);
+ return NS_OK;
+}
+
+nsresult CaptivePortalService::RearmTimer() {
+ LOG(("CaptivePortalService::RearmTimer\n"));
+ // Start a timer to recheck
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ mTimer = nullptr;
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ // If we have successfully determined the state, and we have never detected
+ // a captive portal, we don't need to keep polling, but will rely on events
+ // to trigger detection.
+ if (mState == NOT_CAPTIVE) {
+ return NS_OK;
+ }
+
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ }
+
+ if (mTimer && mDelay > 0) {
+ LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay));
+ return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return NS_OK;
+}
+
+nsresult CaptivePortalService::Initialize() {
+ if (mInitialized) {
+ return NS_OK;
+ }
+ mInitialized = true;
+
+ // Only the main process service should actually do anything. The service in
+ // the content process only mirrors the CP state in the main process.
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true);
+ observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true);
+ observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ }
+
+ LOG(("Initialized CaptivePortalService\n"));
+ return NS_OK;
+}
+
+nsresult CaptivePortalService::Start() {
+ if (!mInitialized) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (xpc::AreNonLocalConnectionsDisabled() &&
+ !Preferences::GetBool("network.captive-portal-service.testMode", false)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ if (mStarted) {
+ return NS_OK;
+ }
+
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN");
+ mStarted = true;
+ mEverBeenCaptive = false;
+
+ // Get the delay prefs
+ Preferences::GetUint("network.captive-portal-service.minInterval",
+ &mMinInterval);
+ Preferences::GetUint("network.captive-portal-service.maxInterval",
+ &mMaxInterval);
+ Preferences::GetFloat("network.captive-portal-service.backoffFactor",
+ &mBackoffFactor);
+
+ LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", mMinInterval,
+ mMaxInterval, mBackoffFactor));
+
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ // When Start is called, perform a check immediately
+ PerformCheck();
+ RearmTimer();
+ return NS_OK;
+}
+
+nsresult CaptivePortalService::Stop() {
+ LOG(("CaptivePortalService::Stop\n"));
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything when called in the content process.
+ return NS_OK;
+ }
+
+ if (!mStarted) {
+ return NS_OK;
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+ mRequestInProgress = false;
+ mStarted = false;
+ mEverBeenCaptive = false;
+ if (mCaptivePortalDetector) {
+ mCaptivePortalDetector->Abort(kInterfaceName);
+ }
+ mCaptivePortalDetector = nullptr;
+
+ // Clear the state in case anyone queries the state while detection is off.
+ mState = UNKNOWN;
+ return NS_OK;
+}
+
+void CaptivePortalService::SetStateInChild(int32_t aState) {
+ // This should only be called in the content process, from ContentChild.cpp
+ // in order to mirror the captive portal state set in the chrome process.
+ MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default);
+
+ mState = aState;
+ mLastChecked = TimeStamp::Now();
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsICaptivePortalService
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+CaptivePortalService::GetState(int32_t* aState) {
+ *aState = mState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::RecheckCaptivePortal() {
+ LOG(("CaptivePortalService::RecheckCaptivePortal\n"));
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ // This is called for user activity. We need to reset the slack count,
+ // so the checks continue to be quite frequent.
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ PerformCheck();
+ RearmTimer();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::GetLastChecked(uint64_t* aLastChecked) {
+ double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds();
+ *aLastChecked = static_cast<uint64_t>(duration);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsITimer
+// This callback gets called every mDelay miliseconds
+// It issues a checkCaptivePortal operation if one isn't already in progress
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Notify(nsITimer* aTimer) {
+ LOG(("CaptivePortalService::Notify\n"));
+ MOZ_ASSERT(aTimer == mTimer);
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ PerformCheck();
+
+ // This is needed because we don't want to always make requests very often.
+ // Every 10 checks, we the delay is increased mBackoffFactor times
+ // to a maximum delay of mMaxInterval
+ mSlackCount++;
+ if (mSlackCount % 10 == 0) {
+ mDelay = mDelay * mBackoffFactor;
+ }
+ if (mDelay > mMaxInterval) {
+ mDelay = mMaxInterval;
+ }
+
+ // Note - if mDelay is 0, the timer will not be rearmed.
+ RearmTimer();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsINamed
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+CaptivePortalService::GetName(nsACString& aName) {
+ aName.AssignLiteral("CaptivePortalService");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsIObserver
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // Doesn't do anything if called in the content process.
+ return NS_OK;
+ }
+
+ LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic));
+ if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
+ // A redirect or altered content has been detected.
+ // The user needs to log in. We are in a captive portal.
+ StateTransition(LOCKED_PORTAL);
+ mLastChecked = TimeStamp::Now();
+ mEverBeenCaptive = true;
+ } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) {
+ // The user has successfully logged in. We have connectivity.
+ StateTransition(UNLOCKED_PORTAL);
+ mLastChecked = TimeStamp::Now();
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ RearmTimer();
+ } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) {
+ // The login has been aborted
+ StateTransition(UNKNOWN);
+ mLastChecked = TimeStamp::Now();
+ mSlackCount = 0;
+ } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Stop();
+ return NS_OK;
+ }
+
+ // Send notification so that the captive portal state is mirrored in the
+ // content process.
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsICaptivePortalService> cps(this);
+ observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE,
+ nullptr);
+ }
+
+ return NS_OK;
+}
+
+void CaptivePortalService::NotifyConnectivityAvailable(bool aCaptive) {
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsICaptivePortalService> cps(this);
+ observerService->NotifyObservers(cps, NS_CAPTIVE_PORTAL_CONNECTIVITY,
+ aCaptive ? u"captive" : u"clear");
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CaptivePortalService::nsICaptivePortalCallback
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+CaptivePortalService::Prepare() {
+ LOG(("CaptivePortalService::Prepare\n"));
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return NS_OK;
+ }
+ // XXX: Finish preparation shouldn't be called until dns and routing is
+ // available.
+ if (mCaptivePortalDetector) {
+ mCaptivePortalDetector->FinishPreparation(kInterfaceName);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::Complete(bool success) {
+ LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success,
+ mState));
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ mLastChecked = TimeStamp::Now();
+
+ // Note: this callback gets called when:
+ // 1. the request is completed, and content is valid (success == true)
+ // 2. when the request is aborted or times out (success == false)
+
+ if (success) {
+ if (mEverBeenCaptive) {
+ StateTransition(UNLOCKED_PORTAL);
+ NotifyConnectivityAvailable(true);
+ } else {
+ StateTransition(NOT_CAPTIVE);
+ NotifyConnectivityAvailable(false);
+ }
+ }
+
+ mRequestInProgress = false;
+ return NS_OK;
+}
+
+void CaptivePortalService::StateTransition(int32_t aNewState) {
+ int32_t oldState = mState;
+ mState = aNewState;
+
+ if ((oldState == UNKNOWN && mState == NOT_CAPTIVE) ||
+ (oldState == LOCKED_PORTAL && mState == UNLOCKED_PORTAL)) {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsICaptivePortalService> cps(this);
+ observerService->NotifyObservers(
+ cps, NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED, nullptr);
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/CaptivePortalService.h b/netwerk/base/CaptivePortalService.h
new file mode 100644
index 0000000000..f0a7200b4b
--- /dev/null
+++ b/netwerk/base/CaptivePortalService.h
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CaptivePortalService_h_
+#define CaptivePortalService_h_
+
+#include "nsICaptivePortalService.h"
+#include "nsICaptivePortalDetector.h"
+#include "nsINamed.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsITimer.h"
+#include "nsCOMArray.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace net {
+
+class CaptivePortalService : public nsICaptivePortalService,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsITimerCallback,
+ public nsICaptivePortalCallback,
+ public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICAPTIVEPORTALSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSICAPTIVEPORTALCALLBACK
+ NS_DECL_NSINAMED
+
+ nsresult Initialize();
+ nsresult Start();
+ nsresult Stop();
+
+ static already_AddRefed<nsICaptivePortalService> GetSingleton();
+
+ // This method is only called in the content process, in order to mirror
+ // the captive portal state in the parent process.
+ void SetStateInChild(int32_t aState);
+
+ private:
+ static const uint32_t kDefaultInterval = 60 * 1000; // check every 60 seconds
+
+ CaptivePortalService();
+ virtual ~CaptivePortalService();
+ nsresult PerformCheck();
+ nsresult RearmTimer();
+ void NotifyConnectivityAvailable(bool aCaptive);
+
+ nsCOMPtr<nsICaptivePortalDetector> mCaptivePortalDetector;
+ int32_t mState{UNKNOWN};
+
+ nsCOMPtr<nsITimer> mTimer;
+ bool mStarted{false};
+ bool mInitialized{false};
+ bool mRequestInProgress{false};
+ bool mEverBeenCaptive{false};
+
+ uint32_t mDelay{kDefaultInterval};
+ int32_t mSlackCount{0};
+
+ uint32_t mMinInterval{kDefaultInterval};
+ uint32_t mMaxInterval{25 * kDefaultInterval};
+ float mBackoffFactor{5.0};
+
+ void StateTransition(int32_t aNewState);
+
+ // This holds a timestamp when the last time when the captive portal check
+ // has changed state.
+ mozilla::TimeStamp mLastChecked;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // CaptivePortalService_h_
diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp
new file mode 100644
index 0000000000..bc1619ee60
--- /dev/null
+++ b/netwerk/base/Dashboard.cpp
@@ -0,0 +1,1182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http:mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/NetDashboardBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/net/Dashboard.h"
+#include "mozilla/net/HttpInfo.h"
+#include "mozilla/net/HTTPSSVC.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsHttp.h"
+#include "nsICancelable.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIInputStream.h"
+#include "nsINamed.h"
+#include "nsINetAddr.h"
+#include "nsISocketTransport.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "nsURLHelper.h"
+#include "mozilla/Logging.h"
+#include "nsIOService.h"
+#include "../cache2/CacheFileUtils.h"
+
+using mozilla::AutoSafeJSContext;
+using mozilla::dom::Sequence;
+using mozilla::dom::ToJSValue;
+
+namespace mozilla {
+namespace net {
+
+class SocketData : public nsISupports {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ SocketData() = default;
+
+ uint64_t mTotalSent{0};
+ uint64_t mTotalRecv{0};
+ nsTArray<SocketInfo> mData;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+
+ private:
+ virtual ~SocketData() = default;
+};
+
+static void GetErrorString(nsresult rv, nsAString& errorString);
+
+NS_IMPL_ISUPPORTS0(SocketData)
+
+class HttpData : public nsISupports {
+ virtual ~HttpData() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ HttpData() = default;
+
+ nsTArray<HttpRetParams> mData;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+};
+
+NS_IMPL_ISUPPORTS0(HttpData)
+
+class WebSocketRequest : public nsISupports {
+ virtual ~WebSocketRequest() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ WebSocketRequest() = default;
+
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+};
+
+NS_IMPL_ISUPPORTS0(WebSocketRequest)
+
+class DnsData : public nsISupports {
+ virtual ~DnsData() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ DnsData() = default;
+
+ nsTArray<DNSCacheEntries> mData;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+};
+
+NS_IMPL_ISUPPORTS0(DnsData)
+
+class ConnectionData : public nsITransportEventSink,
+ public nsITimerCallback,
+ public nsINamed {
+ virtual ~ConnectionData() {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ }
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSITIMERCALLBACK
+
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("net::ConnectionData");
+ return NS_OK;
+ }
+
+ void StartTimer(uint32_t aTimeout);
+ void StopTimer();
+
+ explicit ConnectionData(Dashboard* target) { mDashboard = target; }
+
+ nsCOMPtr<nsISocketTransport> mSocket;
+ nsCOMPtr<nsIInputStream> mStreamIn;
+ nsCOMPtr<nsITimer> mTimer;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+ Dashboard* mDashboard;
+
+ nsCString mHost;
+ uint32_t mPort{0};
+ nsCString mProtocol;
+ uint32_t mTimeout{0};
+
+ nsString mStatus;
+};
+
+NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback,
+ nsINamed)
+
+class RcwnData : public nsISupports {
+ virtual ~RcwnData() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ RcwnData() = default;
+
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+};
+
+NS_IMPL_ISUPPORTS0(RcwnData)
+
+NS_IMETHODIMP
+ConnectionData::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
+ int64_t aProgress, int64_t aProgressMax) {
+ if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
+ StopTimer();
+ }
+
+ GetErrorString(aStatus, mStatus);
+ mEventTarget->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>(
+ "net::Dashboard::GetConnectionStatus", mDashboard,
+ &Dashboard::GetConnectionStatus, this),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ConnectionData::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(aTimer == mTimer);
+
+ if (mSocket) {
+ mSocket->Close(NS_ERROR_ABORT);
+ mSocket = nullptr;
+ mStreamIn = nullptr;
+ }
+
+ mTimer = nullptr;
+
+ mStatus.AssignLiteral(u"NS_ERROR_NET_TIMEOUT");
+ mEventTarget->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>(
+ "net::Dashboard::GetConnectionStatus", mDashboard,
+ &Dashboard::GetConnectionStatus, this),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+void ConnectionData::StartTimer(uint32_t aTimeout) {
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ }
+
+ mTimer->InitWithCallback(this, aTimeout * 1000, nsITimer::TYPE_ONE_SHOT);
+}
+
+void ConnectionData::StopTimer() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+class LookupHelper;
+
+class LookupArgument : public nsISupports {
+ virtual ~LookupArgument() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ LookupArgument(nsIDNSRecord* aRecord, LookupHelper* aHelper) {
+ mRecord = aRecord;
+ mHelper = aHelper;
+ }
+
+ nsCOMPtr<nsIDNSRecord> mRecord;
+ RefPtr<LookupHelper> mHelper;
+};
+
+NS_IMPL_ISUPPORTS0(LookupArgument)
+
+class LookupHelper final : public nsIDNSListener {
+ virtual ~LookupHelper() {
+ if (mCancel) {
+ mCancel->Cancel(NS_ERROR_ABORT);
+ }
+ }
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ LookupHelper() = default;
+
+ nsresult ConstructAnswer(LookupArgument* aArgument);
+ nsresult ConstructHTTPSRRAnswer(LookupArgument* aArgument);
+
+ public:
+ nsCOMPtr<nsICancelable> mCancel;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget{nullptr};
+ nsresult mStatus{NS_ERROR_NOT_INITIALIZED};
+};
+
+NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener)
+
+NS_IMETHODIMP
+LookupHelper::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord,
+ nsresult aStatus) {
+ MOZ_ASSERT(aRequest == mCancel);
+ mCancel = nullptr;
+ mStatus = aStatus;
+
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord = do_QueryInterface(aRecord);
+ if (httpsRecord) {
+ RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this);
+ mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<LookupArgument>>(
+ "net::LookupHelper::ConstructHTTPSRRAnswer", this,
+ &LookupHelper::ConstructHTTPSRRAnswer, arg),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+
+ RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this);
+ mEventTarget->Dispatch(NewRunnableMethod<RefPtr<LookupArgument>>(
+ "net::LookupHelper::ConstructAnswer", this,
+ &LookupHelper::ConstructAnswer, arg),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+nsresult LookupHelper::ConstructAnswer(LookupArgument* aArgument) {
+ nsIDNSRecord* aRecord = aArgument->mRecord;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::DNSLookupDict dict;
+ dict.mAddress.Construct();
+
+ Sequence<nsString>& addresses = dict.mAddress.Value();
+ nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(aRecord);
+ if (NS_SUCCEEDED(mStatus) && record) {
+ dict.mAnswer = true;
+ bool hasMore;
+ record->HasMore(&hasMore);
+ while (hasMore) {
+ nsString* nextAddress = addresses.AppendElement(fallible);
+ if (!nextAddress) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCString nextAddressASCII;
+ record->GetNextAddrAsString(nextAddressASCII);
+ CopyASCIItoUTF16(nextAddressASCII, *nextAddress);
+ record->HasMore(&hasMore);
+ }
+ } else {
+ dict.mAnswer = false;
+ GetErrorString(mStatus, dict.mError);
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ this->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+static void CStringToHexString(const nsACString& aIn, nsAString& aOut) {
+ static const char* const lut = "0123456789ABCDEF";
+
+ size_t len = aIn.Length();
+
+ aOut.SetCapacity(2 * len);
+ for (size_t i = 0; i < aIn.Length(); ++i) {
+ const char c = static_cast<char>(aIn[i]);
+ aOut.Append(lut[(c >> 4) & 0x0F]);
+ aOut.Append(lut[c & 15]);
+ }
+}
+
+nsresult LookupHelper::ConstructHTTPSRRAnswer(LookupArgument* aArgument) {
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord =
+ do_QueryInterface(aArgument->mRecord);
+
+ AutoSafeJSContext cx;
+
+ mozilla::dom::HTTPSRRLookupDict dict;
+ dict.mRecords.Construct();
+
+ Sequence<dom::HTTPSRecord>& records = dict.mRecords.Value();
+ if (NS_SUCCEEDED(mStatus) && httpsRecord) {
+ dict.mAnswer = true;
+ nsTArray<RefPtr<nsISVCBRecord>> svcbRecords;
+ httpsRecord->GetRecords(svcbRecords);
+
+ for (const auto& record : svcbRecords) {
+ dom::HTTPSRecord* nextRecord = records.AppendElement(fallible);
+ if (!nextRecord) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ Unused << record->GetPriority(&nextRecord->mPriority);
+ nsCString name;
+ Unused << record->GetName(name);
+ CopyASCIItoUTF16(name, nextRecord->mTargetName);
+
+ nsTArray<RefPtr<nsISVCParam>> values;
+ Unused << record->GetValues(values);
+ if (values.IsEmpty()) {
+ continue;
+ }
+
+ for (const auto& value : values) {
+ uint16_t type;
+ Unused << value->GetType(&type);
+ switch (type) {
+ case SvcParamKeyAlpn: {
+ nextRecord->mAlpn.Construct();
+ nextRecord->mAlpn.Value().mType = type;
+ nsCOMPtr<nsISVCParamAlpn> alpnParam = do_QueryInterface(value);
+ nsTArray<nsCString> alpn;
+ Unused << alpnParam->GetAlpn(alpn);
+ nsAutoCString alpnStr;
+ for (const auto& str : alpn) {
+ alpnStr.Append(str);
+ alpnStr.Append(',');
+ }
+ CopyASCIItoUTF16(Span(alpnStr.BeginReading(), alpnStr.Length() - 1),
+ nextRecord->mAlpn.Value().mAlpn);
+ break;
+ }
+ case SvcParamKeyNoDefaultAlpn: {
+ nextRecord->mNoDefaultAlpn.Construct();
+ nextRecord->mNoDefaultAlpn.Value().mType = type;
+ break;
+ }
+ case SvcParamKeyPort: {
+ nextRecord->mPort.Construct();
+ nextRecord->mPort.Value().mType = type;
+ nsCOMPtr<nsISVCParamPort> portParam = do_QueryInterface(value);
+ Unused << portParam->GetPort(&nextRecord->mPort.Value().mPort);
+ break;
+ }
+ case SvcParamKeyIpv4Hint: {
+ nextRecord->mIpv4Hint.Construct();
+ nextRecord->mIpv4Hint.Value().mType = type;
+ nsCOMPtr<nsISVCParamIPv4Hint> ipv4Param = do_QueryInterface(value);
+ nsTArray<RefPtr<nsINetAddr>> ipv4Hint;
+ Unused << ipv4Param->GetIpv4Hint(ipv4Hint);
+ if (!ipv4Hint.IsEmpty()) {
+ nextRecord->mIpv4Hint.Value().mAddress.Construct();
+ for (const auto& address : ipv4Hint) {
+ nsString* nextAddress = nextRecord->mIpv4Hint.Value()
+ .mAddress.Value()
+ .AppendElement(fallible);
+ if (!nextAddress) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCString addressASCII;
+ Unused << address->GetAddress(addressASCII);
+ CopyASCIItoUTF16(addressASCII, *nextAddress);
+ }
+ }
+ break;
+ }
+ case SvcParamKeyIpv6Hint: {
+ nextRecord->mIpv6Hint.Construct();
+ nextRecord->mIpv6Hint.Value().mType = type;
+ nsCOMPtr<nsISVCParamIPv6Hint> ipv6Param = do_QueryInterface(value);
+ nsTArray<RefPtr<nsINetAddr>> ipv6Hint;
+ Unused << ipv6Param->GetIpv6Hint(ipv6Hint);
+ if (!ipv6Hint.IsEmpty()) {
+ nextRecord->mIpv6Hint.Value().mAddress.Construct();
+ for (const auto& address : ipv6Hint) {
+ nsString* nextAddress = nextRecord->mIpv6Hint.Value()
+ .mAddress.Value()
+ .AppendElement(fallible);
+ if (!nextAddress) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCString addressASCII;
+ Unused << address->GetAddress(addressASCII);
+ CopyASCIItoUTF16(addressASCII, *nextAddress);
+ }
+ }
+ break;
+ }
+ case SvcParamKeyEchConfig: {
+ nextRecord->mEchConfig.Construct();
+ nextRecord->mEchConfig.Value().mType = type;
+ nsCOMPtr<nsISVCParamEchConfig> echConfigParam =
+ do_QueryInterface(value);
+ nsCString echConfigStr;
+ Unused << echConfigParam->GetEchconfig(echConfigStr);
+ CStringToHexString(echConfigStr,
+ nextRecord->mEchConfig.Value().mEchConfig);
+ break;
+ }
+ case SvcParamKeyODoHConfig: {
+ nextRecord->mODoHConfig.Construct();
+ nextRecord->mODoHConfig.Value().mType = type;
+ nsCOMPtr<nsISVCParamODoHConfig> ODoHConfigParam =
+ do_QueryInterface(value);
+ nsCString ODoHConfigStr;
+ Unused << ODoHConfigParam->GetODoHConfig(ODoHConfigStr);
+ CStringToHexString(ODoHConfigStr,
+ nextRecord->mODoHConfig.Value().mODoHConfig);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ } else {
+ dict.mAnswer = false;
+ GetErrorString(mStatus, dict.mError);
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ this->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Dashboard, nsIDashboard, nsIDashboardEventNotifier)
+
+Dashboard::Dashboard() { mEnableLogging = false; }
+
+NS_IMETHODIMP
+Dashboard::RequestSockets(nsINetDashboardCallback* aCallback) {
+ RefPtr<SocketData> socketData = new SocketData();
+ socketData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ socketData->mEventTarget = GetCurrentSerialEventTarget();
+
+ if (nsIOService::UseSocketProcess()) {
+ if (!gIOService->SocketProcessReady()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<Dashboard> self(this);
+ SocketProcessParent::GetSingleton()->SendGetSocketData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self{std::move(self)},
+ socketData{std::move(socketData)}](SocketDataArgs&& args) {
+ socketData->mData.Assign(args.info());
+ socketData->mTotalSent = args.totalSent();
+ socketData->mTotalRecv = args.totalRecv();
+ socketData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<SocketData>>(
+ "net::Dashboard::GetSockets", self, &Dashboard::GetSockets,
+ socketData),
+ NS_DISPATCH_NORMAL);
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {});
+ return NS_OK;
+ }
+
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod<RefPtr<SocketData>>(
+ "net::Dashboard::GetSocketsDispatch", this,
+ &Dashboard::GetSocketsDispatch, socketData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetSocketsDispatch(SocketData* aSocketData) {
+ RefPtr<SocketData> socketData = aSocketData;
+ if (gSocketTransportService) {
+ gSocketTransportService->GetSocketConnections(&socketData->mData);
+ socketData->mTotalSent = gSocketTransportService->GetSentBytes();
+ socketData->mTotalRecv = gSocketTransportService->GetReceivedBytes();
+ }
+ socketData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<SocketData>>("net::Dashboard::GetSockets", this,
+ &Dashboard::GetSockets, socketData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetSockets(SocketData* aSocketData) {
+ RefPtr<SocketData> socketData = aSocketData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::SocketsDict dict;
+ dict.mSockets.Construct();
+ dict.mSent = 0;
+ dict.mReceived = 0;
+
+ Sequence<mozilla::dom::SocketElement>& sockets = dict.mSockets.Value();
+
+ uint32_t length = socketData->mData.Length();
+ if (!sockets.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < socketData->mData.Length(); i++) {
+ dom::SocketElement& mSocket = *sockets.AppendElement(fallible);
+ CopyASCIItoUTF16(socketData->mData[i].host, mSocket.mHost);
+ mSocket.mPort = socketData->mData[i].port;
+ mSocket.mActive = socketData->mData[i].active;
+ CopyASCIItoUTF16(socketData->mData[i].type, mSocket.mType);
+ mSocket.mSent = (double)socketData->mData[i].sent;
+ mSocket.mReceived = (double)socketData->mData[i].received;
+ dict.mSent += socketData->mData[i].sent;
+ dict.mReceived += socketData->mData[i].received;
+ }
+
+ dict.mSent += socketData->mTotalSent;
+ dict.mReceived += socketData->mTotalRecv;
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE;
+ socketData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestHttpConnections(nsINetDashboardCallback* aCallback) {
+ RefPtr<HttpData> httpData = new HttpData();
+ httpData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ httpData->mEventTarget = GetCurrentSerialEventTarget();
+
+ if (nsIOService::UseSocketProcess()) {
+ if (!gIOService->SocketProcessReady()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<Dashboard> self(this);
+ SocketProcessParent::GetSingleton()->SendGetHttpConnectionData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self{std::move(self)}, httpData](nsTArray<HttpRetParams>&& params) {
+ httpData->mData.Assign(std::move(params));
+ self->GetHttpConnections(httpData);
+ httpData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<HttpData>>(
+ "net::Dashboard::GetHttpConnections", self,
+ &Dashboard::GetHttpConnections, httpData),
+ NS_DISPATCH_NORMAL);
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {});
+ return NS_OK;
+ }
+
+ gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<HttpData>>(
+ "net::Dashboard::GetHttpDispatch", this,
+ &Dashboard::GetHttpDispatch, httpData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetHttpDispatch(HttpData* aHttpData) {
+ RefPtr<HttpData> httpData = aHttpData;
+ HttpInfo::GetHttpConnectionData(&httpData->mData);
+ httpData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<HttpData>>("net::Dashboard::GetHttpConnections",
+ this, &Dashboard::GetHttpConnections,
+ httpData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetHttpConnections(HttpData* aHttpData) {
+ RefPtr<HttpData> httpData = aHttpData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::HttpConnDict dict;
+ dict.mConnections.Construct();
+
+ using mozilla::dom::DnsAndSockInfoDict;
+ using mozilla::dom::HttpConnectionElement;
+ using mozilla::dom::HttpConnInfo;
+ Sequence<HttpConnectionElement>& connections = dict.mConnections.Value();
+
+ uint32_t length = httpData->mData.Length();
+ if (!connections.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < httpData->mData.Length(); i++) {
+ HttpConnectionElement& connection = *connections.AppendElement(fallible);
+
+ CopyASCIItoUTF16(httpData->mData[i].host, connection.mHost);
+ connection.mPort = httpData->mData[i].port;
+ CopyASCIItoUTF16(httpData->mData[i].httpVersion, connection.mHttpVersion);
+ connection.mSsl = httpData->mData[i].ssl;
+
+ connection.mActive.Construct();
+ connection.mIdle.Construct();
+ connection.mDnsAndSocks.Construct();
+
+ Sequence<HttpConnInfo>& active = connection.mActive.Value();
+ Sequence<HttpConnInfo>& idle = connection.mIdle.Value();
+ Sequence<DnsAndSockInfoDict>& dnsAndSocks = connection.mDnsAndSocks.Value();
+
+ if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) ||
+ !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) ||
+ !dnsAndSocks.SetCapacity(httpData->mData[i].dnsAndSocks.Length(),
+ fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].active.Length(); j++) {
+ HttpConnInfo& info = *active.AppendElement(fallible);
+ info.mRtt = httpData->mData[i].active[j].rtt;
+ info.mTtl = httpData->mData[i].active[j].ttl;
+ info.mProtocolVersion = httpData->mData[i].active[j].protocolVersion;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].idle.Length(); j++) {
+ HttpConnInfo& info = *idle.AppendElement(fallible);
+ info.mRtt = httpData->mData[i].idle[j].rtt;
+ info.mTtl = httpData->mData[i].idle[j].ttl;
+ info.mProtocolVersion = httpData->mData[i].idle[j].protocolVersion;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].dnsAndSocks.Length(); j++) {
+ DnsAndSockInfoDict& info = *dnsAndSocks.AppendElement(fallible);
+ info.mSpeculative = httpData->mData[i].dnsAndSocks[j].speculative;
+ }
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ httpData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::GetEnableLogging(bool* value) {
+ *value = mEnableLogging;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::SetEnableLogging(const bool value) {
+ mEnableLogging = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::AddHost(const nsACString& aHost, uint32_t aSerial, bool aEncrypted) {
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ LogData mData(nsCString(aHost), aSerial, aEncrypted);
+ if (mWs.data.Contains(mData)) {
+ return NS_OK;
+ }
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mWs.data.AppendElement(mData);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::RemoveHost(const nsACString& aHost, uint32_t aSerial) {
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1) return NS_ERROR_FAILURE;
+ mWs.data.RemoveElementAt(index);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::NewMsgSent(const nsACString& aHost, uint32_t aSerial,
+ uint32_t aLength) {
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1) return NS_ERROR_FAILURE;
+ mWs.data[index].mMsgSent++;
+ mWs.data[index].mSizeSent += aLength;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::NewMsgReceived(const nsACString& aHost, uint32_t aSerial,
+ uint32_t aLength) {
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
+ if (index == -1) return NS_ERROR_FAILURE;
+ mWs.data[index].mMsgReceived++;
+ mWs.data[index].mSizeReceived += aLength;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestWebsocketConnections(nsINetDashboardCallback* aCallback) {
+ RefPtr<WebSocketRequest> wsRequest = new WebSocketRequest();
+ wsRequest->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ wsRequest->mEventTarget = GetCurrentSerialEventTarget();
+
+ wsRequest->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<WebSocketRequest>>(
+ "net::Dashboard::GetWebSocketConnections", this,
+ &Dashboard::GetWebSocketConnections, wsRequest),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetWebSocketConnections(WebSocketRequest* aWsRequest) {
+ RefPtr<WebSocketRequest> wsRequest = aWsRequest;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::WebSocketDict dict;
+ dict.mWebsockets.Construct();
+ Sequence<mozilla::dom::WebSocketElement>& websockets =
+ dict.mWebsockets.Value();
+
+ mozilla::MutexAutoLock lock(mWs.lock);
+ uint32_t length = mWs.data.Length();
+ if (!websockets.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < mWs.data.Length(); i++) {
+ dom::WebSocketElement& websocket = *websockets.AppendElement(fallible);
+ CopyASCIItoUTF16(mWs.data[i].mHost, websocket.mHostport);
+ websocket.mMsgsent = mWs.data[i].mMsgSent;
+ websocket.mMsgreceived = mWs.data[i].mMsgReceived;
+ websocket.mSentsize = mWs.data[i].mSizeSent;
+ websocket.mReceivedsize = mWs.data[i].mSizeReceived;
+ websocket.mEncrypted = mWs.data[i].mEncrypted;
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ wsRequest->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSInfo(nsINetDashboardCallback* aCallback) {
+ RefPtr<DnsData> dnsData = new DnsData();
+ dnsData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+
+ nsresult rv;
+ dnsData->mData.Clear();
+ dnsData->mEventTarget = GetCurrentSerialEventTarget();
+
+ if (!mDnsService) {
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (nsIOService::UseSocketProcess()) {
+ if (!gIOService->SocketProcessReady()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<Dashboard> self(this);
+ SocketProcessParent::GetSingleton()->SendGetDNSCacheEntries()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self{std::move(self)},
+ dnsData{std::move(dnsData)}](nsTArray<DNSCacheEntries>&& entries) {
+ dnsData->mData.Assign(std::move(entries));
+ dnsData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<DnsData>>(
+ "net::Dashboard::GetDNSCacheEntries", self,
+ &Dashboard::GetDNSCacheEntries, dnsData),
+ NS_DISPATCH_NORMAL);
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {});
+ return NS_OK;
+ }
+
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod<RefPtr<DnsData>>("net::Dashboard::GetDnsInfoDispatch",
+ this, &Dashboard::GetDnsInfoDispatch,
+ dnsData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetDnsInfoDispatch(DnsData* aDnsData) {
+ RefPtr<DnsData> dnsData = aDnsData;
+ if (mDnsService) {
+ mDnsService->GetDNSCacheEntries(&dnsData->mData);
+ }
+ dnsData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<DnsData>>("net::Dashboard::GetDNSCacheEntries",
+ this, &Dashboard::GetDNSCacheEntries,
+ dnsData),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult Dashboard::GetDNSCacheEntries(DnsData* dnsData) {
+ AutoSafeJSContext cx;
+
+ mozilla::dom::DNSCacheDict dict;
+ dict.mEntries.Construct();
+ Sequence<mozilla::dom::DnsCacheEntry>& entries = dict.mEntries.Value();
+
+ uint32_t length = dnsData->mData.Length();
+ if (!entries.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < dnsData->mData.Length(); i++) {
+ dom::DnsCacheEntry& entry = *entries.AppendElement(fallible);
+ entry.mHostaddr.Construct();
+
+ Sequence<nsString>& addrs = entry.mHostaddr.Value();
+ if (!addrs.SetCapacity(dnsData->mData[i].hostaddr.Length(), fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CopyASCIItoUTF16(dnsData->mData[i].hostname, entry.mHostname);
+ entry.mExpiration = dnsData->mData[i].expiration;
+ entry.mTrr = dnsData->mData[i].TRR;
+
+ for (uint32_t j = 0; j < dnsData->mData[i].hostaddr.Length(); j++) {
+ nsString* addr = addrs.AppendElement(fallible);
+ if (!addr) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ CopyASCIItoUTF16(dnsData->mData[i].hostaddr[j], *addr);
+ }
+
+ if (dnsData->mData[i].family == PR_AF_INET6) {
+ entry.mFamily.AssignLiteral(u"ipv6");
+ } else {
+ entry.mFamily.AssignLiteral(u"ipv4");
+ }
+
+ entry.mOriginAttributesSuffix =
+ NS_ConvertUTF8toUTF16(dnsData->mData[i].originAttributesSuffix);
+ entry.mFlags = NS_ConvertUTF8toUTF16(dnsData->mData[i].flags);
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ dnsData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSLookup(const nsACString& aHost,
+ nsINetDashboardCallback* aCallback) {
+ nsresult rv;
+
+ if (!mDnsService) {
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ RefPtr<LookupHelper> helper = new LookupHelper();
+ helper->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ helper->mEventTarget = GetCurrentSerialEventTarget();
+ OriginAttributes attrs;
+ rv = mDnsService->AsyncResolveNative(
+ aHost, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, helper.get(),
+ NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel));
+ return rv;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSHTTPSRRLookup(const nsACString& aHost,
+ nsINetDashboardCallback* aCallback) {
+ nsresult rv;
+
+ if (!mDnsService) {
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ RefPtr<LookupHelper> helper = new LookupHelper();
+ helper->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ helper->mEventTarget = GetCurrentSerialEventTarget();
+ OriginAttributes attrs;
+ rv = mDnsService->AsyncResolveNative(
+ aHost, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, helper.get(),
+ NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel));
+ return rv;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestRcwnStats(nsINetDashboardCallback* aCallback) {
+ RefPtr<RcwnData> rcwnData = new RcwnData();
+ rcwnData->mEventTarget = GetCurrentSerialEventTarget();
+ rcwnData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+
+ return rcwnData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<RcwnData>>("net::Dashboard::GetRcwnData", this,
+ &Dashboard::GetRcwnData, rcwnData),
+ NS_DISPATCH_NORMAL);
+}
+
+nsresult Dashboard::GetRcwnData(RcwnData* aData) {
+ AutoSafeJSContext cx;
+ mozilla::dom::RcwnStatus dict;
+
+ dict.mTotalNetworkRequests = gIOService->GetTotalRequestNumber();
+ dict.mRcwnCacheWonCount = gIOService->GetCacheWonRequestNumber();
+ dict.mRcwnNetWonCount = gIOService->GetNetWonRequestNumber();
+
+ uint32_t cacheSlow, cacheNotSlow;
+ CacheFileUtils::CachePerfStats::GetSlowStats(&cacheSlow, &cacheNotSlow);
+ dict.mCacheSlowCount = cacheSlow;
+ dict.mCacheNotSlowCount = cacheNotSlow;
+
+ dict.mPerfStats.Construct();
+ Sequence<mozilla::dom::RcwnPerfStats>& perfStats = dict.mPerfStats.Value();
+ uint32_t length = CacheFileUtils::CachePerfStats::LAST;
+ if (!perfStats.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < length; i++) {
+ CacheFileUtils::CachePerfStats::EDataType perfType =
+ static_cast<CacheFileUtils::CachePerfStats::EDataType>(i);
+ dom::RcwnPerfStats& elem = *perfStats.AppendElement(fallible);
+ elem.mAvgShort =
+ CacheFileUtils::CachePerfStats::GetAverage(perfType, false);
+ elem.mAvgLong = CacheFileUtils::CachePerfStats::GetAverage(perfType, true);
+ elem.mStddevLong =
+ CacheFileUtils::CachePerfStats::GetStdDev(perfType, true);
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+void HttpConnInfo::SetHTTPProtocolVersion(HttpVersion pv) {
+ switch (pv) {
+ case HttpVersion::v0_9:
+ protocolVersion.AssignLiteral(u"http/0.9");
+ break;
+ case HttpVersion::v1_0:
+ protocolVersion.AssignLiteral(u"http/1.0");
+ break;
+ case HttpVersion::v1_1:
+ protocolVersion.AssignLiteral(u"http/1.1");
+ break;
+ case HttpVersion::v2_0:
+ protocolVersion.AssignLiteral(u"http/2");
+ break;
+ case HttpVersion::v3_0:
+ protocolVersion.AssignLiteral(u"http/3");
+ break;
+ default:
+ protocolVersion.AssignLiteral(u"unknown protocol version");
+ }
+}
+
+NS_IMETHODIMP
+Dashboard::GetLogPath(nsACString& aLogPath) {
+ aLogPath.SetLength(2048);
+ uint32_t len = LogModule::GetLogFile(aLogPath.BeginWriting(), 2048);
+ aLogPath.SetLength(len);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestConnection(const nsACString& aHost, uint32_t aPort,
+ const char* aProtocol, uint32_t aTimeout,
+ nsINetDashboardCallback* aCallback) {
+ nsresult rv;
+ RefPtr<ConnectionData> connectionData = new ConnectionData(this);
+ connectionData->mHost = aHost;
+ connectionData->mPort = aPort;
+ connectionData->mProtocol = aProtocol;
+ connectionData->mTimeout = aTimeout;
+
+ connectionData->mCallback =
+ new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ connectionData->mEventTarget = GetCurrentSerialEventTarget();
+
+ rv = TestNewConnection(connectionData);
+ if (NS_FAILED(rv)) {
+ mozilla::net::GetErrorString(rv, connectionData->mStatus);
+ connectionData->mEventTarget->Dispatch(
+ NewRunnableMethod<RefPtr<ConnectionData>>(
+ "net::Dashboard::GetConnectionStatus", this,
+ &Dashboard::GetConnectionStatus, connectionData),
+ NS_DISPATCH_NORMAL);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult Dashboard::GetConnectionStatus(ConnectionData* aConnectionData) {
+ RefPtr<ConnectionData> connectionData = aConnectionData;
+ AutoSafeJSContext cx;
+
+ mozilla::dom::ConnStatusDict dict;
+ dict.mStatus = connectionData->mStatus;
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE;
+
+ connectionData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+nsresult Dashboard::TestNewConnection(ConnectionData* aConnectionData) {
+ RefPtr<ConnectionData> connectionData = aConnectionData;
+
+ nsresult rv;
+ if (!connectionData->mHost.Length() ||
+ !net_IsValidHostName(connectionData->mHost)) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (connectionData->mProtocol.EqualsLiteral("ssl")) {
+ AutoTArray<nsCString, 1> socketTypes = {connectionData->mProtocol};
+ rv = gSocketTransportService->CreateTransport(
+ socketTypes, connectionData->mHost, connectionData->mPort, nullptr,
+ nullptr, getter_AddRefs(connectionData->mSocket));
+ } else {
+ rv = gSocketTransportService->CreateTransport(
+ nsTArray<nsCString>(), connectionData->mHost, connectionData->mPort,
+ nullptr, nullptr, getter_AddRefs(connectionData->mSocket));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = connectionData->mSocket->SetEventSink(connectionData,
+ GetCurrentSerialEventTarget());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = connectionData->mSocket->OpenInputStream(
+ nsITransport::OPEN_BLOCKING, 0, 0,
+ getter_AddRefs(connectionData->mStreamIn));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ connectionData->StartTimer(connectionData->mTimeout);
+
+ return rv;
+}
+
+using ErrorEntry = struct {
+ nsresult key;
+ const char* error;
+};
+
+#undef ERROR
+#define ERROR(key, val) \
+ { key, #key }
+
+ErrorEntry socketTransportStatuses[] = {
+ ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)),
+ ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)),
+ ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)),
+ ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)),
+ ERROR(NS_NET_STATUS_TLS_HANDSHAKE_STARTING, FAILURE(12)),
+ ERROR(NS_NET_STATUS_TLS_HANDSHAKE_ENDED, FAILURE(13)),
+ ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)),
+ ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)),
+ ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)),
+};
+#undef ERROR
+
+static void GetErrorString(nsresult rv, nsAString& errorString) {
+ for (auto& socketTransportStatus : socketTransportStatuses) {
+ if (socketTransportStatus.key == rv) {
+ errorString.AssignASCII(socketTransportStatus.error);
+ return;
+ }
+ }
+ nsAutoCString errorCString;
+ mozilla::GetErrorName(rv, errorCString);
+ CopyUTF8toUTF16(errorCString, errorString);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/Dashboard.h b/netwerk/base/Dashboard.h
new file mode 100644
index 0000000000..1112a013e9
--- /dev/null
+++ b/netwerk/base/Dashboard.h
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDashboard_h__
+#define nsDashboard_h__
+
+#include "mozilla/Mutex.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "nsIDashboard.h"
+#include "nsIDashboardEventNotifier.h"
+
+class nsIDNSService;
+
+namespace mozilla {
+namespace net {
+
+class SocketData;
+class HttpData;
+class DnsData;
+class WebSocketRequest;
+class ConnectionData;
+class RcwnData;
+
+class Dashboard final : public nsIDashboard, public nsIDashboardEventNotifier {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDASHBOARD
+ NS_DECL_NSIDASHBOARDEVENTNOTIFIER
+
+ Dashboard();
+ static const char* GetErrorString(nsresult rv);
+ nsresult GetConnectionStatus(ConnectionData* aConnectionData);
+
+ private:
+ struct LogData {
+ LogData(nsCString host, uint32_t serial, bool encryption)
+ : mHost(host),
+ mSerial(serial),
+ mMsgSent(0),
+ mMsgReceived(0),
+ mSizeSent(0),
+ mSizeReceived(0),
+ mEncrypted(encryption) {}
+ nsCString mHost;
+ uint32_t mSerial;
+ uint32_t mMsgSent;
+ uint32_t mMsgReceived;
+ uint64_t mSizeSent;
+ uint64_t mSizeReceived;
+ bool mEncrypted;
+ bool operator==(const LogData& a) const {
+ return mHost.Equals(a.mHost) && (mSerial == a.mSerial);
+ }
+ };
+
+ struct WebSocketData {
+ WebSocketData() : lock("Dashboard.webSocketData") {}
+ uint32_t IndexOf(const nsCString& hostname, uint32_t mSerial) {
+ LogData temp(hostname, mSerial, false);
+ return data.IndexOf(temp);
+ }
+ nsTArray<LogData> data;
+ mozilla::Mutex lock MOZ_UNANNOTATED;
+ };
+
+ bool mEnableLogging;
+ WebSocketData mWs;
+
+ private:
+ virtual ~Dashboard() = default;
+
+ nsresult GetSocketsDispatch(SocketData*);
+ nsresult GetHttpDispatch(HttpData*);
+ nsresult GetDnsInfoDispatch(DnsData*);
+ nsresult TestNewConnection(ConnectionData*);
+
+ /* Helper methods that pass the JSON to the callback function. */
+ nsresult GetSockets(SocketData*);
+ nsresult GetHttpConnections(HttpData*);
+ nsresult GetDNSCacheEntries(DnsData*);
+ nsresult GetWebSocketConnections(WebSocketRequest*);
+ nsresult GetRcwnData(RcwnData*);
+
+ nsCOMPtr<nsIDNSService> mDnsService;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsDashboard_h__
diff --git a/netwerk/base/DashboardTypes.h b/netwerk/base/DashboardTypes.h
new file mode 100644
index 0000000000..6dd901d02a
--- /dev/null
+++ b/netwerk/base/DashboardTypes.h
@@ -0,0 +1,175 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_DashboardTypes_h_
+#define mozilla_net_DashboardTypes_h_
+
+#include "ipc/IPCMessageUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsHttp.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+struct SocketInfo {
+ nsCString host;
+ uint64_t sent;
+ uint64_t received;
+ uint16_t port;
+ bool active;
+ nsCString type;
+};
+
+inline bool operator==(const SocketInfo& a, const SocketInfo& b) {
+ return a.host == b.host && a.sent == b.sent && a.received == b.received &&
+ a.port == b.port && a.active == b.active && a.type == b.type;
+}
+
+struct DnsAndConnectSockets {
+ bool speculative;
+};
+
+struct DNSCacheEntries {
+ nsCString hostname;
+ nsTArray<nsCString> hostaddr;
+ uint16_t family;
+ int64_t expiration;
+ nsCString netInterface;
+ bool TRR;
+ nsCString originAttributesSuffix;
+ nsCString flags;
+};
+
+struct HttpConnInfo {
+ uint32_t ttl;
+ uint32_t rtt;
+ nsString protocolVersion;
+
+ void SetHTTPProtocolVersion(HttpVersion pv);
+};
+
+struct HttpRetParams {
+ nsCString host;
+ CopyableTArray<HttpConnInfo> active;
+ CopyableTArray<HttpConnInfo> idle;
+ CopyableTArray<DnsAndConnectSockets> dnsAndSocks;
+ uint32_t counter;
+ uint16_t port;
+ nsCString httpVersion;
+ bool ssl;
+};
+
+} // namespace net
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::net::SocketInfo> {
+ typedef mozilla::net::SocketInfo paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.host);
+ WriteParam(aWriter, aParam.sent);
+ WriteParam(aWriter, aParam.received);
+ WriteParam(aWriter, aParam.port);
+ WriteParam(aWriter, aParam.active);
+ WriteParam(aWriter, aParam.type);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->host) &&
+ ReadParam(aReader, &aResult->sent) &&
+ ReadParam(aReader, &aResult->received) &&
+ ReadParam(aReader, &aResult->port) &&
+ ReadParam(aReader, &aResult->active) &&
+ ReadParam(aReader, &aResult->type);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::DNSCacheEntries> {
+ typedef mozilla::net::DNSCacheEntries paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.hostname);
+ WriteParam(aWriter, aParam.hostaddr);
+ WriteParam(aWriter, aParam.family);
+ WriteParam(aWriter, aParam.expiration);
+ WriteParam(aWriter, aParam.netInterface);
+ WriteParam(aWriter, aParam.TRR);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->hostname) &&
+ ReadParam(aReader, &aResult->hostaddr) &&
+ ReadParam(aReader, &aResult->family) &&
+ ReadParam(aReader, &aResult->expiration) &&
+ ReadParam(aReader, &aResult->netInterface) &&
+ ReadParam(aReader, &aResult->TRR);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::DnsAndConnectSockets> {
+ typedef mozilla::net::DnsAndConnectSockets paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.speculative);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->speculative);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::HttpConnInfo> {
+ typedef mozilla::net::HttpConnInfo paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.ttl);
+ WriteParam(aWriter, aParam.rtt);
+ WriteParam(aWriter, aParam.protocolVersion);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->ttl) &&
+ ReadParam(aReader, &aResult->rtt) &&
+ ReadParam(aReader, &aResult->protocolVersion);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::HttpRetParams> {
+ typedef mozilla::net::HttpRetParams paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.host);
+ WriteParam(aWriter, aParam.active);
+ WriteParam(aWriter, aParam.idle);
+ WriteParam(aWriter, aParam.dnsAndSocks);
+ WriteParam(aWriter, aParam.counter);
+ WriteParam(aWriter, aParam.port);
+ WriteParam(aWriter, aParam.httpVersion);
+ WriteParam(aWriter, aParam.ssl);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->host) &&
+ ReadParam(aReader, &aResult->active) &&
+ ReadParam(aReader, &aResult->idle) &&
+ ReadParam(aReader, &aResult->dnsAndSocks) &&
+ ReadParam(aReader, &aResult->counter) &&
+ ReadParam(aReader, &aResult->port) &&
+ ReadParam(aReader, &aResult->httpVersion) &&
+ ReadParam(aReader, &aResult->ssl);
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_net_DashboardTypes_h_
diff --git a/netwerk/base/DefaultURI.cpp b/netwerk/base/DefaultURI.cpp
new file mode 100644
index 0000000000..0a70218af5
--- /dev/null
+++ b/netwerk/base/DefaultURI.cpp
@@ -0,0 +1,572 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DefaultURI.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsURLHelper.h"
+
+#include "mozilla/ipc/URIParams.h"
+
+namespace mozilla {
+namespace net {
+
+#define NS_DEFAULTURI_CID \
+ { /* 04445aa0-fd27-4c99-bd41-6be6318ae92c */ \
+ 0x04445aa0, 0xfd27, 0x4c99, { \
+ 0xbd, 0x41, 0x6b, 0xe6, 0x31, 0x8a, 0xe9, 0x2c \
+ } \
+ }
+
+#define ASSIGN_AND_ADDREF_THIS(ptrToMutator) \
+ do { \
+ if (ptrToMutator) { \
+ *(ptrToMutator) = do_AddRef(this).take(); \
+ } \
+ } while (0)
+
+static NS_DEFINE_CID(kDefaultURICID, NS_DEFAULTURI_CID);
+
+//----------------------------------------------------------------------------
+// nsIClassInfo
+//----------------------------------------------------------------------------
+
+NS_IMPL_CLASSINFO(DefaultURI, nullptr, nsIClassInfo::THREADSAFE,
+ NS_DEFAULTURI_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(DefaultURI)
+
+//----------------------------------------------------------------------------
+// nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(DefaultURI)
+NS_IMPL_RELEASE(DefaultURI)
+NS_INTERFACE_TABLE_HEAD(DefaultURI)
+ NS_INTERFACE_TABLE(DefaultURI, nsIURI, nsISerializable)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE
+ NS_IMPL_QUERY_CLASSINFO(DefaultURI)
+ if (aIID.Equals(kDefaultURICID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsISizeOf)
+NS_INTERFACE_MAP_END
+
+//----------------------------------------------------------------------------
+// nsISerializable
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP DefaultURI::Read(nsIObjectInputStream* aInputStream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DefaultURI::Write(nsIObjectOutputStream* aOutputStream) {
+ nsAutoCString spec(mURL->Spec());
+ return aOutputStream->WriteStringZ(spec.get());
+}
+
+//----------------------------------------------------------------------------
+// nsISizeOf
+//----------------------------------------------------------------------------
+
+size_t DefaultURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mURL->SizeOf();
+}
+
+size_t DefaultURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+//----------------------------------------------------------------------------
+// nsIURI
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP DefaultURI::GetSpec(nsACString& aSpec) {
+ aSpec = mURL->Spec();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetPrePath(nsACString& aPrePath) {
+ aPrePath = mURL->PrePath();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetScheme(nsACString& aScheme) {
+ aScheme = mURL->Scheme();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetUserPass(nsACString& aUserPass) {
+ aUserPass = mURL->Username();
+ nsAutoCString pass(mURL->Password());
+ if (pass.IsEmpty()) {
+ return NS_OK;
+ }
+ aUserPass.Append(':');
+ aUserPass.Append(pass);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetUsername(nsACString& aUsername) {
+ aUsername = mURL->Username();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetPassword(nsACString& aPassword) {
+ aPassword = mURL->Password();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetHostPort(nsACString& aHostPort) {
+ aHostPort = mURL->HostPort();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetHost(nsACString& aHost) {
+ aHost = mURL->Host();
+
+ // Historically nsIURI.host has always returned an IPv6 address that isn't
+ // enclosed in brackets. Ideally we want to change that, but for the sake of
+ // consitency we'll leave it like that for the moment.
+ // Bug 1603199 should fix this.
+ if (StringBeginsWith(aHost, "["_ns) && StringEndsWith(aHost, "]"_ns) &&
+ aHost.FindChar(':') != kNotFound) {
+ aHost = Substring(aHost, 1, aHost.Length() - 2);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetPort(int32_t* aPort) {
+ *aPort = mURL->Port();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetPathQueryRef(nsACString& aPathQueryRef) {
+ aPathQueryRef = mURL->Path();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::Equals(nsIURI* other, bool* _retval) {
+ if (!other) {
+ *_retval = false;
+ return NS_OK;
+ }
+ RefPtr<DefaultURI> otherUri;
+ nsresult rv = other->QueryInterface(kDefaultURICID, getter_AddRefs(otherUri));
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ *_retval = mURL->Spec() == otherUri->mURL->Spec();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::SchemeIs(const char* scheme, bool* _retval) {
+ if (!scheme) {
+ *_retval = false;
+ return NS_OK;
+ }
+ *_retval = mURL->Scheme().Equals(scheme);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::Resolve(const nsACString& aRelativePath,
+ nsACString& aResult) {
+ nsAutoCString scheme;
+ nsresult rv = net_ExtractURLScheme(aRelativePath, scheme);
+ if (NS_SUCCEEDED(rv)) {
+ aResult = aRelativePath;
+ return NS_OK;
+ }
+
+ // We try to create another URL with this one as its base.
+ RefPtr<MozURL> resolvedURL;
+ rv = MozURL::Init(getter_AddRefs(resolvedURL), aRelativePath, mURL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If parsing the relative url fails, we revert to the previous behaviour
+ // and just return the relative path.
+ aResult = aRelativePath;
+ return NS_OK;
+ }
+
+ aResult = resolvedURL->Spec();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetAsciiSpec(nsACString& aAsciiSpec) {
+ return GetSpec(aAsciiSpec);
+}
+
+NS_IMETHODIMP DefaultURI::GetAsciiHostPort(nsACString& aAsciiHostPort) {
+ return GetHostPort(aAsciiHostPort);
+}
+
+NS_IMETHODIMP DefaultURI::GetAsciiHost(nsACString& aAsciiHost) {
+ return GetHost(aAsciiHost);
+}
+
+NS_IMETHODIMP DefaultURI::GetRef(nsACString& aRef) {
+ aRef = mURL->Ref();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::EqualsExceptRef(nsIURI* other, bool* _retval) {
+ if (!_retval || !other) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ RefPtr<DefaultURI> otherUri;
+ nsresult rv = other->QueryInterface(kDefaultURICID, getter_AddRefs(otherUri));
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ *_retval = mURL->SpecNoRef().Equals(otherUri->mURL->SpecNoRef());
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetSpecIgnoringRef(nsACString& aSpecIgnoringRef) {
+ aSpecIgnoringRef = mURL->SpecNoRef();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetHasRef(bool* aHasRef) {
+ *aHasRef = mURL->HasFragment();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetHasUserPass(bool* aHasUserPass) {
+ *aHasUserPass = !mURL->Username().IsEmpty() || !mURL->Password().IsEmpty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetFilePath(nsACString& aFilePath) {
+ aFilePath = mURL->FilePath();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetQuery(nsACString& aQuery) {
+ aQuery = mURL->Query();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetHasQuery(bool* aHasQuery) {
+ *aHasQuery = mURL->HasQuery();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplayHost(nsACString& aDisplayHost) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetHost(aDisplayHost);
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplayHostPort(nsACString& aDisplayHostPort) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetHostPort(aDisplayHostPort);
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplaySpec(nsACString& aDisplaySpec) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetSpec(aDisplaySpec);
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplayPrePath(nsACString& aDisplayPrePath) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetPrePath(aDisplayPrePath);
+}
+
+NS_IMETHODIMP DefaultURI::Mutate(nsIURIMutator** _retval) {
+ RefPtr<DefaultURI::Mutator> mutator = new DefaultURI::Mutator();
+ mutator->Init(this);
+ mutator.forget(_retval);
+ return NS_OK;
+}
+
+void DefaultURI::Serialize(ipc::URIParams& aParams) {
+ ipc::DefaultURIParams params;
+ params.spec() = mURL->Spec();
+ aParams = params;
+}
+
+//----------------------------------------------------------------------------
+// nsIURIMutator
+//----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(DefaultURI::Mutator)
+NS_IMPL_RELEASE(DefaultURI::Mutator)
+NS_IMETHODIMP DefaultURI::Mutator::QueryInterface(REFNSIID aIID,
+ void** aInstancePtr) {
+ NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!");
+ nsISupports* foundInterface = nullptr;
+ if (aIID.Equals(NS_GET_IID(nsIURI))) {
+ RefPtr<DefaultURI> defaultURI = new DefaultURI();
+ mMutator->Finalize(getter_AddRefs(defaultURI->mURL));
+ foundInterface =
+ static_cast<nsISupports*>(static_cast<nsIURI*>((defaultURI.get())));
+ NS_ADDREF(foundInterface);
+ *aInstancePtr = foundInterface;
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIURIMutator)) ||
+ aIID.Equals(NS_GET_IID(nsISupports))) {
+ foundInterface =
+ static_cast<nsISupports*>(static_cast<nsIURIMutator*>(this));
+ } else if (aIID.Equals(NS_GET_IID(nsIURISetters))) {
+ foundInterface =
+ static_cast<nsISupports*>(static_cast<nsIURISetters*>(this));
+ } else if (aIID.Equals(NS_GET_IID(nsIURISetSpec))) {
+ foundInterface =
+ static_cast<nsISupports*>(static_cast<nsIURISetSpec*>(this));
+ } else if (aIID.Equals(NS_GET_IID(nsISerializable))) {
+ foundInterface =
+ static_cast<nsISupports*>(static_cast<nsISerializable*>(this));
+ }
+
+ if (foundInterface) {
+ NS_ADDREF(foundInterface);
+ *aInstancePtr = foundInterface;
+ return NS_OK;
+ }
+
+ return NS_NOINTERFACE;
+}
+
+NS_IMETHODIMP DefaultURI::Mutator::Read(nsIObjectInputStream* aStream) {
+ nsAutoCString spec;
+ nsresult rv = aStream->ReadCString(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return SetSpec(spec, nullptr);
+}
+
+NS_IMETHODIMP DefaultURI::Mutator::Deserialize(
+ const mozilla::ipc::URIParams& aParams) {
+ if (aParams.type() != ipc::URIParams::TDefaultURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return NS_ERROR_FAILURE;
+ }
+
+ const ipc::DefaultURIParams& params = aParams.get_DefaultURIParams();
+ auto result = MozURL::Mutator::FromSpec(params.spec());
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+ mMutator = Some(result.unwrap());
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::Mutator::Finalize(nsIURI** aURI) {
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ RefPtr<DefaultURI> uri = new DefaultURI();
+ mMutator->Finalize(getter_AddRefs(uri->mURL));
+ mMutator = Nothing();
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::Mutator::SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ auto result = MozURL::Mutator::FromSpec(aSpec);
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+ mMutator = Some(result.unwrap());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetScheme(const nsACString& aScheme,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetScheme(aScheme);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetUserPass(const nsACString& aUserPass,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ int32_t index = aUserPass.FindChar(':');
+ if (index == kNotFound) {
+ mMutator->SetUsername(aUserPass);
+ mMutator->SetPassword(""_ns);
+ return mMutator->GetStatus();
+ }
+
+ mMutator->SetUsername(Substring(aUserPass, 0, index));
+ nsresult rv = mMutator->GetStatus();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mMutator->SetPassword(Substring(aUserPass, index + 1));
+ rv = mMutator->GetStatus();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetUsername(const nsACString& aUsername,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetUsername(aUsername);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetPassword(const nsACString& aPassword,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetPassword(aPassword);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetHostPort(const nsACString& aHostPort,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetHostPort(aHostPort);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetHost(const nsACString& aHost,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetHostname(aHost);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetPort(int32_t aPort, nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetPort(aPort);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetPathQueryRef(const nsACString& aPathQueryRef,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (aPathQueryRef.IsEmpty()) {
+ mMutator->SetFilePath(""_ns);
+ mMutator->SetQuery(""_ns);
+ mMutator->SetRef(""_ns);
+ return mMutator->GetStatus();
+ }
+
+ RefPtr<MozURL> url;
+ mMutator->Finalize(getter_AddRefs(url));
+ mMutator = Nothing();
+
+ if (!url) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString pathQueryRef(aPathQueryRef);
+ if (url->CannotBeABase()) {
+ // If the base URL cannot be a base, then setting the pathQueryRef
+ // needs to change everything after the scheme.
+ pathQueryRef.Insert(":", 0);
+ pathQueryRef.Insert(url->Scheme(), 0);
+ // Clear the URL to make sure FromSpec creates an absolute URL
+ url = nullptr;
+ } else if (!StringBeginsWith(pathQueryRef, "/"_ns)) {
+ // If the base URL can be a base, make sure the path
+ // begins with a /
+ pathQueryRef.Insert('/', 0);
+ }
+
+ auto result = MozURL::Mutator::FromSpec(pathQueryRef, url);
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+ mMutator = Some(result.unwrap());
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetRef(const nsACString& aRef, nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetRef(aRef);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetFilePath(const nsACString& aFilePath,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetFilePath(aFilePath);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetQuery(const nsACString& aQuery,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetQuery(aQuery);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetQueryWithEncoding(const nsACString& aQuery,
+ const mozilla::Encoding* aEncoding,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ // we only support UTF-8 for DefaultURI
+ mMutator->SetQuery(aQuery);
+ return mMutator->GetStatus();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/DefaultURI.h b/netwerk/base/DefaultURI.h
new file mode 100644
index 0000000000..e737067565
--- /dev/null
+++ b/netwerk/base/DefaultURI.h
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DefaultURI_h__
+#define DefaultURI_h__
+
+#include "nsIURI.h"
+#include "nsISerializable.h"
+#include "nsISizeOf.h"
+#include "nsIURIMutator.h"
+#include "mozilla/net/MozURL.h"
+
+namespace mozilla {
+namespace net {
+
+class DefaultURI : public nsIURI, public nsISerializable, public nsISizeOf {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSISERIALIZABLE
+
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ class Mutator final : public nsIURIMutator, public nsISerializable {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURISETSPEC
+ NS_DECL_NSIURISETTERS
+ NS_DECL_NSIURIMUTATOR
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ MOZ_ASSERT_UNREACHABLE("nsIURIMutator.write() should never be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+ void Init(DefaultURI* aFrom) { mMutator = Some(aFrom->mURL->Mutate()); }
+
+ Maybe<MozURL::Mutator> mMutator;
+
+ friend class DefaultURI;
+ };
+
+ private:
+ virtual ~DefaultURI() = default;
+ RefPtr<MozURL> mURL;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // DefaultURI_h__
diff --git a/netwerk/base/EventTokenBucket.cpp b/netwerk/base/EventTokenBucket.cpp
new file mode 100644
index 0000000000..87179d6732
--- /dev/null
+++ b/netwerk/base/EventTokenBucket.cpp
@@ -0,0 +1,410 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EventTokenBucket.h"
+
+#include "nsICancelable.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransportService2.h"
+#ifdef DEBUG
+# include "MainThreadUtils.h"
+#endif
+
+#ifdef XP_WIN
+# include <windows.h>
+# include <mmsystem.h>
+#endif
+
+namespace mozilla {
+namespace net {
+
+////////////////////////////////////////////
+// EventTokenBucketCancelable
+////////////////////////////////////////////
+
+class TokenBucketCancelable : public nsICancelable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ explicit TokenBucketCancelable(class ATokenBucketEvent* event);
+ void Fire();
+
+ private:
+ virtual ~TokenBucketCancelable() = default;
+
+ friend class EventTokenBucket;
+ ATokenBucketEvent* mEvent;
+};
+
+NS_IMPL_ISUPPORTS(TokenBucketCancelable, nsICancelable)
+
+TokenBucketCancelable::TokenBucketCancelable(ATokenBucketEvent* event)
+ : mEvent(event) {}
+
+NS_IMETHODIMP
+TokenBucketCancelable::Cancel(nsresult reason) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mEvent = nullptr;
+ return NS_OK;
+}
+
+void TokenBucketCancelable::Fire() {
+ if (!mEvent) return;
+
+ ATokenBucketEvent* event = mEvent;
+ mEvent = nullptr;
+ event->OnTokenBucketAdmitted();
+}
+
+////////////////////////////////////////////
+// EventTokenBucket
+////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(EventTokenBucket, nsITimerCallback, nsINamed)
+
+// by default 1hz with no burst
+EventTokenBucket::EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize)
+ : mUnitCost(kUsecPerSec),
+ mMaxCredit(kUsecPerSec),
+ mCredit(kUsecPerSec),
+ mPaused(false),
+ mStopped(false),
+ mTimerArmed(false)
+#ifdef XP_WIN
+ ,
+ mFineGrainTimerInUse(false),
+ mFineGrainResetTimerArmed(false)
+#endif
+{
+ mLastUpdate = TimeStamp::Now();
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ }
+ if (NS_SUCCEEDED(rv)) mTimer = NS_NewTimer(sts);
+ SetRate(eventsPerSecond, burstSize);
+}
+
+EventTokenBucket::~EventTokenBucket() {
+ SOCKET_LOG(
+ ("EventTokenBucket::dtor %p events=%zu\n", this, mEvents.GetSize()));
+
+ CleanupTimers();
+
+ // Complete any queued events to prevent hangs
+ while (mEvents.GetSize()) {
+ RefPtr<TokenBucketCancelable> cancelable = mEvents.PopFront();
+ cancelable->Fire();
+ }
+}
+
+void EventTokenBucket::CleanupTimers() {
+ if (mTimer && mTimerArmed) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+ mTimerArmed = false;
+
+#ifdef XP_WIN
+ NormalTimers();
+ if (mFineGrainResetTimer && mFineGrainResetTimerArmed) {
+ mFineGrainResetTimer->Cancel();
+ }
+ mFineGrainResetTimer = nullptr;
+ mFineGrainResetTimerArmed = false;
+#endif
+}
+
+void EventTokenBucket::SetRate(uint32_t eventsPerSecond, uint32_t burstSize) {
+ SOCKET_LOG(("EventTokenBucket::SetRate %p %u %u\n", this, eventsPerSecond,
+ burstSize));
+
+ if (eventsPerSecond > kMaxHz) {
+ eventsPerSecond = kMaxHz;
+ SOCKET_LOG((" eventsPerSecond out of range\n"));
+ }
+
+ if (!eventsPerSecond) {
+ eventsPerSecond = 1;
+ SOCKET_LOG((" eventsPerSecond out of range\n"));
+ }
+
+ mUnitCost = kUsecPerSec / eventsPerSecond;
+ mMaxCredit = mUnitCost * burstSize;
+ if (mMaxCredit > kUsecPerSec * 60 * 15) {
+ SOCKET_LOG((" burstSize out of range\n"));
+ mMaxCredit = kUsecPerSec * 60 * 15;
+ }
+ mCredit = mMaxCredit;
+ mLastUpdate = TimeStamp::Now();
+}
+
+void EventTokenBucket::ClearCredits() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::ClearCredits %p\n", this));
+ mCredit = 0;
+}
+
+uint32_t EventTokenBucket::BurstEventsAvailable() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return static_cast<uint32_t>(mCredit / mUnitCost);
+}
+
+uint32_t EventTokenBucket::QueuedEvents() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ return mEvents.GetSize();
+}
+
+void EventTokenBucket::Pause() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::Pause %p\n", this));
+ if (mPaused || mStopped) return;
+
+ mPaused = true;
+ if (mTimerArmed) {
+ mTimer->Cancel();
+ mTimerArmed = false;
+ }
+}
+
+void EventTokenBucket::UnPause() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::UnPause %p\n", this));
+ if (!mPaused || mStopped) return;
+
+ mPaused = false;
+ DispatchEvents();
+ UpdateTimer();
+}
+
+void EventTokenBucket::Stop() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::Stop %p armed=%d\n", this, mTimerArmed));
+ mStopped = true;
+ CleanupTimers();
+
+ // Complete any queued events to prevent hangs
+ while (mEvents.GetSize()) {
+ RefPtr<TokenBucketCancelable> cancelable = mEvents.PopFront();
+ cancelable->Fire();
+ }
+}
+
+nsresult EventTokenBucket::SubmitEvent(ATokenBucketEvent* event,
+ nsICancelable** cancelable) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::SubmitEvent %p\n", this));
+
+ if (mStopped || !mTimer) return NS_ERROR_FAILURE;
+
+ UpdateCredits();
+
+ RefPtr<TokenBucketCancelable> cancelEvent = new TokenBucketCancelable(event);
+ // When this function exits the cancelEvent needs 2 references, one for the
+ // mEvents queue and one for the caller of SubmitEvent()
+
+ *cancelable = do_AddRef(cancelEvent).take();
+
+ if (mPaused || !TryImmediateDispatch(cancelEvent.get())) {
+ // queue it
+ SOCKET_LOG((" queued\n"));
+ mEvents.Push(cancelEvent.forget());
+ UpdateTimer();
+ } else {
+ SOCKET_LOG((" dispatched synchronously\n"));
+ }
+
+ return NS_OK;
+}
+
+bool EventTokenBucket::TryImmediateDispatch(TokenBucketCancelable* cancelable) {
+ if (mCredit < mUnitCost) return false;
+
+ mCredit -= mUnitCost;
+ cancelable->Fire();
+ return true;
+}
+
+void EventTokenBucket::DispatchEvents() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(("EventTokenBucket::DispatchEvents %p %d\n", this, mPaused));
+ if (mPaused || mStopped) return;
+
+ while (mEvents.GetSize() && mUnitCost <= mCredit) {
+ RefPtr<TokenBucketCancelable> cancelable = mEvents.PopFront();
+ if (cancelable->mEvent) {
+ SOCKET_LOG(
+ ("EventTokenBucket::DispachEvents [%p] "
+ "Dispatching queue token bucket event cost=%" PRIu64
+ " credit=%" PRIu64 "\n",
+ this, mUnitCost, mCredit));
+ mCredit -= mUnitCost;
+ cancelable->Fire();
+ }
+ }
+
+#ifdef XP_WIN
+ if (!mEvents.GetSize()) WantNormalTimers();
+#endif
+}
+
+void EventTokenBucket::UpdateTimer() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mTimerArmed || mPaused || mStopped || !mEvents.GetSize() || !mTimer) {
+ return;
+ }
+
+ if (mCredit >= mUnitCost) return;
+
+ // determine the time needed to wait to accumulate enough credits to admit
+ // one more event and set the timer for that point. Always round it
+ // up because firing early doesn't help.
+ //
+ uint64_t deficit = mUnitCost - mCredit;
+ uint64_t msecWait = (deficit + (kUsecPerMsec - 1)) / kUsecPerMsec;
+
+ if (msecWait < 4) { // minimum wait
+ msecWait = 4;
+ } else if (msecWait > 60000) { // maximum wait
+ msecWait = 60000;
+ }
+
+#ifdef XP_WIN
+ FineGrainTimers();
+#endif
+
+ SOCKET_LOG(
+ ("EventTokenBucket::UpdateTimer %p for %" PRIu64 "ms\n", this, msecWait));
+ nsresult rv = mTimer->InitWithCallback(this, static_cast<uint32_t>(msecWait),
+ nsITimer::TYPE_ONE_SHOT);
+ mTimerArmed = NS_SUCCEEDED(rv);
+}
+
+NS_IMETHODIMP
+EventTokenBucket::Notify(nsITimer* timer) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+#ifdef XP_WIN
+ if (timer == mFineGrainResetTimer) {
+ FineGrainResetTimerNotify();
+ return NS_OK;
+ }
+#endif
+
+ SOCKET_LOG(("EventTokenBucket::Notify() %p\n", this));
+ mTimerArmed = false;
+ if (mStopped) return NS_OK;
+
+ UpdateCredits();
+ DispatchEvents();
+ UpdateTimer();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EventTokenBucket::GetName(nsACString& aName) {
+ aName.AssignLiteral("EventTokenBucket");
+ return NS_OK;
+}
+
+void EventTokenBucket::UpdateCredits() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration elapsed = now - mLastUpdate;
+ mLastUpdate = now;
+
+ mCredit += static_cast<uint64_t>(elapsed.ToMicroseconds());
+ if (mCredit > mMaxCredit) mCredit = mMaxCredit;
+ SOCKET_LOG(("EventTokenBucket::UpdateCredits %p to %" PRIu64 " (%" PRIu64
+ " each.. %3.2f)\n",
+ this, mCredit, mUnitCost, (double)mCredit / mUnitCost));
+}
+
+#ifdef XP_WIN
+void EventTokenBucket::FineGrainTimers() {
+ SOCKET_LOG(("EventTokenBucket::FineGrainTimers %p mFineGrainTimerInUse=%d\n",
+ this, mFineGrainTimerInUse));
+
+ mLastFineGrainTimerUse = TimeStamp::Now();
+
+ if (mFineGrainTimerInUse) return;
+
+ if (mUnitCost > kCostFineGrainThreshold) return;
+
+ SOCKET_LOG(
+ ("EventTokenBucket::FineGrainTimers %p timeBeginPeriod()\n", this));
+
+ mFineGrainTimerInUse = true;
+ timeBeginPeriod(1);
+}
+
+void EventTokenBucket::NormalTimers() {
+ if (!mFineGrainTimerInUse) return;
+ mFineGrainTimerInUse = false;
+
+ SOCKET_LOG(("EventTokenBucket::NormalTimers %p timeEndPeriod()\n", this));
+ timeEndPeriod(1);
+}
+
+void EventTokenBucket::WantNormalTimers() {
+ if (!mFineGrainTimerInUse) return;
+ if (mFineGrainResetTimerArmed) return;
+
+ TimeDuration elapsed(TimeStamp::Now() - mLastFineGrainTimerUse);
+ static const TimeDuration fiveSeconds = TimeDuration::FromSeconds(5);
+
+ if (elapsed >= fiveSeconds) {
+ NormalTimers();
+ return;
+ }
+
+ if (!mFineGrainResetTimer) mFineGrainResetTimer = NS_NewTimer();
+
+ // if we can't delay the reset, just do it now
+ if (!mFineGrainResetTimer) {
+ NormalTimers();
+ return;
+ }
+
+ // pad the callback out 100ms to avoid having to round trip this again if the
+ // timer calls back just a tad early.
+ SOCKET_LOG(
+ ("EventTokenBucket::WantNormalTimers %p "
+ "Will reset timer granularity after delay",
+ this));
+
+ mFineGrainResetTimer->InitWithCallback(
+ this,
+ static_cast<uint32_t>((fiveSeconds - elapsed).ToMilliseconds()) + 100,
+ nsITimer::TYPE_ONE_SHOT);
+ mFineGrainResetTimerArmed = true;
+}
+
+void EventTokenBucket::FineGrainResetTimerNotify() {
+ SOCKET_LOG(("EventTokenBucket::FineGrainResetTimerNotify(%p) events = %zd\n",
+ this, mEvents.GetSize()));
+ mFineGrainResetTimerArmed = false;
+
+ // If we are currently processing events then wait for the queue to drain
+ // before trying to reset back to normal timers again
+ if (!mEvents.GetSize()) WantNormalTimers();
+}
+
+#endif
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/EventTokenBucket.h b/netwerk/base/EventTokenBucket.h
new file mode 100644
index 0000000000..4206a622f5
--- /dev/null
+++ b/netwerk/base/EventTokenBucket.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetEventTokenBucket_h__
+#define NetEventTokenBucket_h__
+
+#include "ARefBase.h"
+#include "nsCOMPtr.h"
+#include "nsDeque.h"
+#include "nsINamed.h"
+#include "nsITimer.h"
+
+#include "mozilla/TimeStamp.h"
+
+class nsICancelable;
+
+namespace mozilla {
+namespace net {
+
+/* A token bucket is used to govern the maximum rate a series of events
+ can be executed at. For instance if your event was "eat a piece of cake"
+ then a token bucket configured to allow "1 piece per day" would spread
+ the eating of a 8 piece cake over 8 days even if you tried to eat the
+ whole thing up front. In a practical sense it 'costs' 1 token to execute
+ an event and tokens are 'earned' at a particular rate as time goes by.
+
+ The token bucket can be perfectly smooth or allow a configurable amount of
+ burstiness. A bursty token bucket allows you to save up unused credits, while
+ a perfectly smooth one would not. A smooth "1 per day" cake token bucket
+ would require 9 days to eat that cake if you skipped a slice on day 4
+ (use the token or lose it), while a token bucket configured with a burst
+ of 2 would just let you eat 2 slices on day 5 (the credits for day 4 and day
+ 5) and finish the cake in the usual 8 days.
+
+ EventTokenBucket(hz=20, burst=5) creates a token bucket with the following
+ properties:
+
+ + events from an infinite stream will be admitted 20 times per second (i.e.
+ hz=20 means 1 event per 50 ms). Timers will be used to space things evenly
+ down to 5ms gaps (i.e. up to 200hz). Token buckets with rates greater than
+ 200hz will admit multiple events with 5ms gaps between them. 10000hz is the
+ maximum rate and 1hz is the minimum rate.
+
+ + The burst size controls the limit of 'credits' that a token bucket can
+ accumulate when idle. For our (20,5) example each event requires 50ms of
+ credit (again, 20hz = 50ms per event). a burst size of 5 means that the
+ token bucket can accumulate a maximum of 250ms (5 * 50ms) for this bucket.
+ If no events have been admitted for the last full second the bucket can
+ still only accumulate 250ms of credit - but that credit means that 5 events
+ can be admitted without delay. A burst size of 1 is the minimum. The
+ EventTokenBucket is created with maximum credits already applied, but they
+ can be cleared with the ClearCredits() method. The maximum burst size is 15
+ minutes worth of events.
+
+ + An event is submitted to the token bucket asynchronously through
+ SubmitEvent(). The OnTokenBucketAdmitted() method of the submitted event
+ is used as a callback when the event is ready to run. A cancelable event is
+ returned to the SubmitEvent() caller for use in the case they do not wish
+ to wait for the callback.
+*/
+
+class EventTokenBucket;
+
+class ATokenBucketEvent {
+ public:
+ virtual void OnTokenBucketAdmitted() = 0;
+};
+
+class TokenBucketCancelable;
+
+class EventTokenBucket : public nsITimerCallback,
+ public nsINamed,
+ public ARefBase {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ // This should be constructed on the main thread
+ EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize);
+
+ // These public methods are all meant to be called from the socket thread
+ void ClearCredits();
+ uint32_t BurstEventsAvailable();
+ uint32_t QueuedEvents();
+
+ // a paused token bucket will not process any events, but it will accumulate
+ // credits. ClearCredits can be used before unpausing if desired.
+ void Pause();
+ void UnPause();
+ void Stop();
+
+ // The returned cancelable event can only be canceled from the socket thread
+ nsresult SubmitEvent(ATokenBucketEvent* event, nsICancelable** cancelable);
+
+ private:
+ virtual ~EventTokenBucket();
+ void CleanupTimers();
+
+ friend class RunNotifyEvent;
+ friend class SetTimerEvent;
+
+ bool TryImmediateDispatch(TokenBucketCancelable* cancelable);
+ void SetRate(uint32_t eventsPerSecond, uint32_t burstSize);
+
+ void DispatchEvents();
+ void UpdateTimer();
+ void UpdateCredits();
+
+ const static uint64_t kUsecPerSec = 1000000;
+ const static uint64_t kUsecPerMsec = 1000;
+ const static uint64_t kMaxHz = 10000;
+
+ uint64_t
+ mUnitCost; // usec of credit needed for 1 event (from eventsPerSecond)
+ uint64_t mMaxCredit; // usec mCredit limit (from busrtSize)
+ uint64_t mCredit; // usec of accumulated credit.
+
+ bool mPaused;
+ bool mStopped;
+ nsRefPtrDeque<TokenBucketCancelable> mEvents;
+ bool mTimerArmed;
+ TimeStamp mLastUpdate;
+
+ // The timer is created on the main thread, but is armed and executes Notify()
+ // callbacks on the socket thread in order to maintain low latency of event
+ // delivery.
+ nsCOMPtr<nsITimer> mTimer;
+
+#ifdef XP_WIN
+ // Windows timers are 15ms granularity by default. When we have active events
+ // that need to be dispatched at 50ms or less granularity we change the OS
+ // granularity to 1ms. 90 seconds after that need has elapsed we will change
+ // it back
+ const static uint64_t kCostFineGrainThreshold = 50 * kUsecPerMsec;
+
+ void FineGrainTimers(); // get 1ms granularity
+ void NormalTimers(); // reset to default granularity
+ void WantNormalTimers(); // reset after 90 seconds if not needed in interim
+ void FineGrainResetTimerNotify(); // delayed callback to reset
+
+ TimeStamp mLastFineGrainTimerUse;
+ bool mFineGrainTimerInUse;
+ bool mFineGrainResetTimerArmed;
+ nsCOMPtr<nsITimer> mFineGrainResetTimer;
+#endif
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/FuzzyLayer.cpp b/netwerk/base/FuzzyLayer.cpp
new file mode 100644
index 0000000000..8a9ee694dd
--- /dev/null
+++ b/netwerk/base/FuzzyLayer.cpp
@@ -0,0 +1,407 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FuzzyLayer.h"
+#include "nsTHashMap.h"
+#include "nsDeque.h"
+#include "nsIRunnable.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+
+#include "prmem.h"
+#include "prio.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gFuzzingLog("nsFuzzingNecko");
+
+#define FUZZING_LOG(args) \
+ MOZ_LOG(mozilla::net::gFuzzingLog, mozilla::LogLevel::Verbose, args)
+
+// Mutex for modifying our hash tables
+Mutex gConnRecvMutex("ConnectRecvMutex");
+
+// This structure will be created by addNetworkFuzzingBuffer below
+// and then added to the gNetworkFuzzingBuffers structure.
+//
+// It is assigned on connect and associated with the socket it belongs to.
+typedef struct {
+ const uint8_t* buf;
+ size_t size;
+ bool allowRead;
+ bool allowUnused;
+ PRNetAddr* addr;
+} NetworkFuzzingBuffer;
+
+// This holds all connections we have currently open.
+static nsTHashMap<nsPtrHashKey<PRFileDesc>, NetworkFuzzingBuffer*>
+ gConnectedNetworkFuzzingBuffers;
+
+// This holds all buffers for connections we can still open.
+static nsDeque<NetworkFuzzingBuffer> gNetworkFuzzingBuffers;
+
+// This is `true` once all connections are closed and either there are
+// no buffers left to be used or all remaining buffers are marked optional.
+// Used by `signalNetworkFuzzingDone` to tell the main thread if it needs
+// to spin-wait for `gFuzzingConnClosed`.
+static Atomic<bool> fuzzingNoWaitRequired(false);
+
+// Used to memorize if the main thread has indicated that it is done with
+// its iteration and we don't expect more connections now.
+static Atomic<bool> fuzzingMainSignaledDone(false);
+
+/*
+ * The flag `gFuzzingConnClosed` is set by `FuzzyClose` when all connections
+ * are closed *and* there are no more buffers in `gNetworkFuzzingBuffers` that
+ * must be used. The main thread spins until this becomes true to synchronize
+ * the fuzzing iteration between the main thread and the socket thread, if
+ * the prior call to `signalNetworkFuzzingDone` returned `false`.
+ */
+Atomic<bool> gFuzzingConnClosed(true);
+
+void addNetworkFuzzingBuffer(const uint8_t* data, size_t size, bool readFirst,
+ bool useIsOptional) {
+ if (size > INT32_MAX) {
+ MOZ_CRASH("Unsupported buffer size");
+ }
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* buf = new NetworkFuzzingBuffer();
+ buf->buf = data;
+ buf->size = size;
+ buf->allowRead = readFirst;
+ buf->allowUnused = useIsOptional;
+ buf->addr = nullptr;
+
+ gNetworkFuzzingBuffers.Push(buf);
+
+ fuzzingMainSignaledDone = false;
+ fuzzingNoWaitRequired = false;
+}
+
+/*
+ * This method should be called by fuzzing from the main thread to signal to
+ * the layer code that a fuzzing iteration is done. As a result, we can throw
+ * away any optional buffers and signal back once all connections have been
+ * closed. The main thread should synchronize on all connections being closed
+ * after the actual request/action is complete.
+ */
+bool signalNetworkFuzzingDone() {
+ FUZZING_LOG(("[signalNetworkFuzzingDone] Called."));
+ MutexAutoLock lock(gConnRecvMutex);
+ bool rv = false;
+
+ if (fuzzingNoWaitRequired) {
+ FUZZING_LOG(("[signalNetworkFuzzingDone] Purging remaining buffers."));
+ // Easy case, we already have no connections and non-optional buffers left.
+ gNetworkFuzzingBuffers.Erase();
+ gFuzzingConnClosed = true;
+ rv = true;
+ } else {
+ // We still have either connections left open or non-optional buffers left.
+ // In this case, FuzzyClose will handle the tear-down and signaling.
+ fuzzingMainSignaledDone = true;
+ }
+
+ return rv;
+}
+
+static PRDescIdentity sFuzzyLayerIdentity;
+static PRIOMethods sFuzzyLayerMethods;
+static PRIOMethods* sFuzzyLayerMethodsPtr = nullptr;
+
+static PRInt16 PR_CALLBACK FuzzyPoll(PRFileDesc* fd, PRInt16 in_flags,
+ PRInt16* out_flags) {
+ *out_flags = 0;
+
+ FUZZING_LOG(("[FuzzyPoll] Called with in_flags=%X.", in_flags));
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+
+ if (in_flags & PR_POLL_READ && fuzzBuf && fuzzBuf->allowRead) {
+ *out_flags = PR_POLL_READ;
+ return PR_POLL_READ;
+ }
+
+ if (in_flags & PR_POLL_WRITE) {
+ *out_flags = PR_POLL_WRITE;
+ return PR_POLL_WRITE;
+ }
+
+ return in_flags;
+}
+
+static PRStatus FuzzyConnect(PRFileDesc* fd, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.PopFront();
+ if (!buf) {
+ FUZZING_LOG(("[FuzzyConnect] Denying additional connection."));
+ return PR_FAILURE;
+ }
+
+ gConnectedNetworkFuzzingBuffers.InsertOrUpdate(fd, buf);
+ fuzzingNoWaitRequired = false;
+
+ FUZZING_LOG(("[FuzzyConnect] Successfully opened connection: %p", fd));
+
+ gFuzzingConnClosed = false;
+
+ return PR_SUCCESS;
+}
+
+static PRInt32 FuzzySendTo(PRFileDesc* fd, const void* buf, PRInt32 amount,
+ PRIntn flags, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.PopFront();
+ if (!buf) {
+ FUZZING_LOG(("[FuzzySentTo] Denying additional connection."));
+ return 0;
+ }
+
+ gConnectedNetworkFuzzingBuffers.InsertOrUpdate(fd, buf);
+
+ // Store connection address
+ buf->addr = new PRNetAddr;
+ memcpy(buf->addr, addr, sizeof(PRNetAddr));
+
+ fuzzingNoWaitRequired = false;
+
+ FUZZING_LOG(("[FuzzySendTo] Successfully opened connection: %p", fd));
+
+ gFuzzingConnClosed = false;
+ }
+
+ // Allow reading once the implementation has written at least some data
+ if (fuzzBuf && !fuzzBuf->allowRead) {
+ FUZZING_LOG(("[FuzzySendTo] Write received, allowing further reads."));
+ fuzzBuf->allowRead = true;
+ }
+
+ FUZZING_LOG(("[FuzzySendTo] Sent %" PRIx32 " bytes of data.", amount));
+
+ return amount;
+}
+
+static PRInt32 FuzzySend(PRFileDesc* fd, const void* buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ FUZZING_LOG(("[FuzzySend] Write on socket that is not connected."));
+ amount = 0;
+ }
+
+ // Allow reading once the implementation has written at least some data
+ if (fuzzBuf && !fuzzBuf->allowRead) {
+ FUZZING_LOG(("[FuzzySend] Write received, allowing further reads."));
+ fuzzBuf->allowRead = true;
+ }
+
+ FUZZING_LOG(("[FuzzySend] Sent %" PRIx32 " bytes of data.", amount));
+
+ return amount;
+}
+
+static PRInt32 FuzzyWrite(PRFileDesc* fd, const void* buf, PRInt32 amount) {
+ return FuzzySend(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRInt32 FuzzyRecv(PRFileDesc* fd, void* buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ FUZZING_LOG(("[FuzzyRecv] Denying read, connection is closed."));
+ return 0;
+ }
+
+ // As long as we haven't written anything, act as if no data was there yet
+ if (!fuzzBuf->allowRead) {
+ FUZZING_LOG(("[FuzzyRecv] Denying read, nothing written before."));
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return -1;
+ }
+
+ if (gFuzzingConnClosed) {
+ FUZZING_LOG(("[FuzzyRecv] Denying read, connection is closed."));
+ return 0;
+ }
+
+ // No data left, act as if the connection was closed.
+ if (!fuzzBuf->size) {
+ FUZZING_LOG(("[FuzzyRecv] Read failed, no data left."));
+ return 0;
+ }
+
+ // Use the remains of fuzzing buffer, if too little is left
+ if (fuzzBuf->size < (PRUint32)amount) amount = fuzzBuf->size;
+
+ // Consume buffer, copy data
+ memcpy(buf, fuzzBuf->buf, amount);
+
+ if (!(flags & PR_MSG_PEEK)) {
+ fuzzBuf->buf += amount;
+ fuzzBuf->size -= amount;
+ }
+
+ FUZZING_LOG(("[FuzzyRecv] Read %" PRIx32 " bytes of data.", amount));
+
+ return amount;
+}
+
+static PRInt32 FuzzyRecvFrom(PRFileDesc* fd, void* buf, PRInt32 amount,
+ PRIntn flags, PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ // Return the same address used for initial SendTo on this fd
+ if (addr) {
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ FUZZING_LOG(("[FuzzyRecvFrom] Denying read, connection is closed."));
+ return 0;
+ }
+
+ if (fuzzBuf->addr) {
+ memcpy(addr, fuzzBuf->addr, sizeof(PRNetAddr));
+ } else {
+ FUZZING_LOG(("[FuzzyRecvFrom] No address found for connection"));
+ }
+ }
+ return FuzzyRecv(fd, buf, amount, flags, timeout);
+}
+
+static PRInt32 FuzzyRead(PRFileDesc* fd, void* buf, PRInt32 amount) {
+ return FuzzyRecv(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRStatus FuzzyClose(PRFileDesc* fd) {
+ if (!fd) {
+ return PR_FAILURE;
+ }
+ PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
+ MOZ_RELEASE_ASSERT(layer && layer->identity == sFuzzyLayerIdentity,
+ "Fuzzy Layer not on top of stack");
+
+ layer->dtor(layer);
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = nullptr;
+ if (gConnectedNetworkFuzzingBuffers.Remove(fd, &fuzzBuf)) {
+ FUZZING_LOG(("[FuzzyClose] Received close on socket %p", fd));
+ if (fuzzBuf->addr) {
+ delete fuzzBuf->addr;
+ }
+ delete fuzzBuf;
+ } else {
+ /* Happens when close is called on a non-connected socket */
+ FUZZING_LOG(("[FuzzyClose] Received close on unknown socket %p.", fd));
+ }
+
+ PRStatus ret = fd->methods->close(fd);
+
+ if (!gConnectedNetworkFuzzingBuffers.Count()) {
+ // At this point, all connections are closed, but we might still have
+ // unused network buffers that were not marked as optional.
+ bool haveRemainingUnusedBuffers = false;
+ for (size_t i = 0; i < gNetworkFuzzingBuffers.GetSize(); ++i) {
+ NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.ObjectAt(i);
+
+ if (!buf->allowUnused) {
+ haveRemainingUnusedBuffers = true;
+ break;
+ }
+ }
+
+ if (haveRemainingUnusedBuffers) {
+ FUZZING_LOG(
+ ("[FuzzyClose] All connections closed, waiting for remaining "
+ "connections."));
+ } else if (!fuzzingMainSignaledDone) {
+ // We have no connections left, but the main thread hasn't signaled us
+ // yet. For now, that means once the main thread signals us, we can tell
+ // it immediately that it won't have to wait for closing connections.
+ FUZZING_LOG(
+ ("[FuzzyClose] All connections closed, waiting for main thread."));
+ fuzzingNoWaitRequired = true;
+ } else {
+ // No connections left and main thread is already done. Perform cleanup
+ // and then signal the main thread to continue.
+ FUZZING_LOG(("[FuzzyClose] All connections closed, cleaning up."));
+
+ gNetworkFuzzingBuffers.Erase();
+ gFuzzingConnClosed = true;
+
+ // We need to dispatch this so the main thread is guaranteed to wake up
+ nsCOMPtr<nsIRunnable> r(new mozilla::Runnable("Dummy"));
+ NS_DispatchToMainThread(r.forget());
+ }
+ } else {
+ FUZZING_LOG(("[FuzzyClose] Connection closed."));
+ }
+
+ return ret;
+}
+
+nsresult AttachFuzzyIOLayer(PRFileDesc* fd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!sFuzzyLayerMethodsPtr) {
+ sFuzzyLayerIdentity = PR_GetUniqueIdentity("Fuzzy Layer");
+ sFuzzyLayerMethods = *PR_GetDefaultIOMethods();
+ sFuzzyLayerMethods.connect = FuzzyConnect;
+ sFuzzyLayerMethods.send = FuzzySend;
+ sFuzzyLayerMethods.sendto = FuzzySendTo;
+ sFuzzyLayerMethods.write = FuzzyWrite;
+ sFuzzyLayerMethods.recv = FuzzyRecv;
+ sFuzzyLayerMethods.recvfrom = FuzzyRecvFrom;
+ sFuzzyLayerMethods.read = FuzzyRead;
+ sFuzzyLayerMethods.close = FuzzyClose;
+ sFuzzyLayerMethods.poll = FuzzyPoll;
+ sFuzzyLayerMethodsPtr = &sFuzzyLayerMethods;
+ }
+
+ PRFileDesc* layer =
+ PR_CreateIOLayerStub(sFuzzyLayerIdentity, sFuzzyLayerMethodsPtr);
+
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRStatus status = PR_PushIOLayer(fd, PR_TOP_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/FuzzyLayer.h b/netwerk/base/FuzzyLayer.h
new file mode 100644
index 0000000000..6b8109f949
--- /dev/null
+++ b/netwerk/base/FuzzyLayer.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FuzzyLayer_h__
+#define FuzzyLayer_h__
+
+#include "prerror.h"
+#include "nsError.h"
+#include "nsIFile.h"
+
+namespace mozilla {
+namespace net {
+
+nsresult AttachFuzzyIOLayer(PRFileDesc* fd);
+
+extern Atomic<bool> gFuzzingConnClosed;
+bool signalNetworkFuzzingDone();
+
+void addNetworkFuzzingBuffer(const uint8_t* data, size_t size,
+ bool readFirst = false,
+ bool useIsOptional = false);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // FuzzyLayer_h__
diff --git a/netwerk/base/FuzzySecurityInfo.cpp b/netwerk/base/FuzzySecurityInfo.cpp
new file mode 100644
index 0000000000..7a2405ab0a
--- /dev/null
+++ b/netwerk/base/FuzzySecurityInfo.cpp
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FuzzySecurityInfo.h"
+
+#include "nsIWebProgressListener.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+FuzzySecurityInfo::FuzzySecurityInfo() {}
+
+FuzzySecurityInfo::~FuzzySecurityInfo() {}
+
+NS_IMPL_ISUPPORTS(FuzzySecurityInfo, nsITransportSecurityInfo)
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSecurityState(uint32_t* state) {
+ *state = nsIWebProgressListener::STATE_IS_SECURE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetErrorCode(int32_t* state) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetErrorCodeString(nsAString& aErrorString) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetFailedCertChain(
+ nsTArray<RefPtr<nsIX509Cert>>& aFailedCertChain) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetServerCert(nsIX509Cert** aServerCert) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSucceededCertChain(
+ nsTArray<RefPtr<nsIX509Cert>>& aSucceededCertChain) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetCipherName(nsACString& aCipherName) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetKeyLength(uint32_t* aKeyLength) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSecretKeyLength(uint32_t* aSecretKeyLength) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetKeaGroupName(nsACString& aKeaGroup) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSignatureSchemeName(nsACString& aSignatureScheme) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetProtocolVersion(uint16_t* aProtocolVersion) {
+ NS_ENSURE_ARG_POINTER(aProtocolVersion);
+ // Must be >= TLS 1.2 for HTTP2
+ *aProtocolVersion = nsITransportSecurityInfo::TLS_VERSION_1_2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetCertificateTransparencyStatus(
+ uint16_t* aCertificateTransparencyStatus) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsDelegatedCredential(bool* aIsDelegCred) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsAcceptedEch(bool* aIsAcceptedEch) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetOverridableErrorCategory(
+ OverridableErrorCategory* aOverridableErrorCode) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetMadeOCSPRequests(bool* aMadeOCSPRequests) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetUsedPrivateDNS(bool* aUsedPrivateDNS) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsExtendedValidation(bool* aIsEV) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::ToString(nsACString& aResult) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+void FuzzySecurityInfo::SerializeToIPC(IPC::MessageWriter* aWriter) {
+ MOZ_CRASH("Unused");
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetNegotiatedNPN(nsACString& aNegotiatedNPN) {
+ aNegotiatedNPN.Assign("h2");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetResumed(bool* aResumed) {
+ *aResumed = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP FuzzySecurityInfo::GetIsBuiltCertChainRootBuiltInRoot(
+ bool* aIsBuiltInRoot) {
+ *aIsBuiltInRoot = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetPeerId(nsACString& aResult) {
+ aResult.Assign(""_ns);
+ return NS_OK;
+}
+
+} // namespace net
+
+} // namespace mozilla
diff --git a/netwerk/base/FuzzySecurityInfo.h b/netwerk/base/FuzzySecurityInfo.h
new file mode 100644
index 0000000000..121c6c8ba9
--- /dev/null
+++ b/netwerk/base/FuzzySecurityInfo.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FuzzySecurityInfo_h__
+#define FuzzySecurityInfo_h__
+
+#include "nsCOMPtr.h"
+#include "nsITransportSecurityInfo.h"
+
+namespace mozilla {
+namespace net {
+
+class FuzzySecurityInfo final : public nsITransportSecurityInfo {
+ public:
+ FuzzySecurityInfo();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTSECURITYINFO
+
+ protected:
+ virtual ~FuzzySecurityInfo();
+}; // class FuzzySecurityInfo
+
+} // namespace net
+} // namespace mozilla
+
+#endif // FuzzySecurityInfo_h__
diff --git a/netwerk/base/FuzzySocketControl.cpp b/netwerk/base/FuzzySocketControl.cpp
new file mode 100644
index 0000000000..ff53358417
--- /dev/null
+++ b/netwerk/base/FuzzySocketControl.cpp
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FuzzySocketControl.h"
+
+#include "FuzzySecurityInfo.h"
+#include "ipc/IPCMessageUtils.h"
+#include "nsITlsHandshakeListener.h"
+#include "sslt.h"
+
+namespace mozilla {
+namespace net {
+
+FuzzySocketControl::FuzzySocketControl() {}
+
+FuzzySocketControl::~FuzzySocketControl() {}
+
+NS_IMPL_ISUPPORTS(FuzzySocketControl, nsITLSSocketControl)
+
+NS_IMETHODIMP
+FuzzySocketControl::GetProviderFlags(uint32_t* aProviderFlags) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetKEAUsed(int16_t* aKea) {
+ // Can be ssl_kea_dh or ssl_kea_ecdh for HTTP2
+ *aKea = ssl_kea_ecdh;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetKEAKeyBits(uint32_t* aKeyBits) {
+ // Must be >= 224 for ecdh and >= 2048 for dh when using HTTP2
+ *aKeyBits = 256;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetSSLVersionUsed(int16_t* aSSLVersionUsed) {
+ // Must be >= TLS 1.2 for HTTP2
+ *aSSLVersionUsed = nsITLSSocketControl::TLS_VERSION_1_2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetSSLVersionOffered(int16_t* aSSLVersionOffered) {
+ *aSSLVersionOffered = nsITLSSocketControl::TLS_VERSION_1_2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetMACAlgorithmUsed(int16_t* aMac) {
+ // The only valid choice for HTTP2 is SSL_MAC_AEAD
+ *aMac = nsITLSSocketControl::SSL_MAC_AEAD;
+ return NS_OK;
+}
+
+bool FuzzySocketControl::GetDenyClientCert() { return false; }
+
+void FuzzySocketControl::SetDenyClientCert(bool aDenyClientCert) {
+ // Called by mozilla::net::nsHttpConnection::StartSpdy
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetClientCertSent(bool* arg) {
+ *arg = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetFailedVerification(bool* arg) {
+ *arg = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetAlpnEarlySelection(nsACString& aAlpnSelected) {
+ // TODO: For now we don't support early selection
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetEarlyDataAccepted(bool* aAccepted) {
+ *aAccepted = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::DriveHandshake() { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::IsAcceptableForHost(const nsACString& hostname,
+ bool* _retval) {
+ NS_ENSURE_ARG(_retval);
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::TestJoinConnection(const nsACString& npnProtocol,
+ const nsACString& hostname, int32_t port,
+ bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::JoinConnection(const nsACString& npnProtocol,
+ const nsACString& hostname, int32_t port,
+ bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::ProxyStartSSL() { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::StartTLS() { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::SetNPNList(nsTArray<nsCString>& protocolArray) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetEsniTxt(nsACString& aEsniTxt) { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::SetEsniTxt(const nsACString& aEsniTxt) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetEchConfig(nsACString& aEchConfig) { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::SetEchConfig(const nsACString& aEchConfig) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::GetRetryEchConfig(nsACString& aEchConfig) { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySocketControl::GetPeerId(nsACString& aResult) {
+ aResult.Assign(""_ns);
+ return NS_OK;
+}
+
+NS_IMETHODIMP FuzzySocketControl::SetHandshakeCallbackListener(
+ nsITlsHandshakeCallbackListener* callback) {
+ if (callback) {
+ callback->HandshakeDone();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::DisableEarlyData(void) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP FuzzySocketControl::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(new FuzzySecurityInfo());
+ securityInfo.forget(aSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySocketControl::AsyncGetSecurityInfo(JSContext* aCx,
+ mozilla::dom::Promise** aPromise) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP FuzzySocketControl::Claim() { return NS_OK; }
+
+NS_IMETHODIMP FuzzySocketControl::SetBrowserId(uint64_t) { return NS_OK; }
+
+NS_IMETHODIMP FuzzySocketControl::GetBrowserId(uint64_t*) {
+ MOZ_CRASH("Unused");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/FuzzySocketControl.h b/netwerk/base/FuzzySocketControl.h
new file mode 100644
index 0000000000..9debbe32e8
--- /dev/null
+++ b/netwerk/base/FuzzySocketControl.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FuzzySocketControl_h__
+#define FuzzySocketControl_h__
+
+#include "nsITLSSocketControl.h"
+
+namespace mozilla {
+namespace net {
+
+class FuzzySocketControl final : public nsITLSSocketControl {
+ public:
+ FuzzySocketControl();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSOCKETCONTROL
+
+ protected:
+ virtual ~FuzzySocketControl();
+}; // class FuzzySocketControl
+
+} // namespace net
+} // namespace mozilla
+
+#endif // FuzzySocketControl_h__
diff --git a/netwerk/base/IOActivityMonitor.cpp b/netwerk/base/IOActivityMonitor.cpp
new file mode 100644
index 0000000000..76047a7a00
--- /dev/null
+++ b/netwerk/base/IOActivityMonitor.cpp
@@ -0,0 +1,495 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "IOActivityMonitor.h"
+#include "nsPrintfCString.h"
+#include "nsSocketTransport2.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/Promise.h"
+#include "prerror.h"
+#include "prio.h"
+#include "prmem.h"
+#include <vector>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+mozilla::StaticRefPtr<IOActivityMonitor> gInstance;
+static Atomic<bool> gActivated(false);
+static PRDescIdentity sNetActivityMonitorLayerIdentity;
+static PRIOMethods sNetActivityMonitorLayerMethods;
+static PRIOMethods* sNetActivityMonitorLayerMethodsPtr = nullptr;
+
+// Maximum number of activities entries in the monitoring class
+#define MAX_ACTIVITY_ENTRIES 1000
+
+// ActivityMonitorSecret is stored in the activity monitor layer
+// and provides a method to get the location.
+//
+// A location can be :
+// - a TCP or UDP socket. The form will be socket://ip:port
+// - a File. The form will be file://path
+//
+// For other cases, the location will be fd://number
+class ActivityMonitorSecret final {
+ public:
+ // constructor used for sockets
+ explicit ActivityMonitorSecret(PRFileDesc* aFd) {
+ mFd = aFd;
+ mLocationSet = false;
+ }
+
+ // constructor used for files
+ explicit ActivityMonitorSecret(PRFileDesc* aFd, const char* aLocation) {
+ mFd = aFd;
+ mLocation.AppendPrintf("file://%s", aLocation);
+ mLocationSet = true;
+ }
+
+ nsCString getLocation() {
+ if (!mLocationSet) {
+ LazySetLocation();
+ }
+ return mLocation;
+ }
+
+ private:
+ // Called to set the location using the FD on the first getLocation() usage
+ // which is typically when a socket is opened. If done earlier, at
+ // construction time, the host won't be bound yet.
+ //
+ // If the location is a file, it needs to be initialized in the
+ // constructor.
+ void LazySetLocation() {
+ mLocationSet = true;
+ PRFileDesc* extract = mFd;
+ while (PR_GetDescType(extract) == PR_DESC_LAYERED) {
+ if (!extract->lower) {
+ break;
+ }
+ extract = extract->lower;
+ }
+
+ PRDescType fdType = PR_GetDescType(extract);
+ // we should not use LazySetLocation for files
+ MOZ_ASSERT(fdType != PR_DESC_FILE);
+
+ switch (fdType) {
+ case PR_DESC_SOCKET_TCP:
+ case PR_DESC_SOCKET_UDP: {
+ mLocation.AppendPrintf("socket://");
+ PRNetAddr addr;
+ PRStatus status = PR_GetSockName(mFd, &addr);
+ if (NS_WARN_IF(status == PR_FAILURE)) {
+ mLocation.AppendPrintf("unknown");
+ break;
+ }
+
+ // grabbing the host
+ char netAddr[mozilla::net::kNetAddrMaxCStrBufSize] = {0};
+ status = PR_NetAddrToString(&addr, netAddr, sizeof(netAddr) - 1);
+ if (NS_WARN_IF(status == PR_FAILURE) || netAddr[0] == 0) {
+ mLocation.AppendPrintf("unknown");
+ break;
+ }
+ mLocation.Append(netAddr);
+
+ // adding the port
+ uint16_t port;
+ if (addr.raw.family == PR_AF_INET) {
+ port = addr.inet.port;
+ } else {
+ port = addr.ipv6.port;
+ }
+ mLocation.AppendPrintf(":%d", port);
+ } break;
+
+ // for all other cases, we just send back fd://<value>
+ default: {
+ mLocation.AppendLiteral("fd://");
+ mLocation.AppendInt(PR_FileDesc2NativeHandle(mFd));
+ }
+ } // end switch
+ }
+
+ private:
+ nsCString mLocation;
+ bool mLocationSet;
+ PRFileDesc* mFd;
+};
+
+// FileDesc2Location converts a PRFileDesc into a "location" by
+// grabbing the ActivityMonitorSecret in layer->secret
+static nsAutoCString FileDesc2Location(PRFileDesc* fd) {
+ nsAutoCString location;
+ PRFileDesc* monitorLayer =
+ PR_GetIdentitiesLayer(fd, sNetActivityMonitorLayerIdentity);
+ if (!monitorLayer) {
+ location.AppendPrintf("unknown");
+ return location;
+ }
+
+ ActivityMonitorSecret* secret = (ActivityMonitorSecret*)monitorLayer->secret;
+ location.AppendPrintf("%s", secret->getLocation().get());
+ return location;
+}
+
+//
+// Wrappers around the socket APIS
+//
+static PRStatus nsNetMon_Connect(PRFileDesc* fd, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ return fd->lower->methods->connect(fd->lower, addr, timeout);
+}
+
+static PRStatus nsNetMon_Close(PRFileDesc* fd) {
+ if (!fd) {
+ return PR_FAILURE;
+ }
+ PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
+ MOZ_RELEASE_ASSERT(
+ layer && layer->identity == sNetActivityMonitorLayerIdentity,
+ "NetActivityMonitor Layer not on top of stack");
+
+ if (layer->secret) {
+ delete (ActivityMonitorSecret*)layer->secret;
+ layer->secret = nullptr;
+ }
+ layer->dtor(layer);
+ return fd->methods->close(fd);
+}
+
+static int32_t nsNetMon_Read(PRFileDesc* fd, void* buf, int32_t len) {
+ int32_t ret = fd->lower->methods->read(fd->lower, buf, len);
+ if (ret >= 0) {
+ IOActivityMonitor::Read(fd, len);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_Write(PRFileDesc* fd, const void* buf, int32_t len) {
+ int32_t ret = fd->lower->methods->write(fd->lower, buf, len);
+ if (ret > 0) {
+ IOActivityMonitor::Write(fd, len);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_Writev(PRFileDesc* fd, const PRIOVec* iov, int32_t size,
+ PRIntervalTime timeout) {
+ int32_t ret = fd->lower->methods->writev(fd->lower, iov, size, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Write(fd, size);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_Recv(PRFileDesc* fd, void* buf, int32_t amount,
+ int flags, PRIntervalTime timeout) {
+ int32_t ret =
+ fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Read(fd, amount);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_Send(PRFileDesc* fd, const void* buf, int32_t amount,
+ int flags, PRIntervalTime timeout) {
+ int32_t ret =
+ fd->lower->methods->send(fd->lower, buf, amount, flags, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Write(fd, amount);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_RecvFrom(PRFileDesc* fd, void* buf, int32_t amount,
+ int flags, PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ int32_t ret = fd->lower->methods->recvfrom(fd->lower, buf, amount, flags,
+ addr, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Read(fd, amount);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_SendTo(PRFileDesc* fd, const void* buf, int32_t amount,
+ int flags, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ int32_t ret =
+ fd->lower->methods->sendto(fd->lower, buf, amount, flags, addr, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Write(fd, amount);
+ }
+ return ret;
+}
+
+static int32_t nsNetMon_AcceptRead(PRFileDesc* listenSock,
+ PRFileDesc** acceptedSock,
+ PRNetAddr** peerAddr, void* buf,
+ int32_t amount, PRIntervalTime timeout) {
+ int32_t ret = listenSock->lower->methods->acceptread(
+ listenSock->lower, acceptedSock, peerAddr, buf, amount, timeout);
+ if (ret > 0) {
+ IOActivityMonitor::Read(listenSock, amount);
+ }
+ return ret;
+}
+
+//
+// Class IOActivityMonitor
+//
+NS_IMPL_ISUPPORTS(IOActivityMonitor, nsINamed)
+
+IOActivityMonitor::IOActivityMonitor() : mLock("IOActivityMonitor::mLock") {
+ RefPtr<IOActivityMonitor> mon(gInstance);
+ MOZ_ASSERT(!mon, "multiple IOActivityMonitor instances!");
+}
+
+// static
+void IOActivityMonitor::RequestActivities(dom::Promise* aPromise) {
+ MOZ_ASSERT(aPromise);
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ aPromise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mon->RequestActivitiesInternal(aPromise);
+}
+
+void IOActivityMonitor::RequestActivitiesInternal(dom::Promise* aPromise) {
+ nsresult result = NS_OK;
+ FallibleTArray<dom::IOActivityDataDictionary> activities;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ // Remove inactive activities
+ for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) {
+ dom::IOActivityDataDictionary* activity = &iter.Data();
+ if (activity->mRx == 0 && activity->mTx == 0) {
+ iter.Remove();
+ } else {
+ if (NS_WARN_IF(!activities.AppendElement(iter.Data(), fallible))) {
+ result = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+ }
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(result))) {
+ aPromise->MaybeReject(result);
+ return;
+ }
+ aPromise->MaybeResolve(activities);
+}
+
+NS_IMETHODIMP
+IOActivityMonitor::GetName(nsACString& aName) {
+ aName.AssignLiteral("IOActivityMonitor");
+ return NS_OK;
+}
+
+// static
+bool IOActivityMonitor::IsActive() { return gActivated; }
+
+// static
+already_AddRefed<IOActivityMonitor> IOActivityMonitor::Get() {
+ if (!gActivated) {
+ return nullptr;
+ }
+
+ RefPtr<IOActivityMonitor> mon = gInstance;
+ return mon.forget();
+}
+
+nsresult IOActivityMonitor::Init() {
+ if (IsActive()) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+ RefPtr<IOActivityMonitor> mon = new IOActivityMonitor();
+ nsresult rv = mon->InitInternal();
+ if (NS_SUCCEEDED(rv)) {
+ gInstance = mon;
+ ClearOnShutdown(&gInstance);
+ gActivated = true;
+ }
+ return rv;
+}
+
+nsresult IOActivityMonitor::InitInternal() {
+ // wraps the socket APIs
+ if (!sNetActivityMonitorLayerMethodsPtr) {
+ sNetActivityMonitorLayerIdentity =
+ PR_GetUniqueIdentity("network activity monitor layer");
+ sNetActivityMonitorLayerMethods = *PR_GetDefaultIOMethods();
+ sNetActivityMonitorLayerMethods.connect = nsNetMon_Connect;
+ sNetActivityMonitorLayerMethods.read = nsNetMon_Read;
+ sNetActivityMonitorLayerMethods.write = nsNetMon_Write;
+ sNetActivityMonitorLayerMethods.writev = nsNetMon_Writev;
+ sNetActivityMonitorLayerMethods.recv = nsNetMon_Recv;
+ sNetActivityMonitorLayerMethods.send = nsNetMon_Send;
+ sNetActivityMonitorLayerMethods.recvfrom = nsNetMon_RecvFrom;
+ sNetActivityMonitorLayerMethods.sendto = nsNetMon_SendTo;
+ sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead;
+ sNetActivityMonitorLayerMethods.close = nsNetMon_Close;
+ sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods;
+ }
+
+ return NS_OK;
+}
+
+nsresult IOActivityMonitor::Shutdown() {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return mon->ShutdownInternal();
+}
+
+nsresult IOActivityMonitor::ShutdownInternal() {
+ mozilla::MutexAutoLock lock(mLock);
+ mActivities.Clear();
+ gActivated = false;
+ return NS_OK;
+}
+
+nsresult IOActivityMonitor::MonitorSocket(PRFileDesc* aFd) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_OK;
+ }
+ PRFileDesc* layer;
+ PRStatus status;
+ layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity,
+ sNetActivityMonitorLayerMethodsPtr);
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd);
+ layer->secret = reinterpret_cast<PRFilePrivate*>(secret);
+ status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ delete secret;
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult IOActivityMonitor::MonitorFile(PRFileDesc* aFd, const char* aPath) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_OK;
+ }
+ PRFileDesc* layer;
+ PRStatus status;
+ layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity,
+ sNetActivityMonitorLayerMethodsPtr);
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd, aPath);
+ layer->secret = reinterpret_cast<PRFilePrivate*>(secret);
+
+ status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
+ if (status == PR_FAILURE) {
+ delete secret;
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool IOActivityMonitor::IncrementActivity(const nsACString& aLocation,
+ uint32_t aRx, uint32_t aTx) {
+ mLock.AssertCurrentThreadOwns();
+ return mActivities.WithEntryHandle(aLocation, fallible, [&](auto&& entry) {
+ if (!entry) return false;
+
+ if (*entry) {
+ // already registered
+ entry->Data().mTx += aTx;
+ entry->Data().mRx += aRx;
+ } else {
+ // Creating a new IOActivity. Notice that mActivities
+ // will grow indefinitely, which is OK since we won't
+ // have but a few hundreds entries at the most, but we
+ // want to assert we have at the most 1000 entries
+ MOZ_ASSERT(mActivities.Count() <= MAX_ACTIVITY_ENTRIES);
+
+ dom::IOActivityDataDictionary activity;
+ activity.mLocation.Assign(aLocation);
+ activity.mTx = aTx;
+ activity.mRx = aRx;
+
+ entry->Insert(std::move(activity));
+ }
+
+ return true;
+ });
+}
+
+nsresult IOActivityMonitor::Write(const nsACString& aLocation,
+ uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_ERROR_FAILURE;
+ }
+ return mon->WriteInternal(aLocation, aAmount);
+}
+
+nsresult IOActivityMonitor::Write(PRFileDesc* fd, uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_ERROR_FAILURE;
+ }
+ return mon->Write(FileDesc2Location(fd), aAmount);
+}
+
+nsresult IOActivityMonitor::WriteInternal(const nsACString& aLocation,
+ uint32_t aAmount) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (!IncrementActivity(aLocation, aAmount, 0)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult IOActivityMonitor::Read(PRFileDesc* fd, uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_ERROR_FAILURE;
+ }
+ return mon->Read(FileDesc2Location(fd), aAmount);
+}
+
+nsresult IOActivityMonitor::Read(const nsACString& aLocation,
+ uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon = IOActivityMonitor::Get();
+ if (!mon) {
+ return NS_ERROR_FAILURE;
+ }
+ return mon->ReadInternal(aLocation, aAmount);
+}
+
+nsresult IOActivityMonitor::ReadInternal(const nsACString& aLocation,
+ uint32_t aAmount) {
+ mozilla::MutexAutoLock lock(mLock);
+ if (!IncrementActivity(aLocation, 0, aAmount)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
diff --git a/netwerk/base/IOActivityMonitor.h b/netwerk/base/IOActivityMonitor.h
new file mode 100644
index 0000000000..5e46bc9bfc
--- /dev/null
+++ b/netwerk/base/IOActivityMonitor.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef IOActivityMonitor_h___
+#define IOActivityMonitor_h___
+
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsClassHashtable.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsINamed.h"
+#include "nsISupports.h"
+#include "prinrval.h"
+#include "prio.h"
+#include "private/pprio.h"
+#include <stdint.h>
+
+namespace mozilla {
+
+namespace dom {
+class Promise;
+}
+
+namespace net {
+
+#define IO_ACTIVITY_ENABLED_PREF "io.activity.enabled"
+
+using Activities = nsTHashMap<nsCStringHashKey, dom::IOActivityDataDictionary>;
+
+// IOActivityMonitor has several roles:
+// - maintains an IOActivity per resource and updates it
+// - sends a dump of the activities to a promise via RequestActivities
+class IOActivityMonitor final : public nsINamed {
+ public:
+ IOActivityMonitor();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINAMED
+
+ // initializes and destroys the singleton
+ static nsresult Init();
+ static nsresult Shutdown();
+
+ // collect amounts of data that are written/read by location
+ static nsresult Read(const nsACString& location, uint32_t aAmount);
+ static nsresult Write(const nsACString& location, uint32_t aAmount);
+
+ static nsresult MonitorFile(PRFileDesc* aFd, const char* aPath);
+ static nsresult MonitorSocket(PRFileDesc* aFd);
+ static nsresult Read(PRFileDesc* fd, uint32_t aAmount);
+ static nsresult Write(PRFileDesc* fd, uint32_t aAmount);
+
+ static bool IsActive();
+ static void RequestActivities(dom::Promise* aPromise);
+
+ private:
+ ~IOActivityMonitor() = default;
+
+ static already_AddRefed<IOActivityMonitor> Get();
+
+ nsresult InitInternal();
+ nsresult ShutdownInternal();
+ bool IncrementActivity(const nsACString& location, uint32_t aRx,
+ uint32_t aTx);
+ nsresult WriteInternal(const nsACString& location, uint32_t aAmount);
+ nsresult ReadInternal(const nsACString& location, uint32_t aAmount);
+ void RequestActivitiesInternal(dom::Promise* aPromise);
+
+ Activities mActivities;
+ // protects mActivities accesses
+ Mutex mLock MOZ_UNANNOTATED;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* IOActivityMonitor_h___ */
diff --git a/netwerk/base/IPv6Utils.h b/netwerk/base/IPv6Utils.h
new file mode 100644
index 0000000000..437c0befec
--- /dev/null
+++ b/netwerk/base/IPv6Utils.h
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NETWORK_IPV6_UTILS_H_
+#define NETWORK_IPV6_UTILS_H_
+
+namespace mozilla {
+namespace net {
+namespace utils {
+
+// IPv6 address scopes.
+#define IPV6_SCOPE_GLOBAL 0 // Global scope.
+#define IPV6_SCOPE_LINKLOCAL 1 // Link-local scope.
+#define IPV6_SCOPE_SITELOCAL 2 // Site-local scope (deprecated).
+#define IPV6_SCOPE_UNIQUELOCAL 3 // Unique local
+#define IPV6_SCOPE_NODELOCAL 4 // Loopback
+
+// Return the scope of the given address.
+static int ipv6_scope(const unsigned char addr[16]) {
+ const unsigned char* b = addr;
+ unsigned short w = (unsigned short)((b[0] << 8) | b[1]);
+
+ if ((b[0] & 0xFE) == 0xFC) {
+ return IPV6_SCOPE_UNIQUELOCAL;
+ }
+ switch (w & 0xFFC0) {
+ case 0xFE80:
+ return IPV6_SCOPE_LINKLOCAL;
+ case 0xFEC0:
+ return IPV6_SCOPE_SITELOCAL;
+ case 0x0000:
+ w = b[1] | b[2] | b[3] | b[4] | b[5] | b[6] | b[7] | b[8] | b[9] | b[10] |
+ b[11] | b[12] | b[13] | b[14];
+ if (w || b[15] != 0x01) {
+ break;
+ }
+ return IPV6_SCOPE_NODELOCAL;
+ default:
+ break;
+ }
+
+ return IPV6_SCOPE_GLOBAL;
+}
+
+} // namespace utils
+} // namespace net
+} // namespace mozilla
+
+#endif // NETWORK_IPV6_UTILS_H_
diff --git a/netwerk/base/InterceptionInfo.cpp b/netwerk/base/InterceptionInfo.cpp
new file mode 100644
index 0000000000..977ec02e47
--- /dev/null
+++ b/netwerk/base/InterceptionInfo.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/net/InterceptionInfo.h"
+#include "nsContentUtils.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(InterceptionInfo, nsIInterceptionInfo)
+
+InterceptionInfo::InterceptionInfo(nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType,
+ const RedirectHistoryArray& aRedirectChain,
+ bool aFromThirdParty)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ mContentPolicyType(aContentPolicyType),
+ mFromThirdParty(aFromThirdParty) {
+ SetRedirectChain(aRedirectChain);
+}
+
+nsIPrincipal* InterceptionInfo::TriggeringPrincipal() {
+ return mTriggeringPrincipal;
+}
+
+void InterceptionInfo::SetTriggeringPrincipal(nsIPrincipal* aPrincipal) {
+ mTriggeringPrincipal = aPrincipal;
+}
+
+nsContentPolicyType InterceptionInfo::ContentPolicyType() {
+ return mContentPolicyType;
+}
+
+nsContentPolicyType InterceptionInfo::ExternalContentPolicyType() {
+ return static_cast<nsContentPolicyType>(
+ nsContentUtils::InternalContentPolicyTypeToExternal(mContentPolicyType));
+}
+
+void InterceptionInfo::SetContentPolicyType(
+ const nsContentPolicyType aContentPolicyType) {
+ mContentPolicyType = aContentPolicyType;
+}
+
+const RedirectHistoryArray& InterceptionInfo::RedirectChain() {
+ return mRedirectChain;
+}
+
+void InterceptionInfo::SetRedirectChain(
+ const RedirectHistoryArray& aRedirectChain) {
+ for (auto entry : aRedirectChain) {
+ mRedirectChain.AppendElement(entry);
+ }
+}
+
+bool InterceptionInfo::FromThirdParty() { return mFromThirdParty; }
+
+void InterceptionInfo::SetFromThirdParty(bool aFromThirdParty) {
+ mFromThirdParty = aFromThirdParty;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/base/InterceptionInfo.h b/netwerk/base/InterceptionInfo.h
new file mode 100644
index 0000000000..ad2f2a4499
--- /dev/null
+++ b/netwerk/base/InterceptionInfo.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_InterceptionInfo_h
+#define mozilla_net_InterceptionInfo_h
+
+#include "nsIContentSecurityPolicy.h"
+#include "nsIInterceptionInfo.h"
+#include "nsIPrincipal.h"
+#include "nsIRedirectHistoryEntry.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla::net {
+
+using RedirectHistoryArray = nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>;
+
+class InterceptionInfo final : public nsIInterceptionInfo {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINTERCEPTIONINFO
+
+ InterceptionInfo(nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType,
+ const RedirectHistoryArray& aRedirectChain,
+ bool aFromThirdParty);
+
+ using nsIInterceptionInfo::GetExtContentPolicyType;
+
+ private:
+ ~InterceptionInfo() = default;
+
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsContentPolicyType mContentPolicyType{nsIContentPolicy::TYPE_INVALID};
+ RedirectHistoryArray mRedirectChain;
+ bool mFromThirdParty = false;
+};
+
+} // namespace mozilla::net
+
+#endif
diff --git a/netwerk/base/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp
new file mode 100644
index 0000000000..d544bf7275
--- /dev/null
+++ b/netwerk/base/LoadContextInfo.cpp
@@ -0,0 +1,168 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LoadContextInfo.h"
+
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsDocShell.h"
+#include "nsIChannel.h"
+#include "nsILoadContext.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::dom;
+namespace mozilla {
+namespace net {
+
+// LoadContextInfo
+
+NS_IMPL_ISUPPORTS(LoadContextInfo, nsILoadContextInfo)
+
+LoadContextInfo::LoadContextInfo(bool aIsAnonymous,
+ OriginAttributes aOriginAttributes)
+ : mIsAnonymous(aIsAnonymous),
+ mOriginAttributes(std::move(aOriginAttributes)) {}
+
+NS_IMETHODIMP LoadContextInfo::GetIsPrivate(bool* aIsPrivate) {
+ *aIsPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetIsAnonymous(bool* aIsAnonymous) {
+ *aIsAnonymous = mIsAnonymous;
+ return NS_OK;
+}
+
+OriginAttributes const* LoadContextInfo::OriginAttributesPtr() {
+ return &mOriginAttributes;
+}
+
+NS_IMETHODIMP LoadContextInfo::GetOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aVal) {
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// LoadContextInfoFactory
+
+NS_IMPL_ISUPPORTS(LoadContextInfoFactory, nsILoadContextInfoFactory)
+
+NS_IMETHODIMP LoadContextInfoFactory::GetDefault(
+ nsILoadContextInfo** aDefault) {
+ nsCOMPtr<nsILoadContextInfo> info =
+ GetLoadContextInfo(false, OriginAttributes());
+ info.forget(aDefault);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::GetPrivate(
+ nsILoadContextInfo** aPrivate) {
+ OriginAttributes attrs;
+ attrs.SyncAttributesWithPrivateBrowsing(true);
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(false, attrs);
+ info.forget(aPrivate);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::GetAnonymous(
+ nsILoadContextInfo** aAnonymous) {
+ nsCOMPtr<nsILoadContextInfo> info =
+ GetLoadContextInfo(true, OriginAttributes());
+ info.forget(aAnonymous);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::Custom(
+ bool aAnonymous, JS::Handle<JS::Value> aOriginAttributes, JSContext* cx,
+ nsILoadContextInfo** _retval) {
+ OriginAttributes attrs;
+ bool status = attrs.Init(cx, aOriginAttributes);
+ NS_ENSURE_TRUE(status, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aAnonymous, attrs);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::FromLoadContext(
+ nsILoadContext* aLoadContext, bool aAnonymous,
+ nsILoadContextInfo** _retval) {
+ nsCOMPtr<nsILoadContextInfo> info =
+ GetLoadContextInfo(aLoadContext, aAnonymous);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP LoadContextInfoFactory::FromWindow(nsIDOMWindow* aWindow,
+ bool aAnonymous,
+ nsILoadContextInfo** _retval) {
+ nsCOMPtr<nsILoadContextInfo> info = GetLoadContextInfo(aWindow, aAnonymous);
+ info.forget(_retval);
+ return NS_OK;
+}
+
+// Helper functions
+
+LoadContextInfo* GetLoadContextInfo(nsIChannel* aChannel) {
+ nsresult rv;
+
+ DebugOnly<bool> pb = NS_UsePrivateBrowsing(aChannel);
+
+ bool anon = false;
+ nsLoadFlags loadFlags;
+ rv = aChannel->GetLoadFlags(&loadFlags);
+ if (NS_SUCCEEDED(rv)) {
+ anon = !!(loadFlags & nsIChannel::LOAD_ANONYMOUS);
+ }
+
+ OriginAttributes oa;
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(aChannel, oa);
+ MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0));
+
+ return new LoadContextInfo(anon, oa);
+}
+
+LoadContextInfo* GetLoadContextInfo(nsILoadContext* aLoadContext,
+ bool aIsAnonymous) {
+ if (!aLoadContext) {
+ return new LoadContextInfo(aIsAnonymous, OriginAttributes());
+ }
+
+ OriginAttributes oa;
+ aLoadContext->GetOriginAttributes(oa);
+
+#ifdef DEBUG
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aLoadContext);
+ if (!docShell ||
+ nsDocShell::Cast(docShell)->GetBrowsingContext()->IsContent()) {
+ MOZ_ASSERT(aLoadContext->UsePrivateBrowsing() ==
+ (oa.mPrivateBrowsingId > 0));
+ }
+#endif
+
+ return new LoadContextInfo(aIsAnonymous, oa);
+}
+
+LoadContextInfo* GetLoadContextInfo(nsIDOMWindow* aWindow, bool aIsAnonymous) {
+ nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow);
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
+
+ return GetLoadContextInfo(loadContext, aIsAnonymous);
+}
+
+LoadContextInfo* GetLoadContextInfo(nsILoadContextInfo* aInfo) {
+ return new LoadContextInfo(aInfo->IsAnonymous(),
+ *aInfo->OriginAttributesPtr());
+}
+
+LoadContextInfo* GetLoadContextInfo(bool const aIsAnonymous,
+ OriginAttributes const& aOriginAttributes) {
+ return new LoadContextInfo(aIsAnonymous, aOriginAttributes);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/LoadContextInfo.h b/netwerk/base/LoadContextInfo.h
new file mode 100644
index 0000000000..efaf912db4
--- /dev/null
+++ b/netwerk/base/LoadContextInfo.h
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLoadContextInfo_h__
+#define nsLoadContextInfo_h__
+
+#include "nsILoadContextInfo.h"
+
+class nsIChannel;
+class nsILoadContext;
+
+namespace mozilla {
+namespace net {
+
+class LoadContextInfo final : public nsILoadContextInfo {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSILOADCONTEXTINFO
+
+ LoadContextInfo(bool aIsAnonymous, OriginAttributes aOriginAttributes);
+
+ private:
+ virtual ~LoadContextInfo() = default;
+
+ protected:
+ bool mIsAnonymous : 1;
+ OriginAttributes mOriginAttributes;
+};
+
+class LoadContextInfoFactory : public nsILoadContextInfoFactory {
+ virtual ~LoadContextInfoFactory() = default;
+
+ public:
+ NS_DECL_ISUPPORTS // deliberately not thread-safe
+ NS_DECL_NSILOADCONTEXTINFOFACTORY
+};
+
+LoadContextInfo* GetLoadContextInfo(nsIChannel* aChannel);
+
+LoadContextInfo* GetLoadContextInfo(nsILoadContext* aLoadContext,
+ bool aIsAnonymous);
+
+LoadContextInfo* GetLoadContextInfo(nsIDOMWindow* aWindow, bool aIsAnonymous);
+
+LoadContextInfo* GetLoadContextInfo(nsILoadContextInfo* aInfo);
+
+LoadContextInfo* GetLoadContextInfo(bool const aIsAnonymous,
+ OriginAttributes const& aOriginAttributes);
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
new file mode 100644
index 0000000000..eb90324c37
--- /dev/null
+++ b/netwerk/base/LoadInfo.cpp
@@ -0,0 +1,2372 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/LoadInfo.h"
+
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/PropertyAndElement.h" // JS_DefineElement
+#include "mozilla/Assertions.h"
+#include "mozilla/ExpandedPrincipal.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/ClientSource.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozIThirdPartyUtil.h"
+#include "ThirdPartyUtil.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocShell.h"
+#include "mozilla/dom/Document.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIScriptElement.h"
+#include "nsISupportsImpl.h"
+#include "nsISupportsUtils.h"
+#include "nsIXPConnect.h"
+#include "nsDocShell.h"
+#include "nsGlobalWindowInner.h"
+#include "nsMixedContentBlocker.h"
+#include "nsQueryObject.h"
+#include "nsRedirectHistoryEntry.h"
+#include "nsSandboxFlags.h"
+#include "nsICookieService.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla::net {
+
+static nsCString CurrentRemoteType() {
+ MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsContentProcess());
+ if (ContentChild* cc = ContentChild::GetSingleton()) {
+ return nsCString(cc->GetRemoteType());
+ }
+ return NOT_REMOTE_TYPE;
+}
+
+static nsContentPolicyType InternalContentPolicyTypeForFrame(
+ CanonicalBrowsingContext* aBrowsingContext) {
+ const auto& maybeEmbedderElementType =
+ aBrowsingContext->GetEmbedderElementType();
+ MOZ_ASSERT(maybeEmbedderElementType.isSome());
+ auto embedderElementType = maybeEmbedderElementType.value();
+
+ // Assign same type as in nsDocShell::DetermineContentType.
+ // N.B. internal content policy type will never be TYPE_DOCUMENT
+ return embedderElementType.EqualsLiteral("iframe")
+ ? nsIContentPolicy::TYPE_INTERNAL_IFRAME
+ : nsIContentPolicy::TYPE_INTERNAL_FRAME;
+}
+
+/* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForDocument(
+ dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal, const nsACString& aTriggeringRemoteType,
+ const OriginAttributes& aOriginAttributes, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags) {
+ return MakeAndAddRef<LoadInfo>(aBrowsingContext, aURI, aTriggeringPrincipal,
+ aTriggeringRemoteType, aOriginAttributes,
+ aSecurityFlags, aSandboxFlags);
+}
+
+/* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForFrame(
+ dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal, const nsACString& aTriggeringRemoteType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags) {
+ return MakeAndAddRef<LoadInfo>(aBrowsingContext, aTriggeringPrincipal,
+ aTriggeringRemoteType, aSecurityFlags,
+ aSandboxFlags);
+}
+
+/* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForNonDocument(
+ dom::WindowGlobalParent* aParentWGP, nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags) {
+ return MakeAndAddRef<LoadInfo>(
+ aParentWGP, aTriggeringPrincipal, aParentWGP->GetRemoteType(),
+ aContentPolicyType, aSecurityFlags, aSandboxFlags);
+}
+
+LoadInfo::LoadInfo(
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsINode* aLoadingContext, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ const Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo,
+ const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ uint32_t aSandboxFlags, bool aSkipCheckForBrokenURLOrZeroSized)
+ : mLoadingPrincipal(aLoadingContext ? aLoadingContext->NodePrincipal()
+ : aLoadingPrincipal),
+ mTriggeringPrincipal(aTriggeringPrincipal ? aTriggeringPrincipal
+ : mLoadingPrincipal.get()),
+ mTriggeringRemoteType(CurrentRemoteType()),
+ mSandboxedNullPrincipalID(nsID::GenerateUUID()),
+ mClientInfo(aLoadingClientInfo),
+ mController(aController),
+ mLoadingContext(do_GetWeakReference(aLoadingContext)),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mInternalContentPolicyType(aContentPolicyType),
+ mSkipCheckForBrokenURLOrZeroSized(aSkipCheckForBrokenURLOrZeroSized) {
+ MOZ_ASSERT(mLoadingPrincipal);
+ MOZ_ASSERT(mTriggeringPrincipal);
+
+#ifdef DEBUG
+ // TYPE_DOCUMENT loads initiated by javascript tests will go through
+ // nsIOService and use the wrong constructor. Don't enforce the
+ // !TYPE_DOCUMENT check in those cases
+ bool skipContentTypeCheck = false;
+ skipContentTypeCheck =
+ Preferences::GetBool("network.loadinfo.skip_type_assertion");
+#endif
+
+ // This constructor shouldn't be used for TYPE_DOCUMENT loads that don't
+ // have a loadingPrincipal
+ MOZ_ASSERT(skipContentTypeCheck || mLoadingPrincipal ||
+ mInternalContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT);
+
+ // We should only get an explicit controller for subresource requests.
+ MOZ_DIAGNOSTIC_ASSERT(aController.isNothing() ||
+ !nsContentUtils::IsNonSubresourceInternalPolicyType(
+ mInternalContentPolicyType));
+
+ // TODO(bug 1259873): Above, we initialize mIsThirdPartyContext to false
+ // meaning that consumers of LoadInfo that don't pass a context or pass a
+ // context from which we can't find a window will default to assuming that
+ // they're 1st party. It would be nice if we could default "safe" and assume
+ // that we are 3rd party until proven otherwise.
+
+ // if consumers pass both, aLoadingContext and aLoadingPrincipal
+ // then the loadingPrincipal must be the same as the node's principal
+ MOZ_ASSERT(!aLoadingContext || !aLoadingPrincipal ||
+ aLoadingContext->NodePrincipal() == aLoadingPrincipal);
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ mForceInheritPrincipalDropped =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ ExtContentPolicyType externalType =
+ nsContentUtils::InternalContentPolicyTypeToExternal(aContentPolicyType);
+
+ if (aLoadingContext) {
+ // Ensure that all network requests for a window client have the ClientInfo
+ // properly set. Workers must currently pass the loading ClientInfo
+ // explicitly. We allow main thread requests to explicitly pass the value as
+ // well.
+ if (mClientInfo.isNothing()) {
+ mClientInfo = aLoadingContext->OwnerDoc()->GetClientInfo();
+ }
+
+ // For subresource loads set the service worker based on the calling
+ // context's controller. Workers must currently pass the controller in
+ // explicitly. We allow main thread requests to explicitly pass the value
+ // as well, but otherwise extract from the loading context here.
+ if (mController.isNothing() &&
+ !nsContentUtils::IsNonSubresourceInternalPolicyType(
+ mInternalContentPolicyType)) {
+ mController = aLoadingContext->OwnerDoc()->GetController();
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> contextOuter =
+ aLoadingContext->OwnerDoc()->GetWindow();
+ if (contextOuter) {
+ ComputeIsThirdPartyContext(contextOuter);
+ RefPtr<dom::BrowsingContext> bc = contextOuter->GetBrowsingContext();
+ MOZ_ASSERT(bc);
+ mBrowsingContextID = bc->Id();
+
+ nsGlobalWindowInner* innerWindow =
+ nsGlobalWindowInner::Cast(contextOuter->GetCurrentInnerWindow());
+ if (innerWindow) {
+ mTopLevelPrincipal = innerWindow->GetTopLevelAntiTrackingPrincipal();
+
+ if (!mTopLevelPrincipal &&
+ externalType == ExtContentPolicy::TYPE_SUBDOCUMENT && bc->IsTop()) {
+ // If this is the first level iframe, innerWindow is our top-level
+ // principal.
+ mTopLevelPrincipal = innerWindow->GetPrincipal();
+ }
+ }
+
+ // Let's inherit the cookie behavior and permission from the parent
+ // document.
+ mCookieJarSettings = aLoadingContext->OwnerDoc()->CookieJarSettings();
+ }
+
+ mInnerWindowID = aLoadingContext->OwnerDoc()->InnerWindowID();
+ RefPtr<WindowContext> ctx = WindowContext::GetById(mInnerWindowID);
+ if (ctx) {
+ mLoadingEmbedderPolicy = ctx->GetEmbedderPolicy();
+ }
+ mDocumentHasUserInteracted =
+ aLoadingContext->OwnerDoc()->UserHasInteracted();
+
+ // Inherit HTTPS-Only Mode flags from parent document
+ mHttpsOnlyStatus |= aLoadingContext->OwnerDoc()->HttpsOnlyStatus();
+
+ // When the element being loaded is a frame, we choose the frame's window
+ // for the window ID and the frame element's window as the parent
+ // window. This is the behavior that Chrome exposes to add-ons.
+ // NB: If the frameLoaderOwner doesn't have a frame loader, then the load
+ // must be coming from an object (such as a plugin) that's loaded into it
+ // instead of a document being loaded. In that case, treat this object like
+ // any other non-document-loading element.
+ RefPtr<nsFrameLoaderOwner> frameLoaderOwner =
+ do_QueryObject(aLoadingContext);
+ RefPtr<nsFrameLoader> fl =
+ frameLoaderOwner ? frameLoaderOwner->GetFrameLoader() : nullptr;
+ if (fl) {
+ nsCOMPtr<nsIDocShell> docShell = fl->GetDocShell(IgnoreErrors());
+ if (docShell) {
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_GetInterface(docShell);
+ if (outerWindow) {
+ RefPtr<dom::BrowsingContext> bc = outerWindow->GetBrowsingContext();
+ mFrameBrowsingContextID = bc ? bc->Id() : 0;
+ }
+ }
+ }
+
+ // if the document forces all mixed content to be blocked, then we
+ // store that bit for all requests on the loadinfo.
+ mBlockAllMixedContent =
+ aLoadingContext->OwnerDoc()->GetBlockAllMixedContent(false) ||
+ (nsContentUtils::IsPreloadType(mInternalContentPolicyType) &&
+ aLoadingContext->OwnerDoc()->GetBlockAllMixedContent(true));
+
+ if (mLoadingPrincipal && BasePrincipal::Cast(mTriggeringPrincipal)
+ ->OverridesCSP(mLoadingPrincipal)) {
+ // if the load is triggered by an addon which potentially overrides the
+ // CSP of the document, then do not force insecure requests to be
+ // upgraded.
+ mUpgradeInsecureRequests = false;
+ } else {
+ // if the document forces all requests to be upgraded from http to https,
+ // then we should do that for all requests. If it only forces preloads to
+ // be upgraded then we should enforce upgrade insecure requests only for
+ // preloads.
+ mUpgradeInsecureRequests =
+ aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(false) ||
+ (nsContentUtils::IsPreloadType(mInternalContentPolicyType) &&
+ aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(true));
+ }
+
+ if (nsMixedContentBlocker::IsUpgradableContentType(
+ mInternalContentPolicyType, /* aConsiderPrefs */ false)) {
+ // Check the load is within a secure context but ignore loopback URLs
+ if (mLoadingPrincipal->GetIsOriginPotentiallyTrustworthy() &&
+ !mLoadingPrincipal->GetIsLoopbackHost()) {
+ if (nsMixedContentBlocker::IsUpgradableContentType(
+ mInternalContentPolicyType, /* aConsiderPrefs */ true)) {
+ mBrowserUpgradeInsecureRequests = true;
+ } else {
+ mBrowserWouldUpgradeInsecureRequests = true;
+ }
+ }
+ }
+ }
+ mOriginAttributes = mLoadingPrincipal->OriginAttributesRef();
+
+ // We need to do this after inheriting the document's origin attributes
+ // above, in case the loading principal ends up being the system principal.
+ if (aLoadingContext) {
+ nsCOMPtr<nsILoadContext> loadContext =
+ aLoadingContext->OwnerDoc()->GetLoadContext();
+ nsCOMPtr<nsIDocShell> docShell = aLoadingContext->OwnerDoc()->GetDocShell();
+ if (loadContext && docShell &&
+ docShell->GetBrowsingContext()->IsContent()) {
+ bool usePrivateBrowsing;
+ nsresult rv = loadContext->GetUsePrivateBrowsing(&usePrivateBrowsing);
+ if (NS_SUCCEEDED(rv)) {
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(usePrivateBrowsing);
+ }
+ }
+
+ if (!loadContext) {
+ // Things like svg documents being used as images don't have a load
+ // context or a docshell, in that case try to inherit private browsing
+ // from the documents channel (which is how we determine which imgLoader
+ // is used).
+ nsCOMPtr<nsIChannel> channel = aLoadingContext->OwnerDoc()->GetChannel();
+ if (channel) {
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(
+ NS_UsePrivateBrowsing(channel));
+ }
+ }
+
+ // For chrome docshell, the mPrivateBrowsingId remains 0 even its
+ // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in
+ // origin attributes if the type of the docshell is content.
+ MOZ_ASSERT(!docShell || !docShell->GetBrowsingContext()->IsChrome() ||
+ mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+}
+
+/* Constructor takes an outer window, but no loadingNode or loadingPrincipal.
+ * This constructor should only be used for TYPE_DOCUMENT loads, since they
+ * have a null loadingNode and loadingPrincipal.
+ */
+LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsISupports* aContextForTopLevelLoad,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ mTriggeringRemoteType(CurrentRemoteType()),
+ mSandboxedNullPrincipalID(nsID::GenerateUUID()),
+ mContextForTopLevelLoad(do_GetWeakReference(aContextForTopLevelLoad)),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT) {
+ // Top-level loads are never third-party
+ // Grab the information we can out of the window.
+ MOZ_ASSERT(aOuterWindow);
+ MOZ_ASSERT(mTriggeringPrincipal);
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ mForceInheritPrincipalDropped =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ RefPtr<BrowsingContext> bc = aOuterWindow->GetBrowsingContext();
+ mBrowsingContextID = bc ? bc->Id() : 0;
+
+ // This should be removed in bug 1618557
+ nsGlobalWindowInner* innerWindow =
+ nsGlobalWindowInner::Cast(aOuterWindow->GetCurrentInnerWindow());
+ if (innerWindow) {
+ mTopLevelPrincipal = innerWindow->GetTopLevelAntiTrackingPrincipal();
+ }
+
+ // get the docshell from the outerwindow, and then get the originattributes
+ nsCOMPtr<nsIDocShell> docShell = aOuterWindow->GetDocShell();
+ MOZ_ASSERT(docShell);
+ mOriginAttributes = nsDocShell::Cast(docShell)->GetOriginAttributes();
+
+ // We sometimes use this constructor for security checks for outer windows
+ // that aren't top level.
+ if (aSecurityFlags != nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK) {
+ MOZ_ASSERT(aOuterWindow->GetBrowsingContext()->IsTop());
+ }
+
+#ifdef DEBUG
+ if (docShell->GetBrowsingContext()->IsChrome()) {
+ MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+#endif
+
+ // Let's take the current cookie behavior and current cookie permission
+ // for the documents' loadInfo. Note that for any other loadInfos,
+ // cookieBehavior will be BEHAVIOR_REJECT for security reasons.
+ bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
+ bool shouldResistFingerprinting =
+ nsContentUtils::ShouldResistFingerprinting_dangerous(
+ aURI, mOriginAttributes,
+ "We are creating CookieJarSettings, so we can't have one already.",
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ mCookieJarSettings = CookieJarSettings::Create(
+ isPrivate ? CookieJarSettings::ePrivate : CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+}
+
+LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ const OriginAttributes& aOriginAttributes,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ mTriggeringRemoteType(aTriggeringRemoteType),
+ mSandboxedNullPrincipalID(nsID::GenerateUUID()),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT) {
+ // Top-level loads are never third-party
+ // Grab the information we can out of the window.
+ MOZ_ASSERT(aBrowsingContext);
+ MOZ_ASSERT(mTriggeringPrincipal);
+ MOZ_ASSERT(aSecurityFlags !=
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK);
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ mForceInheritPrincipalDropped =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ mBrowsingContextID = aBrowsingContext->Id();
+ mOriginAttributes = aOriginAttributes;
+
+#ifdef DEBUG
+ if (aBrowsingContext->IsChrome()) {
+ MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+#endif
+
+ // If we think we should not resist fingerprinting, defer to the opener's
+ // RFP bit (if there is an opener.) If the opener is also exempted, it stays
+ // true, otherwise we will put a false into the CJS and that will be respected
+ // on this document.
+ bool shouldResistFingerprinting =
+ nsContentUtils::ShouldResistFingerprinting_dangerous(
+ aURI, mOriginAttributes,
+ "We are creating CookieJarSettings, so we can't have one already.",
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ RefPtr<BrowsingContext> opener = aBrowsingContext->GetOpener();
+ if (!shouldResistFingerprinting && opener &&
+ opener->GetCurrentWindowContext()) {
+ shouldResistFingerprinting =
+ opener->GetCurrentWindowContext()->ShouldResistFingerprinting();
+ }
+
+ const bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
+
+ // Let's take the current cookie behavior and current cookie permission
+ // for the documents' loadInfo. Note that for any other loadInfos,
+ // cookieBehavior will be BEHAVIOR_REJECT for security reasons.
+ mCookieJarSettings = CookieJarSettings::Create(
+ isPrivate ? CookieJarSettings::ePrivate : CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+}
+
+LoadInfo::LoadInfo(dom::WindowGlobalParent* aParentWGP,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ nsContentPolicyType aContentPolicyType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ mTriggeringRemoteType(aTriggeringRemoteType),
+ mSandboxedNullPrincipalID(nsID::GenerateUUID()),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mInternalContentPolicyType(aContentPolicyType) {
+ CanonicalBrowsingContext* parentBC = aParentWGP->BrowsingContext();
+ MOZ_ASSERT(parentBC);
+ ComputeAncestors(parentBC, mAncestorPrincipals, mAncestorBrowsingContextIDs);
+
+ RefPtr<WindowGlobalParent> topLevelWGP = aParentWGP->TopWindowContext();
+
+ // if the load is sandboxed, we can not also inherit the principal
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ mForceInheritPrincipalDropped =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ // Ensure that all network requests for a window client have the ClientInfo
+ // properly set.
+ mClientInfo = aParentWGP->GetClientInfo();
+ mLoadingPrincipal = aParentWGP->DocumentPrincipal();
+ ComputeIsThirdPartyContext(aParentWGP);
+
+ mBrowsingContextID = parentBC->Id();
+
+ // Let's inherit the cookie behavior and permission from the embedder
+ // document.
+ mCookieJarSettings = aParentWGP->CookieJarSettings();
+ if (topLevelWGP->BrowsingContext()->IsTop()) {
+ if (mCookieJarSettings) {
+ bool stopAtOurLevel = mCookieJarSettings->GetCookieBehavior() ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER;
+ if (!stopAtOurLevel ||
+ topLevelWGP->OuterWindowId() != aParentWGP->OuterWindowId()) {
+ mTopLevelPrincipal = topLevelWGP->DocumentPrincipal();
+ }
+ }
+ }
+
+ if (!mTopLevelPrincipal && parentBC->IsTop()) {
+ // If this is the first level iframe, embedder WindowGlobalParent's document
+ // principal is our top-level principal.
+ mTopLevelPrincipal = aParentWGP->DocumentPrincipal();
+ }
+
+ mInnerWindowID = aParentWGP->InnerWindowId();
+ mDocumentHasUserInteracted = aParentWGP->DocumentHasUserInteracted();
+
+ // if the document forces all mixed content to be blocked, then we
+ // store that bit for all requests on the loadinfo.
+ mBlockAllMixedContent = aParentWGP->GetDocumentBlockAllMixedContent();
+
+ if (mTopLevelPrincipal && BasePrincipal::Cast(mTriggeringPrincipal)
+ ->OverridesCSP(mTopLevelPrincipal)) {
+ // if the load is triggered by an addon which potentially overrides the
+ // CSP of the document, then do not force insecure requests to be
+ // upgraded.
+ mUpgradeInsecureRequests = false;
+ } else {
+ // if the document forces all requests to be upgraded from http to https,
+ // then we should do that for all requests. If it only forces preloads to
+ // be upgraded then we should enforce upgrade insecure requests only for
+ // preloads.
+ mUpgradeInsecureRequests = aParentWGP->GetDocumentUpgradeInsecureRequests();
+ }
+ mOriginAttributes = mLoadingPrincipal->OriginAttributesRef();
+
+ // We need to do this after inheriting the document's origin attributes
+ // above, in case the loading principal ends up being the system principal.
+ if (parentBC->IsContent()) {
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(
+ parentBC->UsePrivateBrowsing());
+ }
+
+ mHttpsOnlyStatus |= aParentWGP->HttpsOnlyStatus();
+
+ // For chrome BC, the mPrivateBrowsingId remains 0 even its
+ // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in
+ // origin attributes if the type of the BC is content.
+ if (parentBC->IsChrome()) {
+ MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+
+ RefPtr<WindowContext> ctx = WindowContext::GetById(mInnerWindowID);
+ if (ctx) {
+ mLoadingEmbedderPolicy = ctx->GetEmbedderPolicy();
+
+ if (Document* document = ctx->GetDocument()) {
+ mIsOriginTrialCoepCredentiallessEnabledForTopLevel =
+ document->Trials().IsEnabled(OriginTrial::CoepCredentialless);
+ }
+ }
+}
+
+// Used for TYPE_FRAME or TYPE_IFRAME load.
+LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : LoadInfo(aBrowsingContext->GetParentWindowContext(), aTriggeringPrincipal,
+ aTriggeringRemoteType,
+ InternalContentPolicyTypeForFrame(aBrowsingContext),
+ aSecurityFlags, aSandboxFlags) {
+ mFrameBrowsingContextID = aBrowsingContext->Id();
+}
+
+LoadInfo::LoadInfo(const LoadInfo& rhs)
+ : mLoadingPrincipal(rhs.mLoadingPrincipal),
+ mTriggeringPrincipal(rhs.mTriggeringPrincipal),
+ mPrincipalToInherit(rhs.mPrincipalToInherit),
+ mTopLevelPrincipal(rhs.mTopLevelPrincipal),
+ mResultPrincipalURI(rhs.mResultPrincipalURI),
+ mChannelCreationOriginalURI(rhs.mChannelCreationOriginalURI),
+ mCookieJarSettings(rhs.mCookieJarSettings),
+ mCspToInherit(rhs.mCspToInherit),
+ mTriggeringRemoteType(rhs.mTriggeringRemoteType),
+ mSandboxedNullPrincipalID(rhs.mSandboxedNullPrincipalID),
+ mClientInfo(rhs.mClientInfo),
+ // mReservedClientSource must be handled specially during redirect
+ // mReservedClientInfo must be handled specially during redirect
+ // mInitialClientInfo must be handled specially during redirect
+ mController(rhs.mController),
+ mPerformanceStorage(rhs.mPerformanceStorage),
+ mLoadingContext(rhs.mLoadingContext),
+ mContextForTopLevelLoad(rhs.mContextForTopLevelLoad),
+ mSecurityFlags(rhs.mSecurityFlags),
+ mSandboxFlags(rhs.mSandboxFlags),
+ mTriggeringSandboxFlags(rhs.mTriggeringSandboxFlags),
+ mTriggeringWindowId(rhs.mTriggeringWindowId),
+ mTriggeringStorageAccess(rhs.mTriggeringStorageAccess),
+ mInternalContentPolicyType(rhs.mInternalContentPolicyType),
+ mTainting(rhs.mTainting),
+ mBlockAllMixedContent(rhs.mBlockAllMixedContent),
+ mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests),
+ mBrowserUpgradeInsecureRequests(rhs.mBrowserUpgradeInsecureRequests),
+ mBrowserDidUpgradeInsecureRequests(
+ rhs.mBrowserDidUpgradeInsecureRequests),
+ mBrowserWouldUpgradeInsecureRequests(
+ rhs.mBrowserWouldUpgradeInsecureRequests),
+ mForceAllowDataURI(rhs.mForceAllowDataURI),
+ mAllowInsecureRedirectToDataURI(rhs.mAllowInsecureRedirectToDataURI),
+ mSkipContentPolicyCheckForWebRequest(
+ rhs.mSkipContentPolicyCheckForWebRequest),
+ mOriginalFrameSrcLoad(rhs.mOriginalFrameSrcLoad),
+ mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped),
+ mInnerWindowID(rhs.mInnerWindowID),
+ mBrowsingContextID(rhs.mBrowsingContextID),
+ mWorkerAssociatedBrowsingContextID(
+ rhs.mWorkerAssociatedBrowsingContextID),
+ mFrameBrowsingContextID(rhs.mFrameBrowsingContextID),
+ mInitialSecurityCheckDone(rhs.mInitialSecurityCheckDone),
+ mIsThirdPartyContext(rhs.mIsThirdPartyContext),
+ mIsThirdPartyContextToTopWindow(rhs.mIsThirdPartyContextToTopWindow),
+ mIsFormSubmission(rhs.mIsFormSubmission),
+ mSendCSPViolationEvents(rhs.mSendCSPViolationEvents),
+ mOriginAttributes(rhs.mOriginAttributes),
+ mRedirectChainIncludingInternalRedirects(
+ rhs.mRedirectChainIncludingInternalRedirects.Clone()),
+ mRedirectChain(rhs.mRedirectChain.Clone()),
+ mAncestorPrincipals(rhs.mAncestorPrincipals.Clone()),
+ mAncestorBrowsingContextIDs(rhs.mAncestorBrowsingContextIDs.Clone()),
+ mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders.Clone()),
+ mRequestBlockingReason(rhs.mRequestBlockingReason),
+ mForcePreflight(rhs.mForcePreflight),
+ mIsPreflight(rhs.mIsPreflight),
+ mLoadTriggeredFromExternal(rhs.mLoadTriggeredFromExternal),
+ mDocumentHasUserInteracted(rhs.mDocumentHasUserInteracted),
+ mAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ rhs.mAllowListFutureDocumentsCreatedFromThisRedirectChain),
+ mNeedForCheckingAntiTrackingHeuristic(
+ rhs.mNeedForCheckingAntiTrackingHeuristic),
+ mCspNonce(rhs.mCspNonce),
+ mIntegrityMetadata(rhs.mIntegrityMetadata),
+ mSkipContentSniffing(rhs.mSkipContentSniffing),
+ mHttpsOnlyStatus(rhs.mHttpsOnlyStatus),
+ mHstsStatus(rhs.mHstsStatus),
+ mHasValidUserGestureActivation(rhs.mHasValidUserGestureActivation),
+ mAllowDeprecatedSystemRequests(rhs.mAllowDeprecatedSystemRequests),
+ mIsInDevToolsContext(rhs.mIsInDevToolsContext),
+ mParserCreatedScript(rhs.mParserCreatedScript),
+ mStoragePermission(rhs.mStoragePermission),
+ mOverriddenFingerprintingSettings(rhs.mOverriddenFingerprintingSettings),
+#ifdef DEBUG
+ mOverriddenFingerprintingSettingsIsSet(
+ rhs.mOverriddenFingerprintingSettingsIsSet),
+#endif
+ mIsMetaRefresh(rhs.mIsMetaRefresh),
+ mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes),
+ mIsMediaRequest(rhs.mIsMediaRequest),
+ mIsMediaInitialRequest(rhs.mIsMediaInitialRequest),
+ mIsFromObjectOrEmbed(rhs.mIsFromObjectOrEmbed),
+ mLoadingEmbedderPolicy(rhs.mLoadingEmbedderPolicy),
+ mIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ rhs.mIsOriginTrialCoepCredentiallessEnabledForTopLevel),
+ mUnstrippedURI(rhs.mUnstrippedURI),
+ mInterceptionInfo(rhs.mInterceptionInfo),
+ mHasInjectedCookieForCookieBannerHandling(
+ rhs.mHasInjectedCookieForCookieBannerHandling),
+ mWasSchemelessInput(rhs.mWasSchemelessInput) {
+}
+
+LoadInfo::LoadInfo(
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aTopLevelPrincipal,
+ nsIURI* aResultPrincipalURI, nsICookieJarSettings* aCookieJarSettings,
+ nsIContentSecurityPolicy* aCspToInherit,
+ const nsACString& aTriggeringRemoteType,
+ const nsID& aSandboxedNullPrincipalID, const Maybe<ClientInfo>& aClientInfo,
+ const Maybe<ClientInfo>& aReservedClientInfo,
+ const Maybe<ClientInfo>& aInitialClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags,
+ uint32_t aTriggeringSandboxFlags, uint64_t aTriggeringWindowId,
+ bool aTriggeringStorageAccess, nsContentPolicyType aContentPolicyType,
+ LoadTainting aTainting, bool aBlockAllMixedContent,
+ bool aUpgradeInsecureRequests, bool aBrowserUpgradeInsecureRequests,
+ bool aBrowserDidUpgradeInsecureRequests,
+ bool aBrowserWouldUpgradeInsecureRequests, bool aForceAllowDataURI,
+ bool aAllowInsecureRedirectToDataURI,
+ bool aSkipContentPolicyCheckForWebRequest, bool aOriginalFrameSrcLoad,
+ bool aForceInheritPrincipalDropped, uint64_t aInnerWindowID,
+ uint64_t aBrowsingContextID, uint64_t aFrameBrowsingContextID,
+ bool aInitialSecurityCheckDone, bool aIsThirdPartyContext,
+ const Maybe<bool>& aIsThirdPartyContextToTopWindow, bool aIsFormSubmission,
+ bool aSendCSPViolationEvents, const OriginAttributes& aOriginAttributes,
+ RedirectHistoryArray&& aRedirectChainIncludingInternalRedirects,
+ RedirectHistoryArray&& aRedirectChain,
+ nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals,
+ const nsTArray<uint64_t>& aAncestorBrowsingContextIDs,
+ const nsTArray<nsCString>& aCorsUnsafeHeaders, bool aForcePreflight,
+ bool aIsPreflight, bool aLoadTriggeredFromExternal,
+ bool aServiceWorkerTaintingSynthesized, bool aDocumentHasUserInteracted,
+ bool aAllowListFutureDocumentsCreatedFromThisRedirectChain,
+ bool aNeedForCheckingAntiTrackingHeuristic, const nsAString& aCspNonce,
+ const nsAString& aIntegrityMetadata, bool aSkipContentSniffing,
+ uint32_t aHttpsOnlyStatus, bool aHstsStatus,
+ bool aHasValidUserGestureActivation, bool aAllowDeprecatedSystemRequests,
+ bool aIsInDevToolsContext, bool aParserCreatedScript,
+ nsILoadInfo::StoragePermissionState aStoragePermission,
+ const Maybe<RFPTarget>& aOverriddenFingerprintingSettings,
+ bool aIsMetaRefresh, uint32_t aRequestBlockingReason,
+ nsINode* aLoadingContext,
+ nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy,
+ bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel,
+ nsIURI* aUnstrippedURI, nsIInterceptionInfo* aInterceptionInfo,
+ bool aHasInjectedCookieForCookieBannerHandling, bool aWasSchemelessInput)
+ : mLoadingPrincipal(aLoadingPrincipal),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mPrincipalToInherit(aPrincipalToInherit),
+ mTopLevelPrincipal(aTopLevelPrincipal),
+ mResultPrincipalURI(aResultPrincipalURI),
+ mCookieJarSettings(aCookieJarSettings),
+ mCspToInherit(aCspToInherit),
+ mTriggeringRemoteType(aTriggeringRemoteType),
+ mSandboxedNullPrincipalID(aSandboxedNullPrincipalID),
+ mClientInfo(aClientInfo),
+ mReservedClientInfo(aReservedClientInfo),
+ mInitialClientInfo(aInitialClientInfo),
+ mController(aController),
+ mLoadingContext(do_GetWeakReference(aLoadingContext)),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mTriggeringSandboxFlags(aTriggeringSandboxFlags),
+ mTriggeringWindowId(aTriggeringWindowId),
+ mTriggeringStorageAccess(aTriggeringStorageAccess),
+ mInternalContentPolicyType(aContentPolicyType),
+ mTainting(aTainting),
+ mBlockAllMixedContent(aBlockAllMixedContent),
+ mUpgradeInsecureRequests(aUpgradeInsecureRequests),
+ mBrowserUpgradeInsecureRequests(aBrowserUpgradeInsecureRequests),
+ mBrowserDidUpgradeInsecureRequests(aBrowserDidUpgradeInsecureRequests),
+ mBrowserWouldUpgradeInsecureRequests(
+ aBrowserWouldUpgradeInsecureRequests),
+ mForceAllowDataURI(aForceAllowDataURI),
+ mAllowInsecureRedirectToDataURI(aAllowInsecureRedirectToDataURI),
+ mSkipContentPolicyCheckForWebRequest(
+ aSkipContentPolicyCheckForWebRequest),
+ mOriginalFrameSrcLoad(aOriginalFrameSrcLoad),
+ mForceInheritPrincipalDropped(aForceInheritPrincipalDropped),
+ mInnerWindowID(aInnerWindowID),
+ mBrowsingContextID(aBrowsingContextID),
+ mFrameBrowsingContextID(aFrameBrowsingContextID),
+ mInitialSecurityCheckDone(aInitialSecurityCheckDone),
+ mIsThirdPartyContext(aIsThirdPartyContext),
+ mIsThirdPartyContextToTopWindow(aIsThirdPartyContextToTopWindow),
+ mIsFormSubmission(aIsFormSubmission),
+ mSendCSPViolationEvents(aSendCSPViolationEvents),
+ mOriginAttributes(aOriginAttributes),
+ mRedirectChainIncludingInternalRedirects(
+ std::move(aRedirectChainIncludingInternalRedirects)),
+ mRedirectChain(std::move(aRedirectChain)),
+ mAncestorPrincipals(std::move(aAncestorPrincipals)),
+ mAncestorBrowsingContextIDs(aAncestorBrowsingContextIDs.Clone()),
+ mCorsUnsafeHeaders(aCorsUnsafeHeaders.Clone()),
+ mRequestBlockingReason(aRequestBlockingReason),
+ mForcePreflight(aForcePreflight),
+ mIsPreflight(aIsPreflight),
+ mLoadTriggeredFromExternal(aLoadTriggeredFromExternal),
+ mServiceWorkerTaintingSynthesized(aServiceWorkerTaintingSynthesized),
+ mDocumentHasUserInteracted(aDocumentHasUserInteracted),
+ mAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ aAllowListFutureDocumentsCreatedFromThisRedirectChain),
+ mNeedForCheckingAntiTrackingHeuristic(
+ aNeedForCheckingAntiTrackingHeuristic),
+ mCspNonce(aCspNonce),
+ mIntegrityMetadata(aIntegrityMetadata),
+ mSkipContentSniffing(aSkipContentSniffing),
+ mHttpsOnlyStatus(aHttpsOnlyStatus),
+ mHstsStatus(aHstsStatus),
+ mHasValidUserGestureActivation(aHasValidUserGestureActivation),
+ mAllowDeprecatedSystemRequests(aAllowDeprecatedSystemRequests),
+ mIsInDevToolsContext(aIsInDevToolsContext),
+ mParserCreatedScript(aParserCreatedScript),
+ mStoragePermission(aStoragePermission),
+ mOverriddenFingerprintingSettings(aOverriddenFingerprintingSettings),
+ mIsMetaRefresh(aIsMetaRefresh),
+ mLoadingEmbedderPolicy(aLoadingEmbedderPolicy),
+ mIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ aIsOriginTrialCoepCredentiallessEnabledForTopLevel),
+ mUnstrippedURI(aUnstrippedURI),
+ mInterceptionInfo(aInterceptionInfo),
+ mHasInjectedCookieForCookieBannerHandling(
+ aHasInjectedCookieForCookieBannerHandling),
+ mWasSchemelessInput(aWasSchemelessInput) {
+ // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
+ MOZ_ASSERT(mLoadingPrincipal ||
+ aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
+ MOZ_ASSERT(mTriggeringPrincipal);
+}
+
+// static
+void LoadInfo::ComputeAncestors(
+ CanonicalBrowsingContext* aBC,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aAncestorPrincipals,
+ nsTArray<uint64_t>& aBrowsingContextIDs) {
+ MOZ_ASSERT(aAncestorPrincipals.IsEmpty());
+ MOZ_ASSERT(aBrowsingContextIDs.IsEmpty());
+ CanonicalBrowsingContext* ancestorBC = aBC;
+ // Iterate over ancestor WindowGlobalParents, collecting principals and outer
+ // window IDs.
+ while (WindowGlobalParent* ancestorWGP =
+ ancestorBC->GetParentWindowContext()) {
+ ancestorBC = ancestorWGP->BrowsingContext();
+
+ nsCOMPtr<nsIPrincipal> parentPrincipal = ancestorWGP->DocumentPrincipal();
+ MOZ_ASSERT(parentPrincipal, "Ancestor principal is null");
+ aAncestorPrincipals.AppendElement(parentPrincipal.forget());
+ aBrowsingContextIDs.AppendElement(ancestorBC->Id());
+ }
+}
+void LoadInfo::ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow) {
+ ExtContentPolicyType type =
+ nsContentUtils::InternalContentPolicyTypeToExternal(
+ mInternalContentPolicyType);
+ if (type == ExtContentPolicy::TYPE_DOCUMENT) {
+ // Top-level loads are never third-party.
+ mIsThirdPartyContext = false;
+ return;
+ }
+
+ nsCOMPtr<mozIThirdPartyUtil> util(do_GetService(THIRDPARTYUTIL_CONTRACTID));
+ if (NS_WARN_IF(!util)) {
+ return;
+ }
+
+ util->IsThirdPartyWindow(aOuterWindow, nullptr, &mIsThirdPartyContext);
+}
+
+void LoadInfo::ComputeIsThirdPartyContext(dom::WindowGlobalParent* aGlobal) {
+ if (nsILoadInfo::GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ // Top-level loads are never third-party.
+ mIsThirdPartyContext = false;
+ return;
+ }
+
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+ if (!thirdPartyUtil) {
+ return;
+ }
+ thirdPartyUtil->IsThirdPartyGlobal(aGlobal, &mIsThirdPartyContext);
+}
+
+NS_IMPL_ISUPPORTS(LoadInfo, nsILoadInfo)
+
+LoadInfo::~LoadInfo() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); }
+
+already_AddRefed<nsILoadInfo> LoadInfo::Clone() const {
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ return copy.forget();
+}
+
+already_AddRefed<nsILoadInfo> LoadInfo::CloneWithNewSecFlags(
+ nsSecurityFlags aSecurityFlags) const {
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ copy->mSecurityFlags = aSecurityFlags;
+ return copy.forget();
+}
+
+already_AddRefed<nsILoadInfo> LoadInfo::CloneForNewRequest() const {
+ RefPtr<LoadInfo> copy(new LoadInfo(*this));
+ copy->mInitialSecurityCheckDone = false;
+ copy->mRedirectChainIncludingInternalRedirects.Clear();
+ copy->mRedirectChain.Clear();
+ copy->mResultPrincipalURI = nullptr;
+ return copy.forget();
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) {
+ *aLoadingPrincipal = do_AddRef(mLoadingPrincipal).take();
+ return NS_OK;
+}
+
+nsIPrincipal* LoadInfo::VirtualGetLoadingPrincipal() {
+ return mLoadingPrincipal;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) {
+ *aTriggeringPrincipal = do_AddRef(mTriggeringPrincipal).take();
+ return NS_OK;
+}
+
+nsIPrincipal* LoadInfo::TriggeringPrincipal() { return mTriggeringPrincipal; }
+
+NS_IMETHODIMP
+LoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) {
+ *aPrincipalToInherit = do_AddRef(mPrincipalToInherit).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) {
+ MOZ_ASSERT(aPrincipalToInherit, "must be a valid principal to inherit");
+ mPrincipalToInherit = aPrincipalToInherit;
+ return NS_OK;
+}
+
+nsIPrincipal* LoadInfo::PrincipalToInherit() { return mPrincipalToInherit; }
+
+nsIPrincipal* LoadInfo::FindPrincipalToInherit(nsIChannel* aChannel) {
+ if (mPrincipalToInherit) {
+ return mPrincipalToInherit;
+ }
+
+ nsCOMPtr<nsIURI> uri = mResultPrincipalURI;
+ if (!uri) {
+ Unused << aChannel->GetOriginalURI(getter_AddRefs(uri));
+ }
+
+ auto* prin = BasePrincipal::Cast(mTriggeringPrincipal);
+ return prin->PrincipalToInherit(uri);
+}
+
+const nsID& LoadInfo::GetSandboxedNullPrincipalID() {
+ MOZ_ASSERT(!mSandboxedNullPrincipalID.Equals(nsID{}),
+ "mSandboxedNullPrincipalID wasn't initialized?");
+ return mSandboxedNullPrincipalID;
+}
+
+void LoadInfo::ResetSandboxedNullPrincipalID() {
+ mSandboxedNullPrincipalID = nsID::GenerateUUID();
+}
+
+nsIPrincipal* LoadInfo::GetTopLevelPrincipal() { return mTopLevelPrincipal; }
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringRemoteType(nsACString& aTriggeringRemoteType) {
+ aTriggeringRemoteType = mTriggeringRemoteType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetTriggeringRemoteType(const nsACString& aTriggeringRemoteType) {
+ mTriggeringRemoteType = aTriggeringRemoteType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingDocument(Document** aResult) {
+ if (nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext)) {
+ RefPtr<Document> context = node->OwnerDoc();
+ context.forget(aResult);
+ }
+ return NS_OK;
+}
+
+nsINode* LoadInfo::LoadingNode() {
+ nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext);
+ return node;
+}
+
+already_AddRefed<nsISupports> LoadInfo::ContextForTopLevelLoad() {
+ // Most likely you want to query LoadingNode() instead of
+ // ContextForTopLevelLoad() if this assertion fires.
+ MOZ_ASSERT(mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "should only query this context for top level document loads");
+ nsCOMPtr<nsISupports> context = do_QueryReferent(mContextForTopLevelLoad);
+ return context.forget();
+}
+
+already_AddRefed<nsISupports> LoadInfo::GetLoadingContext() {
+ nsCOMPtr<nsISupports> context;
+ if (mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
+ context = ContextForTopLevelLoad();
+ } else {
+ context = LoadingNode();
+ }
+ return context.forget();
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingContextXPCOM(nsISupports** aResult) {
+ nsCOMPtr<nsISupports> context = GetLoadingContext();
+ context.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSecurityFlags(nsSecurityFlags* aResult) {
+ *aResult = mSecurityFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSandboxFlags(uint32_t* aResult) {
+ *aResult = mSandboxFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringSandboxFlags(uint32_t* aResult) {
+ *aResult = mTriggeringSandboxFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetTriggeringSandboxFlags(uint32_t aFlags) {
+ mTriggeringSandboxFlags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringWindowId(uint64_t* aResult) {
+ *aResult = mTriggeringWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetTriggeringWindowId(uint64_t aFlags) {
+ mTriggeringWindowId = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTriggeringStorageAccess(bool* aResult) {
+ *aResult = mTriggeringStorageAccess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetTriggeringStorageAccess(bool aFlags) {
+ mTriggeringStorageAccess = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSecurityMode(uint32_t* aFlags) {
+ *aFlags = (mSecurityFlags &
+ (nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT |
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED |
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL |
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) {
+ *aIsInThirdPartyContext = mIsThirdPartyContext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsInThirdPartyContext(bool aIsInThirdPartyContext) {
+ mIsThirdPartyContext = aIsInThirdPartyContext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsThirdPartyContextToTopWindow(
+ bool* aIsThirdPartyContextToTopWindow) {
+ *aIsThirdPartyContextToTopWindow =
+ mIsThirdPartyContextToTopWindow.valueOr(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsThirdPartyContextToTopWindow(
+ bool aIsThirdPartyContextToTopWindow) {
+ mIsThirdPartyContextToTopWindow = Some(aIsThirdPartyContextToTopWindow);
+ return NS_OK;
+}
+
+static const uint32_t sCookiePolicyMask =
+ nsILoadInfo::SEC_COOKIES_DEFAULT | nsILoadInfo::SEC_COOKIES_INCLUDE |
+ nsILoadInfo::SEC_COOKIES_SAME_ORIGIN | nsILoadInfo::SEC_COOKIES_OMIT;
+
+NS_IMETHODIMP
+LoadInfo::GetCookiePolicy(uint32_t* aResult) {
+ uint32_t policy = mSecurityFlags & sCookiePolicyMask;
+ if (policy == nsILoadInfo::SEC_COOKIES_DEFAULT) {
+ policy = (mSecurityFlags & SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT)
+ ? nsILoadInfo::SEC_COOKIES_SAME_ORIGIN
+ : nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+
+ *aResult = policy;
+ return NS_OK;
+}
+
+namespace {
+
+already_AddRefed<nsICookieJarSettings> CreateCookieJarSettings(
+ nsContentPolicyType aContentPolicyType, bool aIsPrivate,
+ bool shouldResistFingerprinting) {
+ if (StaticPrefs::network_cookieJarSettings_unblocked_for_testing()) {
+ return aIsPrivate ? CookieJarSettings::Create(CookieJarSettings::ePrivate,
+ shouldResistFingerprinting)
+ : CookieJarSettings::Create(CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+ }
+
+ // These contentPolictTypes require a real CookieJarSettings because favicon
+ // and save-as requests must send cookies. Anything else should not
+ // send/receive cookies.
+ if (aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON ||
+ aContentPolicyType == nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
+ return aIsPrivate ? CookieJarSettings::Create(CookieJarSettings::ePrivate,
+ shouldResistFingerprinting)
+ : CookieJarSettings::Create(CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+ }
+
+ return CookieJarSettings::GetBlockingAll(shouldResistFingerprinting);
+}
+
+} // namespace
+
+NS_IMETHODIMP
+LoadInfo::GetCookieJarSettings(nsICookieJarSettings** aCookieJarSettings) {
+ if (!mCookieJarSettings) {
+ bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
+ nsCOMPtr<nsIPrincipal> loadingPrincipal;
+ Unused << this->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal));
+ bool shouldResistFingerprinting =
+ nsContentUtils::ShouldResistFingerprinting_dangerous(
+ loadingPrincipal,
+ "CookieJarSettings can't exist yet, we're creating it",
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ mCookieJarSettings = CreateCookieJarSettings(
+ mInternalContentPolicyType, isPrivate, shouldResistFingerprinting);
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings = mCookieJarSettings;
+ cookieJarSettings.forget(aCookieJarSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetCookieJarSettings(nsICookieJarSettings* aCookieJarSettings) {
+ MOZ_ASSERT(aCookieJarSettings);
+ // We allow the overwrite of CookieJarSettings.
+ mCookieJarSettings = aCookieJarSettings;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetStoragePermission(
+ nsILoadInfo::StoragePermissionState* aStoragePermission) {
+ *aStoragePermission = mStoragePermission;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetStoragePermission(
+ nsILoadInfo::StoragePermissionState aStoragePermission) {
+ mStoragePermission = aStoragePermission;
+ return NS_OK;
+}
+
+const Maybe<RFPTarget>& LoadInfo::GetOverriddenFingerprintingSettings() {
+#ifdef DEBUG
+ RefPtr<BrowsingContext> browsingContext;
+ GetTargetBrowsingContext(getter_AddRefs(browsingContext));
+
+ // Exclude this check if the target browsing context is for the parent
+ // process.
+ MOZ_ASSERT_IF(XRE_IsParentProcess() && browsingContext &&
+ !browsingContext->IsInProcess(),
+ mOverriddenFingerprintingSettingsIsSet);
+#endif
+ return mOverriddenFingerprintingSettings;
+}
+
+void LoadInfo::SetOverriddenFingerprintingSettings(RFPTarget aTargets) {
+ mOverriddenFingerprintingSettings.reset();
+ mOverriddenFingerprintingSettings.emplace(aTargets);
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsMetaRefresh(bool* aIsMetaRefresh) {
+ *aIsMetaRefresh = mIsMetaRefresh;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsMetaRefresh(bool aIsMetaRefresh) {
+ mIsMetaRefresh = aIsMetaRefresh;
+ return NS_OK;
+}
+
+void LoadInfo::SetIncludeCookiesSecFlag() {
+ MOZ_ASSERT((mSecurityFlags & sCookiePolicyMask) ==
+ nsILoadInfo::SEC_COOKIES_DEFAULT);
+ mSecurityFlags =
+ (mSecurityFlags & ~sCookiePolicyMask) | nsILoadInfo::SEC_COOKIES_INCLUDE;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal) {
+ *aInheritPrincipal =
+ (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal) {
+ *aInheritPrincipal =
+ (mSecurityFlags &
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed) {
+ *aLoadingSandboxed = (mSandboxFlags & SANDBOXED_ORIGIN);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAboutBlankInherits(bool* aResult) {
+ *aResult = (mSecurityFlags & nsILoadInfo::SEC_ABOUT_BLANK_INHERITS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAllowChrome(bool* aResult) {
+ *aResult = (mSecurityFlags & nsILoadInfo::SEC_ALLOW_CHROME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetDisallowScript(bool* aResult) {
+ *aResult = (mSecurityFlags & nsILoadInfo::SEC_DISALLOW_SCRIPT);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetDontFollowRedirects(bool* aResult) {
+ *aResult = (mSecurityFlags & nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadErrorPage(bool* aResult) {
+ *aResult = (mSecurityFlags & nsILoadInfo::SEC_LOAD_ERROR_PAGE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsFormSubmission(bool* aResult) {
+ *aResult = mIsFormSubmission;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsFormSubmission(bool aValue) {
+ mIsFormSubmission = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSendCSPViolationEvents(bool* aResult) {
+ *aResult = mSendCSPViolationEvents;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetSendCSPViolationEvents(bool aValue) {
+ mSendCSPViolationEvents = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult) {
+ // We have to use nsContentPolicyType because ExtContentPolicyType is not
+ // visible from xpidl.
+ *aResult = static_cast<nsContentPolicyType>(
+ nsContentUtils::InternalContentPolicyTypeToExternal(
+ mInternalContentPolicyType));
+ return NS_OK;
+}
+
+nsContentPolicyType LoadInfo::InternalContentPolicyType() {
+ return mInternalContentPolicyType;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBlockAllMixedContent(bool* aResult) {
+ *aResult = mBlockAllMixedContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetUpgradeInsecureRequests(bool* aResult) {
+ *aResult = mUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBrowserUpgradeInsecureRequests(bool* aResult) {
+ *aResult = mBrowserUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBrowserDidUpgradeInsecureRequests(bool* aResult) {
+ *aResult = mBrowserDidUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBrowserWouldUpgradeInsecureRequests(bool* aResult) {
+ *aResult = mBrowserWouldUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI) {
+ MOZ_ASSERT(!mForceAllowDataURI ||
+ mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "can only allow data URI navigation for TYPE_DOCUMENT");
+ mForceAllowDataURI = aForceAllowDataURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceAllowDataURI(bool* aForceAllowDataURI) {
+ *aForceAllowDataURI = mForceAllowDataURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetAllowInsecureRedirectToDataURI(
+ bool aAllowInsecureRedirectToDataURI) {
+ mAllowInsecureRedirectToDataURI = aAllowInsecureRedirectToDataURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAllowInsecureRedirectToDataURI(
+ bool* aAllowInsecureRedirectToDataURI) {
+ *aAllowInsecureRedirectToDataURI = mAllowInsecureRedirectToDataURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetSkipContentPolicyCheckForWebRequest(bool aSkip) {
+ mSkipContentPolicyCheckForWebRequest = aSkip;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSkipContentPolicyCheckForWebRequest(bool* aSkip) {
+ *aSkip = mSkipContentPolicyCheckForWebRequest;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetOriginalFrameSrcLoad(bool aOriginalFrameSrcLoad) {
+ mOriginalFrameSrcLoad = aOriginalFrameSrcLoad;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetOriginalFrameSrcLoad(bool* aOriginalFrameSrcLoad) {
+ *aOriginalFrameSrcLoad = mOriginalFrameSrcLoad;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForceInheritPrincipalDropped(bool* aResult) {
+ *aResult = mForceInheritPrincipalDropped;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetInnerWindowID(uint64_t* aResult) {
+ *aResult = mInnerWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBrowsingContextID(uint64_t* aResult) {
+ *aResult = mBrowsingContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetWorkerAssociatedBrowsingContextID(uint64_t* aResult) {
+ *aResult = mWorkerAssociatedBrowsingContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetWorkerAssociatedBrowsingContextID(uint64_t aID) {
+ mWorkerAssociatedBrowsingContextID = aID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetFrameBrowsingContextID(uint64_t* aResult) {
+ *aResult = mFrameBrowsingContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTargetBrowsingContextID(uint64_t* aResult) {
+ return (nsILoadInfo::GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT)
+ ? GetFrameBrowsingContextID(aResult)
+ : GetBrowsingContextID(aResult);
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBrowsingContext(dom::BrowsingContext** aResult) {
+ *aResult = BrowsingContext::Get(mBrowsingContextID).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetWorkerAssociatedBrowsingContext(dom::BrowsingContext** aResult) {
+ *aResult = BrowsingContext::Get(mWorkerAssociatedBrowsingContextID).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetFrameBrowsingContext(dom::BrowsingContext** aResult) {
+ *aResult = BrowsingContext::Get(mFrameBrowsingContextID).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTargetBrowsingContext(dom::BrowsingContext** aResult) {
+ uint64_t targetBrowsingContextID = 0;
+ MOZ_ALWAYS_SUCCEEDS(GetTargetBrowsingContextID(&targetBrowsingContextID));
+ *aResult = BrowsingContext::Get(targetBrowsingContextID).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::ResetPrincipalToInheritToNullPrincipal() {
+ // take the originAttributes from the LoadInfo and create
+ // a new NullPrincipal using those origin attributes.
+ nsCOMPtr<nsIPrincipal> newNullPrincipal =
+ NullPrincipal::Create(mOriginAttributes);
+
+ mPrincipalToInherit = newNullPrincipal;
+
+ // setting SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER will overrule
+ // any non null owner set on the channel and will return the principal
+ // form the loadinfo instead.
+ mSecurityFlags |= SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mOriginAttributes = attrs;
+ return NS_OK;
+}
+
+nsresult LoadInfo::GetOriginAttributes(
+ mozilla::OriginAttributes* aOriginAttributes) {
+ NS_ENSURE_ARG(aOriginAttributes);
+ *aOriginAttributes = mOriginAttributes;
+ return NS_OK;
+}
+
+nsresult LoadInfo::SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ mOriginAttributes = aOriginAttributes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone) {
+ // Indicates whether the channel was ever evaluated by the
+ // ContentSecurityManager. Once set to true, this flag must
+ // remain true throughout the lifetime of the channel.
+ // Setting it to anything else than true will be discarded.
+ MOZ_ASSERT(aInitialSecurityCheckDone,
+ "aInitialSecurityCheckDone must be true");
+ mInitialSecurityCheckDone =
+ mInitialSecurityCheckDone || aInitialSecurityCheckDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetInitialSecurityCheckDone(bool* aResult) {
+ *aResult = mInitialSecurityCheckDone;
+ return NS_OK;
+}
+
+// To prevent unintenional credential and information leaks in content
+// processes we can use this function to truncate a Principal's URI as much as
+// possible.
+already_AddRefed<nsIPrincipal> CreateTruncatedPrincipal(
+ nsIPrincipal* aPrincipal) {
+ nsCOMPtr<nsIPrincipal> truncatedPrincipal;
+ // System Principal URIs don't need to be truncated as they don't contain any
+ // sensitive browsing history information.
+ if (aPrincipal->IsSystemPrincipal()) {
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+ }
+
+ // Content Principal URIs are the main location of the information we need to
+ // truncate.
+ if (aPrincipal->GetIsContentPrincipal()) {
+ // Certain URIs (chrome, resource, about, jar) don't need to be truncated
+ // as they should be free of any sensitive user browsing history.
+ if (aPrincipal->SchemeIs("chrome") || aPrincipal->SchemeIs("resource") ||
+ aPrincipal->SchemeIs("about") || aPrincipal->SchemeIs("jar")) {
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+ }
+
+ // Different parts of the URI are preserved due to being vital to the
+ // browser's operation.
+ // Scheme for differentiating between different types of URIs and how to
+ // truncate them and later on utilize them.
+ // Host and Port to retain the redirect chain's core functionality.
+ // Path would ideally be removed but needs to be retained to ensure that
+ // http/https redirect loops can be detected.
+ // The entirety of the Query String, Reference Fragment, and User Info
+ // subcomponents must be stripped to avoid leaking Oauth tokens, user
+ // identifiers, and similar bits of information that these subcomponents may
+ // contain.
+ nsAutoCString scheme;
+ nsAutoCString separator("://");
+ nsAutoCString hostPort;
+ nsAutoCString path;
+ nsAutoCString uriString("");
+ if (aPrincipal->SchemeIs("view-source")) {
+ // The path portion of the view-source URI will be the URI whose source is
+ // being viewed, so we create a new URI object with a truncated form of
+ // the path and append the view-source scheme to the front again.
+ nsAutoCString viewSourcePath;
+ aPrincipal->GetFilePath(viewSourcePath);
+
+ nsCOMPtr<nsIURI> nestedURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(nestedURI), viewSourcePath);
+
+ if (NS_FAILED(rv)) {
+ // Since the path here should be an already validated URI this should
+ // never happen.
+ NS_WARNING(viewSourcePath.get());
+ MOZ_ASSERT(false,
+ "Failed to create truncated form of URI with NS_NewURI.");
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+ }
+
+ nestedURI->GetScheme(scheme);
+ nestedURI->GetHostPort(hostPort);
+ nestedURI->GetFilePath(path);
+ uriString += "view-source:";
+ } else {
+ aPrincipal->GetScheme(scheme);
+ aPrincipal->GetHostPort(hostPort);
+ aPrincipal->GetFilePath(path);
+ }
+ uriString += scheme + separator + hostPort + path;
+
+ nsCOMPtr<nsIURI> truncatedURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(truncatedURI), uriString);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(uriString.get());
+ MOZ_ASSERT(false,
+ "Failed to create truncated form of URI with NS_NewURI.");
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+ }
+
+ return BasePrincipal::CreateContentPrincipal(
+ truncatedURI, aPrincipal->OriginAttributesRef());
+ }
+
+ // Null Principal Precursor URIs can also contain information that needs to
+ // be truncated.
+ if (aPrincipal->GetIsNullPrincipal()) {
+ nsCOMPtr<nsIPrincipal> precursorPrincipal =
+ aPrincipal->GetPrecursorPrincipal();
+ // If there is no precursor then nothing needs to be truncated.
+ if (!precursorPrincipal) {
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+ }
+
+ // Otherwise we return a new Null Principal with the original's Origin
+ // Attributes and a truncated version of the original's precursor URI.
+ nsCOMPtr<nsIPrincipal> truncatedPrecursor =
+ CreateTruncatedPrincipal(precursorPrincipal);
+ return NullPrincipal::CreateWithInheritedAttributes(truncatedPrecursor);
+ }
+
+ // Expanded Principals shouldn't contain sensitive information but their
+ // allowlists might so we truncate that information here.
+ if (aPrincipal->GetIsExpandedPrincipal()) {
+ nsTArray<nsCOMPtr<nsIPrincipal>> truncatedAllowList;
+
+ for (const auto& allowedPrincipal : BasePrincipal::Cast(aPrincipal)
+ ->As<ExpandedPrincipal>()
+ ->AllowList()) {
+ nsCOMPtr<nsIPrincipal> truncatedPrincipal =
+ CreateTruncatedPrincipal(allowedPrincipal);
+
+ truncatedAllowList.AppendElement(truncatedPrincipal);
+ }
+
+ return ExpandedPrincipal::Create(truncatedAllowList,
+ aPrincipal->OriginAttributesRef());
+ }
+
+ // If we hit this assertion we need to update this function to add the
+ // Principals and URIs seen as new corner cases to handle.
+ MOZ_ASSERT(false, "Unhandled Principal or URI type encountered.");
+
+ truncatedPrincipal = aPrincipal;
+ return truncatedPrincipal.forget();
+}
+
+NS_IMETHODIMP
+LoadInfo::AppendRedirectHistoryEntry(nsIChannel* aChannel,
+ bool aIsInternalRedirect) {
+ NS_ENSURE_ARG(aChannel);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIPrincipal> uriPrincipal;
+ nsIScriptSecurityManager* sm = nsContentUtils::GetSecurityManager();
+ sm->GetChannelURIPrincipal(aChannel, getter_AddRefs(uriPrincipal));
+
+ nsCOMPtr<nsIURI> referrer;
+ nsCString remoteAddress;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+ if (httpChannel) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ Unused << httpChannel->GetReferrerInfo(getter_AddRefs(referrerInfo));
+ if (referrerInfo) {
+ referrer = referrerInfo->GetComputedReferrer();
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> intChannel(do_QueryInterface(aChannel));
+ if (intChannel) {
+ Unused << intChannel->GetRemoteAddress(remoteAddress);
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> truncatedPrincipal =
+ CreateTruncatedPrincipal(uriPrincipal);
+
+ nsCOMPtr<nsIRedirectHistoryEntry> entry =
+ new nsRedirectHistoryEntry(truncatedPrincipal, referrer, remoteAddress);
+
+ mRedirectChainIncludingInternalRedirects.AppendElement(entry);
+ if (!aIsInternalRedirect) {
+ mRedirectChain.AppendElement(entry);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetRedirects(JSContext* aCx, JS::MutableHandle<JS::Value> aRedirects,
+ const RedirectHistoryArray& aArray) {
+ JS::Rooted<JSObject*> redirects(aCx,
+ JS::NewArrayObject(aCx, aArray.Length()));
+ NS_ENSURE_TRUE(redirects, NS_ERROR_OUT_OF_MEMORY);
+
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
+
+ for (size_t idx = 0; idx < aArray.Length(); idx++) {
+ JS::Rooted<JSObject*> jsobj(aCx);
+ nsresult rv =
+ xpc->WrapNative(aCx, global, aArray[idx],
+ NS_GET_IID(nsIRedirectHistoryEntry), jsobj.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(jsobj);
+
+ bool rc = JS_DefineElement(aCx, redirects, idx, jsobj, JSPROP_ENUMERATE);
+ NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
+ }
+
+ aRedirects.setObject(*redirects);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetRedirectChainIncludingInternalRedirects(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aChain) {
+ return GetRedirects(aCx, aChain, mRedirectChainIncludingInternalRedirects);
+}
+
+const RedirectHistoryArray&
+LoadInfo::RedirectChainIncludingInternalRedirects() {
+ return mRedirectChainIncludingInternalRedirects;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetRedirectChain(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aChain) {
+ return GetRedirects(aCx, aChain, mRedirectChain);
+}
+
+const RedirectHistoryArray& LoadInfo::RedirectChain() { return mRedirectChain; }
+
+const nsTArray<nsCOMPtr<nsIPrincipal>>& LoadInfo::AncestorPrincipals() {
+ return mAncestorPrincipals;
+}
+
+const nsTArray<uint64_t>& LoadInfo::AncestorBrowsingContextIDs() {
+ return mAncestorBrowsingContextIDs;
+}
+
+void LoadInfo::SetCorsPreflightInfo(const nsTArray<nsCString>& aHeaders,
+ bool aForcePreflight) {
+ MOZ_ASSERT(GetSecurityMode() ==
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT);
+ MOZ_ASSERT(!mInitialSecurityCheckDone);
+ mCorsUnsafeHeaders = aHeaders.Clone();
+ mForcePreflight = aForcePreflight;
+}
+
+const nsTArray<nsCString>& LoadInfo::CorsUnsafeHeaders() {
+ return mCorsUnsafeHeaders;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetForcePreflight(bool* aForcePreflight) {
+ *aForcePreflight = mForcePreflight;
+ return NS_OK;
+}
+
+void LoadInfo::SetIsPreflight() {
+ MOZ_ASSERT(GetSecurityMode() ==
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT);
+ MOZ_ASSERT(!mInitialSecurityCheckDone);
+ mIsPreflight = true;
+}
+
+void LoadInfo::SetUpgradeInsecureRequests(bool aValue) {
+ mUpgradeInsecureRequests = aValue;
+}
+
+void LoadInfo::SetBrowserUpgradeInsecureRequests() {
+ mBrowserUpgradeInsecureRequests = true;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetBrowserDidUpgradeInsecureRequests(
+ bool aBrowserDidUpgradeInsecureRequests) {
+ mBrowserDidUpgradeInsecureRequests = aBrowserDidUpgradeInsecureRequests;
+ return NS_OK;
+}
+
+void LoadInfo::SetBrowserWouldUpgradeInsecureRequests() {
+ mBrowserWouldUpgradeInsecureRequests = true;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsPreflight(bool* aIsPreflight) {
+ *aIsPreflight = mIsPreflight;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetLoadTriggeredFromExternal(bool aLoadTriggeredFromExternal) {
+ MOZ_ASSERT(!aLoadTriggeredFromExternal ||
+ mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "can only set load triggered from external for TYPE_DOCUMENT");
+ mLoadTriggeredFromExternal = aLoadTriggeredFromExternal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadTriggeredFromExternal(bool* aLoadTriggeredFromExternal) {
+ *aLoadTriggeredFromExternal = mLoadTriggeredFromExternal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetServiceWorkerTaintingSynthesized(
+ bool* aServiceWorkerTaintingSynthesized) {
+ MOZ_ASSERT(aServiceWorkerTaintingSynthesized);
+ *aServiceWorkerTaintingSynthesized = mServiceWorkerTaintingSynthesized;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetTainting(uint32_t* aTaintingOut) {
+ MOZ_ASSERT(aTaintingOut);
+ *aTaintingOut = static_cast<uint32_t>(mTainting);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::MaybeIncreaseTainting(uint32_t aTainting) {
+ NS_ENSURE_ARG(aTainting <= TAINTING_OPAQUE);
+
+ // Skip if the tainting has been set by the service worker.
+ if (mServiceWorkerTaintingSynthesized) {
+ return NS_OK;
+ }
+
+ LoadTainting tainting = static_cast<LoadTainting>(aTainting);
+ if (tainting > mTainting) {
+ mTainting = tainting;
+ }
+ return NS_OK;
+}
+
+void LoadInfo::SynthesizeServiceWorkerTainting(LoadTainting aTainting) {
+ MOZ_DIAGNOSTIC_ASSERT(aTainting <= LoadTainting::Opaque);
+ mTainting = aTainting;
+
+ // Flag to prevent the tainting from being increased.
+ mServiceWorkerTaintingSynthesized = true;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetDocumentHasUserInteracted(bool* aDocumentHasUserInteracted) {
+ MOZ_ASSERT(aDocumentHasUserInteracted);
+ *aDocumentHasUserInteracted = mDocumentHasUserInteracted;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetDocumentHasUserInteracted(bool aDocumentHasUserInteracted) {
+ mDocumentHasUserInteracted = aDocumentHasUserInteracted;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ bool* aValue) {
+ MOZ_ASSERT(aValue);
+ *aValue = mAllowListFutureDocumentsCreatedFromThisRedirectChain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetAllowListFutureDocumentsCreatedFromThisRedirectChain(bool aValue) {
+ mAllowListFutureDocumentsCreatedFromThisRedirectChain = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetNeedForCheckingAntiTrackingHeuristic(bool* aValue) {
+ MOZ_ASSERT(aValue);
+ *aValue = mNeedForCheckingAntiTrackingHeuristic;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetNeedForCheckingAntiTrackingHeuristic(bool aValue) {
+ mNeedForCheckingAntiTrackingHeuristic = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetCspNonce(nsAString& aCspNonce) {
+ aCspNonce = mCspNonce;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetCspNonce(const nsAString& aCspNonce) {
+ MOZ_ASSERT(!mInitialSecurityCheckDone,
+ "setting the nonce is only allowed before any sec checks");
+ mCspNonce = aCspNonce;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIntegrityMetadata(nsAString& aIntegrityMetadata) {
+ aIntegrityMetadata = mIntegrityMetadata;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) {
+ MOZ_ASSERT(!mInitialSecurityCheckDone,
+ "setting the nonce is only allowed before any sec checks");
+ mIntegrityMetadata = aIntegrityMetadata;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) {
+ *aSkipContentSniffing = mSkipContentSniffing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) {
+ mSkipContentSniffing = aSkipContentSniffing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetHttpsOnlyStatus(uint32_t* aHttpsOnlyStatus) {
+ *aHttpsOnlyStatus = mHttpsOnlyStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHttpsOnlyStatus(uint32_t aHttpsOnlyStatus) {
+ mHttpsOnlyStatus = aHttpsOnlyStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetHstsStatus(bool* aHstsStatus) {
+ *aHstsStatus = mHstsStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHstsStatus(bool aHstsStatus) {
+ mHstsStatus = aHstsStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetHasValidUserGestureActivation(
+ bool* aHasValidUserGestureActivation) {
+ *aHasValidUserGestureActivation = mHasValidUserGestureActivation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHasValidUserGestureActivation(
+ bool aHasValidUserGestureActivation) {
+ mHasValidUserGestureActivation = aHasValidUserGestureActivation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetAllowDeprecatedSystemRequests(
+ bool* aAllowDeprecatedSystemRequests) {
+ *aAllowDeprecatedSystemRequests = mAllowDeprecatedSystemRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetAllowDeprecatedSystemRequests(
+ bool aAllowDeprecatedSystemRequests) {
+ mAllowDeprecatedSystemRequests = aAllowDeprecatedSystemRequests;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsUserTriggeredSave(bool* aIsUserTriggeredSave) {
+ *aIsUserTriggeredSave =
+ mIsUserTriggeredSave ||
+ mInternalContentPolicyType == nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsUserTriggeredSave(bool aIsUserTriggeredSave) {
+ mIsUserTriggeredSave = aIsUserTriggeredSave;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsInDevToolsContext(bool* aIsInDevToolsContext) {
+ *aIsInDevToolsContext = mIsInDevToolsContext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsInDevToolsContext(bool aIsInDevToolsContext) {
+ mIsInDevToolsContext = aIsInDevToolsContext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetParserCreatedScript(bool* aParserCreatedScript) {
+ *aParserCreatedScript = mParserCreatedScript;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetParserCreatedScript(bool aParserCreatedScript) {
+ mParserCreatedScript = aParserCreatedScript;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsTopLevelLoad(bool* aResult) {
+ RefPtr<dom::BrowsingContext> bc;
+ GetTargetBrowsingContext(getter_AddRefs(bc));
+ *aResult = !bc || bc->IsTop();
+ return NS_OK;
+}
+
+void LoadInfo::SetIsFromProcessingFrameAttributes() {
+ mIsFromProcessingFrameAttributes = true;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsFromProcessingFrameAttributes(
+ bool* aIsFromProcessingFrameAttributes) {
+ MOZ_ASSERT(aIsFromProcessingFrameAttributes);
+ *aIsFromProcessingFrameAttributes = mIsFromProcessingFrameAttributes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsMediaRequest(bool aIsMediaRequest) {
+ mIsMediaRequest = aIsMediaRequest;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsMediaRequest(bool* aIsMediaRequest) {
+ MOZ_ASSERT(aIsMediaRequest);
+ *aIsMediaRequest = mIsMediaRequest;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsMediaInitialRequest(bool aIsMediaInitialRequest) {
+ mIsMediaInitialRequest = aIsMediaInitialRequest;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsMediaInitialRequest(bool* aIsMediaInitialRequest) {
+ MOZ_ASSERT(aIsMediaInitialRequest);
+ *aIsMediaInitialRequest = mIsMediaInitialRequest;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsFromObjectOrEmbed(bool aIsFromObjectOrEmbed) {
+ mIsFromObjectOrEmbed = aIsFromObjectOrEmbed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsFromObjectOrEmbed(bool* aIsFromObjectOrEmbed) {
+ MOZ_ASSERT(aIsFromObjectOrEmbed);
+ *aIsFromObjectOrEmbed = mIsFromObjectOrEmbed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetShouldSkipCheckForBrokenURLOrZeroSized(
+ bool* aShouldSkipCheckForBrokenURLOrZeroSized) {
+ MOZ_ASSERT(aShouldSkipCheckForBrokenURLOrZeroSized);
+ *aShouldSkipCheckForBrokenURLOrZeroSized = mSkipCheckForBrokenURLOrZeroSized;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetResultPrincipalURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mResultPrincipalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetResultPrincipalURI(nsIURI* aURI) {
+ mResultPrincipalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetChannelCreationOriginalURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mChannelCreationOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetChannelCreationOriginalURI(nsIURI* aURI) {
+ mChannelCreationOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetRequestBlockingReason(uint32_t aReason) {
+ mRequestBlockingReason = aReason;
+ return NS_OK;
+}
+NS_IMETHODIMP
+LoadInfo::GetRequestBlockingReason(uint32_t* aReason) {
+ *aReason = mRequestBlockingReason;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetUnstrippedURI(nsIURI** aURI) {
+ *aURI = do_AddRef(mUnstrippedURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetUnstrippedURI(nsIURI* aURI) {
+ mUnstrippedURI = aURI;
+ return NS_OK;
+}
+
+void LoadInfo::SetClientInfo(const ClientInfo& aClientInfo) {
+ mClientInfo.emplace(aClientInfo);
+}
+
+const Maybe<ClientInfo>& LoadInfo::GetClientInfo() { return mClientInfo; }
+
+void LoadInfo::GiveReservedClientSource(
+ UniquePtr<ClientSource>&& aClientSource) {
+ MOZ_DIAGNOSTIC_ASSERT(aClientSource);
+ mReservedClientSource = std::move(aClientSource);
+ SetReservedClientInfo(mReservedClientSource->Info());
+}
+
+UniquePtr<ClientSource> LoadInfo::TakeReservedClientSource() {
+ if (mReservedClientSource) {
+ // If the reserved ClientInfo was set due to a ClientSource being present,
+ // then clear that info object when the ClientSource is taken.
+ mReservedClientInfo.reset();
+ }
+ return std::move(mReservedClientSource);
+}
+
+void LoadInfo::SetReservedClientInfo(const ClientInfo& aClientInfo) {
+ MOZ_DIAGNOSTIC_ASSERT(mInitialClientInfo.isNothing());
+ // Treat assignments of the same value as a no-op. The emplace below
+ // will normally assert when overwriting an existing value.
+ if (mReservedClientInfo.isSome()) {
+ if (mReservedClientInfo.ref() == aClientInfo) {
+ return;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(false, "mReservedClientInfo already set");
+ mReservedClientInfo.reset();
+ }
+ mReservedClientInfo.emplace(aClientInfo);
+}
+
+void LoadInfo::OverrideReservedClientInfoInParent(
+ const ClientInfo& aClientInfo) {
+ // This should only be called to handle redirects in the parent process.
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ mInitialClientInfo.reset();
+ mReservedClientInfo.reset();
+ mReservedClientInfo.emplace(aClientInfo);
+}
+
+const Maybe<ClientInfo>& LoadInfo::GetReservedClientInfo() {
+ return mReservedClientInfo;
+}
+
+void LoadInfo::SetInitialClientInfo(const ClientInfo& aClientInfo) {
+ MOZ_DIAGNOSTIC_ASSERT(!mReservedClientSource);
+ MOZ_DIAGNOSTIC_ASSERT(mReservedClientInfo.isNothing());
+ // Treat assignments of the same value as a no-op. The emplace below
+ // will normally assert when overwriting an existing value.
+ if (mInitialClientInfo.isSome() && mInitialClientInfo.ref() == aClientInfo) {
+ return;
+ }
+ mInitialClientInfo.emplace(aClientInfo);
+}
+
+const Maybe<ClientInfo>& LoadInfo::GetInitialClientInfo() {
+ return mInitialClientInfo;
+}
+
+void LoadInfo::SetController(const ServiceWorkerDescriptor& aServiceWorker) {
+ mController.emplace(aServiceWorker);
+}
+
+void LoadInfo::ClearController() { mController.reset(); }
+
+const Maybe<ServiceWorkerDescriptor>& LoadInfo::GetController() {
+ return mController;
+}
+
+void LoadInfo::SetPerformanceStorage(PerformanceStorage* aPerformanceStorage) {
+ mPerformanceStorage = aPerformanceStorage;
+}
+
+PerformanceStorage* LoadInfo::GetPerformanceStorage() {
+ if (mPerformanceStorage) {
+ return mPerformanceStorage;
+ }
+
+ auto* innerWindow = nsGlobalWindowInner::GetInnerWindowWithId(mInnerWindowID);
+ if (!innerWindow) {
+ return nullptr;
+ }
+
+ if (!TriggeringPrincipal()->Equals(innerWindow->GetPrincipal())) {
+ return nullptr;
+ }
+
+ if (nsILoadInfo::GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT &&
+ !GetIsFromProcessingFrameAttributes()) {
+ // We only report loads caused by processing the attributes of the
+ // browsing context container.
+ return nullptr;
+ }
+
+ mozilla::dom::Performance* performance = innerWindow->GetPerformance();
+ if (!performance) {
+ return nullptr;
+ }
+
+ return performance->AsPerformanceStorage();
+}
+
+NS_IMETHODIMP
+LoadInfo::GetCspEventListener(nsICSPEventListener** aCSPEventListener) {
+ *aCSPEventListener = do_AddRef(mCSPEventListener).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener) {
+ mCSPEventListener = aCSPEventListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetInternalContentPolicyType(nsContentPolicyType* aResult) {
+ *aResult = mInternalContentPolicyType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadingEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) {
+ *aOutPolicy = mLoadingEmbedderPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetLoadingEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
+ mLoadingEmbedderPolicy = aPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ bool* aIsOriginTrialCoepCredentiallessEnabledForTopLevel) {
+ *aIsOriginTrialCoepCredentiallessEnabledForTopLevel =
+ mIsOriginTrialCoepCredentiallessEnabledForTopLevel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel) {
+ mIsOriginTrialCoepCredentiallessEnabledForTopLevel =
+ aIsOriginTrialCoepCredentiallessEnabledForTopLevel;
+ return NS_OK;
+}
+
+already_AddRefed<nsIContentSecurityPolicy> LoadInfo::GetCsp() {
+ // Before querying the CSP from the client we have to check if the
+ // triggeringPrincipal originates from an addon and potentially
+ // overrides the CSP stored within the client.
+ if (mLoadingPrincipal && BasePrincipal::Cast(mTriggeringPrincipal)
+ ->OverridesCSP(mLoadingPrincipal)) {
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(mTriggeringPrincipal);
+ nsCOMPtr<nsIContentSecurityPolicy> addonCSP;
+ if (ep) {
+ addonCSP = ep->GetCsp();
+ }
+ return addonCSP.forget();
+ }
+
+ if (mClientInfo.isNothing()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext);
+ RefPtr<Document> doc = node ? node->OwnerDoc() : nullptr;
+
+ // If the client is of type window, then we return the cached CSP
+ // stored on the document instead of having to deserialize the CSP
+ // from the ClientInfo.
+ if (doc && mClientInfo->Type() == ClientType::Window) {
+ nsCOMPtr<nsIContentSecurityPolicy> docCSP = doc->GetCsp();
+ return docCSP.forget();
+ }
+
+ Maybe<mozilla::ipc::CSPInfo> cspInfo = mClientInfo->GetCspInfo();
+ if (cspInfo.isNothing()) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIContentSecurityPolicy> clientCSP =
+ CSPInfoToCSP(cspInfo.ref(), doc);
+ return clientCSP.forget();
+}
+
+already_AddRefed<nsIContentSecurityPolicy> LoadInfo::GetPreloadCsp() {
+ if (mClientInfo.isNothing()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsINode> node = do_QueryReferent(mLoadingContext);
+ RefPtr<Document> doc = node ? node->OwnerDoc() : nullptr;
+
+ // If the client is of type window, then we return the cached CSP
+ // stored on the document instead of having to deserialize the CSP
+ // from the ClientInfo.
+ if (doc && mClientInfo->Type() == ClientType::Window) {
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = doc->GetPreloadCsp();
+ return preloadCsp.forget();
+ }
+
+ Maybe<mozilla::ipc::CSPInfo> cspInfo = mClientInfo->GetPreloadCspInfo();
+ if (cspInfo.isNothing()) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCSP =
+ CSPInfoToCSP(cspInfo.ref(), doc);
+ return preloadCSP.forget();
+}
+
+already_AddRefed<nsIContentSecurityPolicy> LoadInfo::GetCspToInherit() {
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = mCspToInherit;
+ return cspToInherit.forget();
+}
+
+nsIInterceptionInfo* LoadInfo::InterceptionInfo() { return mInterceptionInfo; }
+
+void LoadInfo::SetInterceptionInfo(nsIInterceptionInfo* aInfo) {
+ mInterceptionInfo = aInfo;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetHasInjectedCookieForCookieBannerHandling(
+ bool* aHasInjectedCookieForCookieBannerHandling) {
+ *aHasInjectedCookieForCookieBannerHandling =
+ mHasInjectedCookieForCookieBannerHandling;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHasInjectedCookieForCookieBannerHandling(
+ bool aHasInjectedCookieForCookieBannerHandling) {
+ mHasInjectedCookieForCookieBannerHandling =
+ aHasInjectedCookieForCookieBannerHandling;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetWasSchemelessInput(bool* aWasSchemelessInput) {
+ *aWasSchemelessInput = mWasSchemelessInput;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) {
+ mWasSchemelessInput = aWasSchemelessInput;
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h
new file mode 100644
index 0000000000..a8631b09b2
--- /dev/null
+++ b/netwerk/base/LoadInfo.h
@@ -0,0 +1,413 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LoadInfo_h
+#define mozilla_LoadInfo_h
+
+#include "nsIContentSecurityPolicy.h"
+#include "nsIInterceptionInfo.h"
+#include "nsILoadInfo.h"
+#include "nsIPrincipal.h"
+#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
+#include "nsIURI.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+
+class nsDocShell;
+class nsICookieJarSettings;
+class nsINode;
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+
+namespace dom {
+class PerformanceStorage;
+class XMLHttpRequestMainThread;
+class CanonicalBrowsingContext;
+class WindowGlobalParent;
+} // namespace dom
+
+namespace net {
+class EarlyHintPreloader;
+class LoadInfoArgs;
+class LoadInfo;
+} // namespace net
+
+namespace ipc {
+// we have to forward declare that function so we can use it as a friend.
+nsresult LoadInfoArgsToLoadInfo(const mozilla::net::LoadInfoArgs& aLoadInfoArgs,
+ const nsACString& aOriginRemoteType,
+ nsINode* aCspToInheritLoadingContext,
+ net::LoadInfo** outLoadInfo);
+} // namespace ipc
+
+namespace net {
+
+using RedirectHistoryArray = nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>;
+
+/**
+ * Class that provides an nsILoadInfo implementation.
+ */
+class LoadInfo final : public nsILoadInfo {
+ template <typename T, typename... Args>
+ friend already_AddRefed<T> mozilla::MakeAndAddRef(Args&&... aArgs);
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSILOADINFO
+
+ // Used for TYPE_DOCUMENT load.
+ static already_AddRefed<LoadInfo> CreateForDocument(
+ dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ const OriginAttributes& aOriginAttributes, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags);
+
+ // Used for TYPE_FRAME or TYPE_IFRAME load.
+ static already_AddRefed<LoadInfo> CreateForFrame(
+ dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags);
+
+ // Use for non-{TYPE_DOCUMENT|TYPE_FRAME|TYPE_IFRAME} load.
+ static already_AddRefed<LoadInfo> CreateForNonDocument(
+ dom::WindowGlobalParent* aParentWGP, nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags);
+
+ // aLoadingPrincipal MUST NOT BE NULL.
+ LoadInfo(nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsINode* aLoadingContext, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ const Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo =
+ Maybe<mozilla::dom::ClientInfo>(),
+ const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController =
+ Maybe<mozilla::dom::ServiceWorkerDescriptor>(),
+ uint32_t aSandboxFlags = 0,
+ bool aSkipCheckForBrokenURLOrZeroSized = 0);
+
+ // Constructor used for TYPE_DOCUMENT loads which have a different
+ // loadingContext than other loads. This ContextForTopLevelLoad is
+ // only used for content policy checks.
+ LoadInfo(nsPIDOMWindowOuter* aOuterWindow, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsISupports* aContextForTopLevelLoad, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags);
+
+ private:
+ // Use factory function CreateForDocument
+ // Used for TYPE_DOCUMENT load.
+ LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ const OriginAttributes& aOriginAttributes,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags);
+
+ // Use factory function CreateForFrame
+ // Used for TYPE_FRAME or TYPE_IFRAME load.
+ LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags);
+
+ // Used for loads initiated by DocumentLoadListener that are not TYPE_DOCUMENT
+ // | TYPE_FRAME | TYPE_FRAME.
+ LoadInfo(dom::WindowGlobalParent* aParentWGP,
+ nsIPrincipal* aTriggeringPrincipal,
+ const nsACString& aTriggeringRemoteType,
+ nsContentPolicyType aContentPolicyType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags);
+
+ public:
+ // Compute a list of ancestor principals and BrowsingContext IDs.
+ // See methods AncestorPrincipals and AncestorBrowsingContextIDs
+ // in nsILoadInfo.idl for details.
+ static void ComputeAncestors(
+ dom::CanonicalBrowsingContext* aBC,
+ nsTArray<nsCOMPtr<nsIPrincipal>>& aAncestorPrincipals,
+ nsTArray<uint64_t>& aBrowsingContextIDs);
+
+ // create an exact copy of the loadinfo
+ already_AddRefed<nsILoadInfo> Clone() const;
+
+ // hands off!!! don't use CloneWithNewSecFlags unless you know
+ // exactly what you are doing - it should only be used within
+ // nsBaseChannel::Redirect()
+ already_AddRefed<nsILoadInfo> CloneWithNewSecFlags(
+ nsSecurityFlags aSecurityFlags) const;
+ // creates a copy of the loadinfo which is appropriate to use for a
+ // separate request. I.e. not for a redirect or an inner channel, but
+ // when a separate request is made with the same security properties.
+ already_AddRefed<nsILoadInfo> CloneForNewRequest() const;
+
+ // The `nsContentPolicyType GetExternalContentPolicyType()` version in the
+ // base class is hidden by the implementation of
+ // `GetExternalContentPolicyType(nsContentPolicyType* aResult)` in
+ // LoadInfo.cpp. Explicit mark it visible.
+ using nsILoadInfo::GetExternalContentPolicyType;
+
+ void SetIsPreflight();
+ void SetUpgradeInsecureRequests(bool aValue);
+ void SetBrowserUpgradeInsecureRequests();
+ void SetBrowserWouldUpgradeInsecureRequests();
+ void SetIsFromProcessingFrameAttributes();
+
+ // Hands off from the cspToInherit functionality!
+ //
+ // For navigations, GetCSPToInherit returns what the spec calls the
+ // "request's client's global object's CSP list", or more precisely
+ // a snapshot of it taken when the navigation starts. For navigations
+ // that need to inherit their CSP, this is the right CSP to use for
+ // the new document. We need a way to transfer the CSP from the
+ // docshell (where the navigation starts) to the point where the new
+ // document is created and decides whether to inherit its CSP, and
+ // this is the mechanism we use for that.
+ //
+ // For example:
+ // A document with a CSP triggers a new top-level data: URI load.
+ // We pass the CSP of the document that triggered the load all the
+ // way to docshell. Within docshell we call SetCSPToInherit() on the
+ // loadinfo. Within Document::InitCSP() we check if the newly created
+ // document needs to inherit the CSP. If so, we call GetCSPToInherit()
+ // and set the inherited CSP as the CSP for the new document. Please
+ // note that any additonal Meta CSP in that document will be merged
+ // into that CSP. Any subresource loads within that document
+ // subesquently will receive the correct CSP by querying
+ // loadinfo->GetCsp() from that point on.
+ void SetCSPToInherit(nsIContentSecurityPolicy* aCspToInherit) {
+ mCspToInherit = aCspToInherit;
+ }
+
+ bool HasIsThirdPartyContextToTopWindowSet() {
+ return mIsThirdPartyContextToTopWindow.isSome();
+ }
+ void ClearIsThirdPartyContextToTopWindow() {
+ mIsThirdPartyContextToTopWindow.reset();
+ }
+
+#ifdef DEBUG
+ void MarkOverriddenFingerprintingSettingsAsSet() {
+ mOverriddenFingerprintingSettingsIsSet = true;
+ }
+#endif
+
+ private:
+ // private constructor that is only allowed to be called from within
+ // HttpChannelParent and FTPChannelParent declared as friends undeneath.
+ // In e10s we can not serialize nsINode, hence we store the innerWindowID.
+ // Please note that aRedirectChain uses swapElements.
+ LoadInfo(
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aTopLevelPrincipal,
+ nsIURI* aResultPrincipalURI, nsICookieJarSettings* aCookieJarSettings,
+ nsIContentSecurityPolicy* aCspToInherit,
+ const nsACString& aTriggeringRemoteType,
+ const nsID& aSandboxedNullPrincipalID,
+ const Maybe<mozilla::dom::ClientInfo>& aClientInfo,
+ const Maybe<mozilla::dom::ClientInfo>& aReservedClientInfo,
+ const Maybe<mozilla::dom::ClientInfo>& aInitialClientInfo,
+ const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags,
+ uint32_t aTriggeringSandboxFlags, uint64_t aTriggeringWindowId,
+ bool aTriggeringStorageAccess, nsContentPolicyType aContentPolicyType,
+ LoadTainting aTainting, bool aBlockAllMixedContent,
+ bool aUpgradeInsecureRequests, bool aBrowserUpgradeInsecureRequests,
+ bool aBrowserDidUpgradeInsecureRequests,
+ bool aBrowserWouldUpgradeInsecureRequests, bool aForceAllowDataURI,
+ bool aAllowInsecureRedirectToDataURI,
+ bool aSkipContentPolicyCheckForWebRequest, bool aOriginalFrameSrcLoad,
+ bool aForceInheritPrincipalDropped, uint64_t aInnerWindowID,
+ uint64_t aBrowsingContextID, uint64_t aFrameBrowsingContextID,
+ bool aInitialSecurityCheckDone, bool aIsThirdPartyContext,
+ const Maybe<bool>& aIsThirdPartyContextToTopWindow,
+ bool aIsFormSubmission, bool aSendCSPViolationEvents,
+ const OriginAttributes& aOriginAttributes,
+ RedirectHistoryArray&& aRedirectChainIncludingInternalRedirects,
+ RedirectHistoryArray&& aRedirectChain,
+ nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals,
+ const nsTArray<uint64_t>& aAncestorBrowsingContextIDs,
+ const nsTArray<nsCString>& aCorsUnsafeHeaders, bool aForcePreflight,
+ bool aIsPreflight, bool aLoadTriggeredFromExternal,
+ bool aServiceWorkerTaintingSynthesized, bool aDocumentHasUserInteracted,
+ bool aAllowListFutureDocumentsCreatedFromThisRedirectChain,
+ bool aNeedForCheckingAntiTrackingHeuristic, const nsAString& aCspNonce,
+ const nsAString& aIntegrityMetadata, bool aSkipContentSniffing,
+ uint32_t aHttpsOnlyStatus, bool aHstsStatus,
+ bool aHasValidUserGestureActivation, bool aAllowDeprecatedSystemRequests,
+ bool aIsInDevToolsContext, bool aParserCreatedScript,
+ nsILoadInfo::StoragePermissionState aStoragePermission,
+ const Maybe<RFPTarget>& aOverriddenFingerprintingSettings,
+ bool aIsMetaRefresh, uint32_t aRequestBlockingReason,
+ nsINode* aLoadingContext,
+ nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy,
+ bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel,
+ nsIURI* aUnstrippedURI, nsIInterceptionInfo* aInterceptionInfo,
+ bool aHasInjectedCookieForCookieBannerHandling, bool aWasSchemelessInput);
+ LoadInfo(const LoadInfo& rhs);
+
+ NS_IMETHOD GetRedirects(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aRedirects,
+ const RedirectHistoryArray& aArra);
+
+ friend nsresult mozilla::ipc::LoadInfoArgsToLoadInfo(
+ const mozilla::net::LoadInfoArgs& aLoadInfoArgs,
+ const nsACString& aOriginRemoteType, nsINode* aCspToInheritLoadingContext,
+ net::LoadInfo** outLoadInfo);
+
+ ~LoadInfo();
+
+ void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow);
+ void ComputeIsThirdPartyContext(dom::WindowGlobalParent* aGlobal);
+
+ // This function is the *only* function which can change the securityflags
+ // of a loadinfo. It only exists because of the XHR code. Don't call it
+ // from anywhere else!
+ void SetIncludeCookiesSecFlag();
+ friend class mozilla::dom::XMLHttpRequestMainThread;
+
+ // nsDocShell::OpenInitializedChannel and EarlyHintPreloader::OpenChannel
+ // needs to update the loadInfo with the correct browsingContext.
+ friend class ::nsDocShell;
+ friend class mozilla::net::EarlyHintPreloader;
+ void UpdateBrowsingContextID(uint64_t aBrowsingContextID) {
+ mBrowsingContextID = aBrowsingContextID;
+ }
+ void UpdateFrameBrowsingContextID(uint64_t aFrameBrowsingContextID) {
+ mFrameBrowsingContextID = aFrameBrowsingContextID;
+ }
+ MOZ_NEVER_INLINE void ReleaseMembers();
+
+ // if you add a member, please also update the copy constructor and consider
+ // if it should be merged from parent channel through
+ // ParentLoadInfoForwarderArgs.
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+ nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsCOMPtr<nsIURI> mChannelCreationOriginalURI;
+ nsCOMPtr<nsICSPEventListener> mCSPEventListener;
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+ nsCOMPtr<nsIContentSecurityPolicy> mCspToInherit;
+ nsCString mTriggeringRemoteType;
+ nsID mSandboxedNullPrincipalID;
+
+ Maybe<mozilla::dom::ClientInfo> mClientInfo;
+ UniquePtr<mozilla::dom::ClientSource> mReservedClientSource;
+ Maybe<mozilla::dom::ClientInfo> mReservedClientInfo;
+ Maybe<mozilla::dom::ClientInfo> mInitialClientInfo;
+ Maybe<mozilla::dom::ServiceWorkerDescriptor> mController;
+ RefPtr<mozilla::dom::PerformanceStorage> mPerformanceStorage;
+
+ nsWeakPtr mLoadingContext;
+ nsWeakPtr mContextForTopLevelLoad;
+ nsSecurityFlags mSecurityFlags;
+ uint32_t mSandboxFlags;
+ uint32_t mTriggeringSandboxFlags = 0;
+ uint64_t mTriggeringWindowId = 0;
+ bool mTriggeringStorageAccess = false;
+ nsContentPolicyType mInternalContentPolicyType;
+ LoadTainting mTainting = LoadTainting::Basic;
+ bool mBlockAllMixedContent = false;
+ bool mUpgradeInsecureRequests = false;
+ bool mBrowserUpgradeInsecureRequests = false;
+ bool mBrowserDidUpgradeInsecureRequests = false;
+ bool mBrowserWouldUpgradeInsecureRequests = false;
+ bool mForceAllowDataURI = false;
+ bool mAllowInsecureRedirectToDataURI = false;
+ bool mSkipContentPolicyCheckForWebRequest = false;
+ bool mOriginalFrameSrcLoad = false;
+ bool mForceInheritPrincipalDropped = false;
+ uint64_t mInnerWindowID = 0;
+ uint64_t mBrowsingContextID = 0;
+ uint64_t mWorkerAssociatedBrowsingContextID = 0;
+ uint64_t mFrameBrowsingContextID = 0;
+ bool mInitialSecurityCheckDone = false;
+ // NB: TYPE_DOCUMENT implies !third-party.
+ bool mIsThirdPartyContext = false;
+ Maybe<bool> mIsThirdPartyContextToTopWindow;
+ bool mIsFormSubmission = false;
+ bool mSendCSPViolationEvents = true;
+ OriginAttributes mOriginAttributes;
+ RedirectHistoryArray mRedirectChainIncludingInternalRedirects;
+ RedirectHistoryArray mRedirectChain;
+ nsTArray<nsCOMPtr<nsIPrincipal>> mAncestorPrincipals;
+ nsTArray<uint64_t> mAncestorBrowsingContextIDs;
+ nsTArray<nsCString> mCorsUnsafeHeaders;
+ uint32_t mRequestBlockingReason = BLOCKING_REASON_NONE;
+ bool mForcePreflight = false;
+ bool mIsPreflight = false;
+ bool mLoadTriggeredFromExternal = false;
+ bool mServiceWorkerTaintingSynthesized = false;
+ bool mDocumentHasUserInteracted = false;
+ bool mAllowListFutureDocumentsCreatedFromThisRedirectChain = false;
+ bool mNeedForCheckingAntiTrackingHeuristic = false;
+ nsString mCspNonce;
+ nsString mIntegrityMetadata;
+ bool mSkipContentSniffing = false;
+ uint32_t mHttpsOnlyStatus = nsILoadInfo::HTTPS_ONLY_UNINITIALIZED;
+ bool mHstsStatus = false;
+ bool mHasValidUserGestureActivation = false;
+ bool mAllowDeprecatedSystemRequests = false;
+ bool mIsUserTriggeredSave = false;
+ bool mIsInDevToolsContext = false;
+ bool mParserCreatedScript = false;
+ nsILoadInfo::StoragePermissionState mStoragePermission =
+ nsILoadInfo::NoStoragePermission;
+ Maybe<RFPTarget> mOverriddenFingerprintingSettings;
+#ifdef DEBUG
+ // A boolean used to ensure the mOverriddenFingerprintingSettings is set
+ // before use it.
+ bool mOverriddenFingerprintingSettingsIsSet = false;
+#endif
+ bool mIsMetaRefresh = false;
+
+ // Is true if this load was triggered by processing the attributes of the
+ // browsing context container.
+ // See nsILoadInfo.isFromProcessingFrameAttributes
+ bool mIsFromProcessingFrameAttributes = false;
+
+ // See nsILoadInfo.isMediaRequest and nsILoadInfo.isMediaInitialRequest.
+ bool mIsMediaRequest = false;
+ bool mIsMediaInitialRequest = false;
+
+ // See nsILoadInfo.isFromObjectOrEmbed
+ bool mIsFromObjectOrEmbed = false;
+
+ bool mSkipCheckForBrokenURLOrZeroSized = false;
+
+ // The cross origin embedder policy that the loading need to respect.
+ // If the value is nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP, CORP checking
+ // must be performed for the loading.
+ // See https://wicg.github.io/cross-origin-embedder-policy/#corp-check.
+ nsILoadInfo::CrossOriginEmbedderPolicy mLoadingEmbedderPolicy =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+
+ bool mIsOriginTrialCoepCredentiallessEnabledForTopLevel = false;
+
+ nsCOMPtr<nsIURI> mUnstrippedURI;
+
+ nsCOMPtr<nsIInterceptionInfo> mInterceptionInfo;
+
+ bool mHasInjectedCookieForCookieBannerHandling = false;
+ bool mWasSchemelessInput = false;
+};
+
+// This is exposed solely for testing purposes and should not be used outside of
+// LoadInfo
+already_AddRefed<nsIPrincipal> CreateTruncatedPrincipal(nsIPrincipal*);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_LoadInfo_h
diff --git a/netwerk/base/LoadTainting.h b/netwerk/base/LoadTainting.h
new file mode 100644
index 0000000000..11dc8a304f
--- /dev/null
+++ b/netwerk/base/LoadTainting.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LoadTainting_h
+#define mozilla_LoadTainting_h
+
+#include <stdint.h>
+
+namespace mozilla {
+
+// Define an enumeration to reflect the concept of response tainting from the
+// the fetch spec:
+//
+// https://fetch.spec.whatwg.org/#concept-request-response-tainting
+//
+// Roughly the tainting means:
+//
+// * Basic: the request resulted in a same-origin or non-http load
+// * CORS: the request resulted in a cross-origin load with CORS headers
+// * Opaque: the request resulted in a cross-origin load without CORS headers
+//
+// The enumeration is purposefully designed such that more restrictive tainting
+// corresponds to a higher integral value.
+enum class LoadTainting : uint8_t { Basic = 0, CORS = 1, Opaque = 2 };
+
+} // namespace mozilla
+
+#endif // mozilla_LoadTainting_h
diff --git a/netwerk/base/MemoryDownloader.cpp b/netwerk/base/MemoryDownloader.cpp
new file mode 100644
index 0000000000..afb4552cca
--- /dev/null
+++ b/netwerk/base/MemoryDownloader.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MemoryDownloader.h"
+
+#include "mozilla/Assertions.h"
+#include "nsIInputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(MemoryDownloader, nsIStreamListener, nsIRequestObserver)
+
+MemoryDownloader::MemoryDownloader(IObserver* aObserver)
+ : mObserver(aObserver), mStatus(NS_ERROR_NOT_INITIALIZED) {}
+
+NS_IMETHODIMP
+MemoryDownloader::OnStartRequest(nsIRequest* aRequest) {
+ MOZ_ASSERT(!mData);
+ mData.reset(new FallibleTArray<uint8_t>());
+ mStatus = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryDownloader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ MOZ_ASSERT_IF(NS_FAILED(mStatus), NS_FAILED(aStatus));
+ MOZ_ASSERT(!mData == NS_FAILED(mStatus));
+ Data data;
+ data.swap(mData);
+ RefPtr<IObserver> observer;
+ observer.swap(mObserver);
+ observer->OnDownloadComplete(this, aRequest, aStatus, std::move(data));
+ return NS_OK;
+}
+
+nsresult MemoryDownloader::ConsumeData(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ MemoryDownloader* self = static_cast<MemoryDownloader*>(aClosure);
+ if (!self->mData->AppendElements(aFromRawSegment, aCount, fallible)) {
+ // The error returned by ConsumeData isn't propagated to the
+ // return of ReadSegments, so it has to be passed as state.
+ self->mStatus = NS_ERROR_OUT_OF_MEMORY;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MemoryDownloader::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStr,
+ uint64_t aSourceOffset, uint32_t aCount) {
+ uint32_t n;
+ MOZ_ASSERT(mData);
+ nsresult rv = aInStr->ReadSegments(ConsumeData, this, aCount, &n);
+ if (NS_SUCCEEDED(mStatus) && NS_FAILED(rv)) {
+ mStatus = rv;
+ }
+ if (NS_WARN_IF(NS_FAILED(mStatus))) {
+ mData.reset(nullptr);
+ return mStatus;
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/MemoryDownloader.h b/netwerk/base/MemoryDownloader.h
new file mode 100644
index 0000000000..8b5700f5bf
--- /dev/null
+++ b/netwerk/base/MemoryDownloader.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_MemoryDownloader_h__
+#define mozilla_net_MemoryDownloader_h__
+
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIStreamListener.h"
+#include "nsTArray.h"
+
+/**
+ * mozilla::net::MemoryDownloader
+ *
+ * This class is similar to nsIDownloader, but stores the downloaded
+ * stream in memory instead of a file. Ownership of the temporary
+ * memory is transferred to the observer when download is complete;
+ * there is no need to retain a reference to the downloader.
+ */
+
+namespace mozilla {
+namespace net {
+
+class MemoryDownloader final : public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ using Data = mozilla::UniquePtr<FallibleTArray<uint8_t>>;
+
+ class IObserver : public nsISupports {
+ public:
+ // Note: aData may be null if (and only if) aStatus indicates failure.
+ virtual void OnDownloadComplete(MemoryDownloader* aDownloader,
+ nsIRequest* aRequest, nsresult aStatus,
+ Data aData) = 0;
+ };
+
+ explicit MemoryDownloader(IObserver* aObserver);
+
+ private:
+ virtual ~MemoryDownloader() = default;
+
+ static nsresult ConsumeData(nsIInputStream* in, void* closure,
+ const char* fromRawSegment, uint32_t toOffset,
+ uint32_t count, uint32_t* writeCount);
+
+ RefPtr<IObserver> mObserver;
+ Data mData;
+ nsresult mStatus;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_MemoryDownloader_h__
diff --git a/netwerk/base/NetUtil.sys.mjs b/netwerk/base/NetUtil.sys.mjs
new file mode 100644
index 0000000000..679c9979a7
--- /dev/null
+++ b/netwerk/base/NetUtil.sys.mjs
@@ -0,0 +1,453 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
+ * vim: sw=4 ts=4 sts=4 et filetype=javascript
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Necko utilities
+ */
+
+// //////////////////////////////////////////////////////////////////////////////
+// // Constants
+
+const PR_UINT32_MAX = 0xffffffff;
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+// //////////////////////////////////////////////////////////////////////////////
+// // NetUtil Object
+
+export var NetUtil = {
+ /**
+ * Function to perform simple async copying from aSource (an input stream)
+ * to aSink (an output stream). The copy will happen on some background
+ * thread. Both streams will be closed when the copy completes.
+ *
+ * @param aSource
+ * The input stream to read from
+ * @param aSink
+ * The output stream to write to
+ * @param aCallback [optional]
+ * A function that will be called at copy completion with a single
+ * argument: the nsresult status code for the copy operation.
+ *
+ * @return An nsIRequest representing the copy operation (for example, this
+ * can be used to cancel the copying). The consumer can ignore the
+ * return value if desired.
+ */
+ asyncCopy: function NetUtil_asyncCopy(aSource, aSink, aCallback = null) {
+ if (!aSource || !aSink) {
+ let exception = new Components.Exception(
+ "Must have a source and a sink",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ // make a stream copier
+ var copier = Cc[
+ "@mozilla.org/network/async-stream-copier;1"
+ ].createInstance(Ci.nsIAsyncStreamCopier2);
+ copier.init(
+ aSource,
+ aSink,
+ null /* Default event target */,
+ 0 /* Default length */,
+ true,
+ true /* Auto-close */
+ );
+
+ var observer;
+ if (aCallback) {
+ observer = {
+ onStartRequest(aRequest) {},
+ onStopRequest(aRequest, aStatusCode) {
+ aCallback(aStatusCode);
+ },
+ };
+ } else {
+ observer = null;
+ }
+
+ // start the copying
+ copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
+ return copier;
+ },
+
+ /**
+ * Asynchronously opens a source and fetches the response. While the fetch
+ * is asynchronous, I/O may happen on the main thread. When reading from
+ * a local file, prefer using IOUtils methods instead.
+ *
+ * @param aSource
+ * This argument can be one of the following:
+ * - An options object that will be passed to NetUtil.newChannel.
+ * - An existing nsIChannel.
+ * - An existing nsIInputStream.
+ * Using an nsIURI, nsIFile, or string spec directly is deprecated.
+ * @param aCallback
+ * The callback function that will be notified upon completion. It
+ * will get these arguments:
+ * 1) An nsIInputStream containing the data from aSource, if any.
+ * 2) The status code from opening the source.
+ * 3) Reference to the nsIRequest.
+ */
+ asyncFetch: function NetUtil_asyncFetch(aSource, aCallback) {
+ if (!aSource || !aCallback) {
+ let exception = new Components.Exception(
+ "Must have a source and a callback",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ // Create a pipe that will create our output stream that we can use once
+ // we have gotten all the data.
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, PR_UINT32_MAX, null);
+
+ // Create a listener that will give data to the pipe's output stream.
+ let listener = Cc[
+ "@mozilla.org/network/simple-stream-listener;1"
+ ].createInstance(Ci.nsISimpleStreamListener);
+ listener.init(pipe.outputStream, {
+ onStartRequest(aRequest) {},
+ onStopRequest(aRequest, aStatusCode) {
+ pipe.outputStream.close();
+ aCallback(pipe.inputStream, aStatusCode, aRequest);
+ },
+ });
+
+ // Input streams are handled slightly differently from everything else.
+ if (aSource instanceof Ci.nsIInputStream) {
+ let pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(
+ Ci.nsIInputStreamPump
+ );
+ pump.init(aSource, 0, 0, true);
+ pump.asyncRead(listener, null);
+ return;
+ }
+
+ let channel = aSource;
+ if (!(channel instanceof Ci.nsIChannel)) {
+ channel = this.newChannel(aSource);
+ }
+
+ try {
+ channel.asyncOpen(listener);
+ } catch (e) {
+ let exception = new Components.Exception(
+ "Failed to open input source '" + channel.originalURI.spec + "'",
+ e.result,
+ Components.stack.caller,
+ aSource,
+ e
+ );
+ throw exception;
+ }
+ },
+
+ /**
+ * Constructs a new URI for the given spec, character set, and base URI, or
+ * an nsIFile.
+ *
+ * @param aTarget
+ * The string spec for the desired URI or an nsIFile.
+ * @param aOriginCharset [optional]
+ * The character set for the URI. Only used if aTarget is not an
+ * nsIFile.
+ * @param aBaseURI [optional]
+ * The base URI for the spec. Only used if aTarget is not an
+ * nsIFile.
+ *
+ * @return an nsIURI object.
+ */
+ newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI) {
+ if (!aTarget) {
+ let exception = new Components.Exception(
+ "Must have a non-null string spec or nsIFile object",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (aTarget instanceof Ci.nsIFile) {
+ return Services.io.newFileURI(aTarget);
+ }
+
+ return Services.io.newURI(aTarget, aOriginCharset, aBaseURI);
+ },
+
+ /**
+ * Constructs a new channel for the given source.
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ *
+ * @param aWhatToLoad
+ * This argument used to be a string spec for the desired URI, an
+ * nsIURI, or an nsIFile. Now it should be an options object with
+ * the following properties:
+ * {
+ * uri:
+ * The full URI spec string, nsIURI or nsIFile to create the
+ * channel for.
+ * Note that this cannot be an nsIFile if you have to specify a
+ * non-default charset or base URI. Call NetUtil.newURI first if
+ * you need to construct an URI using those options.
+ * loadingNode:
+ * loadingPrincipal:
+ * triggeringPrincipal:
+ * securityFlags:
+ * contentPolicyType:
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * loadUsingSystemPrincipal:
+ * Set this to true to use the system principal as
+ * loadingPrincipal. This must be omitted if loadingPrincipal or
+ * loadingNode are present.
+ * This should be used with care as it skips security checks.
+ * }
+ * @return an nsIChannel object.
+ */
+ newChannel: function NetUtil_newChannel(aWhatToLoad) {
+ // Make sure the API is called using only the options object.
+ if (typeof aWhatToLoad != "object" || arguments.length != 1) {
+ throw new Components.Exception(
+ "newChannel requires a single object argument",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ let {
+ uri,
+ loadingNode,
+ loadingPrincipal,
+ loadUsingSystemPrincipal,
+ triggeringPrincipal,
+ securityFlags,
+ contentPolicyType,
+ } = aWhatToLoad;
+
+ if (!uri) {
+ throw new Components.Exception(
+ "newChannel requires the 'uri' property on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (typeof uri == "string" || uri instanceof Ci.nsIFile) {
+ uri = this.newURI(uri);
+ }
+
+ if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) {
+ throw new Components.Exception(
+ "newChannel requires at least one of the 'loadingNode'," +
+ " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" +
+ " properties on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (loadUsingSystemPrincipal === true) {
+ if (loadingNode || loadingPrincipal) {
+ throw new Components.Exception(
+ "newChannel does not accept 'loadUsingSystemPrincipal'" +
+ " if the 'loadingNode' or 'loadingPrincipal' properties" +
+ " are present on the options object.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+ loadingPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+ } else if (loadUsingSystemPrincipal !== undefined) {
+ throw new Components.Exception(
+ "newChannel requires the 'loadUsingSystemPrincipal'" +
+ " property on the options object to be 'true' or 'undefined'.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+
+ if (securityFlags === undefined) {
+ if (!loadUsingSystemPrincipal) {
+ throw new Components.Exception(
+ "newChannel requires the 'securityFlags' property on" +
+ " the options object unless loading from system principal.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+ securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+ }
+
+ if (contentPolicyType === undefined) {
+ if (!loadUsingSystemPrincipal) {
+ throw new Components.Exception(
+ "newChannel requires the 'contentPolicyType' property on" +
+ " the options object unless loading from system principal.",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ }
+ contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
+ }
+
+ let channel = Services.io.newChannelFromURI(
+ uri,
+ loadingNode || null,
+ loadingPrincipal || null,
+ triggeringPrincipal || null,
+ securityFlags,
+ contentPolicyType
+ );
+ if (loadUsingSystemPrincipal) {
+ channel.loadInfo.allowDeprecatedSystemRequests = true;
+ }
+ return channel;
+ },
+
+ newWebTransport: function NetUtil_newWebTransport() {
+ return Services.io.newWebTransport();
+ },
+
+ /**
+ * Reads aCount bytes from aInputStream into a string.
+ *
+ * @param aInputStream
+ * The input stream to read from.
+ * @param aCount
+ * The number of bytes to read from the stream.
+ * @param aOptions [optional]
+ * charset
+ * The character encoding of stream data.
+ * replacement
+ * The character to replace unknown byte sequences.
+ * If unset, it causes an exceptions to be thrown.
+ *
+ * @return the bytes from the input stream in string form.
+ *
+ * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
+ * block the calling thread (non-blocking mode only).
+ * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
+ * aCount amount of data.
+ * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences
+ */
+ readInputStreamToString: function NetUtil_readInputStreamToString(
+ aInputStream,
+ aCount,
+ aOptions
+ ) {
+ if (!(aInputStream instanceof Ci.nsIInputStream)) {
+ let exception = new Components.Exception(
+ "First argument should be an nsIInputStream",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (!aCount) {
+ let exception = new Components.Exception(
+ "Non-zero amount of bytes must be specified",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (aOptions && "charset" in aOptions) {
+ let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
+ Ci.nsIConverterInputStream
+ );
+ try {
+ // When replacement is set, the character that is unknown sequence
+ // replaces with aOptions.replacement character.
+ if (!("replacement" in aOptions)) {
+ // aOptions.replacement isn't set.
+ // If input stream has unknown sequences for aOptions.charset,
+ // throw NS_ERROR_ILLEGAL_INPUT.
+ aOptions.replacement = 0;
+ }
+
+ cis.init(aInputStream, aOptions.charset, aCount, aOptions.replacement);
+ let str = {};
+ cis.readString(-1, str);
+ cis.close();
+ return str.value;
+ } catch (e) {
+ // Adjust the stack so it throws at the caller's location.
+ throw new Components.Exception(
+ e.message,
+ e.result,
+ Components.stack.caller,
+ e.data
+ );
+ }
+ }
+
+ let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ sis.init(aInputStream);
+ try {
+ return sis.readBytes(aCount);
+ } catch (e) {
+ // Adjust the stack so it throws at the caller's location.
+ throw new Components.Exception(
+ e.message,
+ e.result,
+ Components.stack.caller,
+ e.data
+ );
+ }
+ },
+
+ /**
+ * Reads aCount bytes from aInputStream into a string.
+ *
+ * @param {nsIInputStream} aInputStream
+ * The input stream to read from.
+ * @param {integer} [aCount = aInputStream.available()]
+ * The number of bytes to read from the stream.
+ *
+ * @return the bytes from the input stream in string form.
+ *
+ * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
+ * block the calling thread (non-blocking mode only).
+ * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
+ * aCount amount of data.
+ */
+ readInputStream(aInputStream, aCount) {
+ if (!(aInputStream instanceof Ci.nsIInputStream)) {
+ let exception = new Components.Exception(
+ "First argument should be an nsIInputStream",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ if (!aCount) {
+ aCount = aInputStream.available();
+ }
+
+ let stream = new BinaryInputStream(aInputStream);
+ let result = new ArrayBuffer(aCount);
+ stream.readArrayBuffer(result.byteLength, result);
+ return result;
+ },
+};
diff --git a/netwerk/base/NetworkConnectivityService.cpp b/netwerk/base/NetworkConnectivityService.cpp
new file mode 100644
index 0000000000..1e126742ce
--- /dev/null
+++ b/netwerk/base/NetworkConnectivityService.cpp
@@ -0,0 +1,554 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DNSUtils.h"
+#include "NetworkConnectivityService.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "nsIOService.h"
+#include "nsICancelable.h"
+#include "xpcpublic.h"
+#include "nsSocketTransport2.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsINetworkLinkService.h"
+#include "mozilla/StaticPrefs_network.h"
+
+static mozilla::LazyLogModule gNCSLog("NetworkConnectivityService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gNCSLog, mozilla::LogLevel::Debug, args)
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(NetworkConnectivityService, nsIDNSListener, nsIObserver,
+ nsINetworkConnectivityService, nsIStreamListener)
+
+static StaticRefPtr<NetworkConnectivityService> gConnService;
+
+NetworkConnectivityService::NetworkConnectivityService()
+ : mDNSv4(UNKNOWN),
+ mDNSv6(UNKNOWN),
+ mIPv4(UNKNOWN),
+ mIPv6(UNKNOWN),
+ mNAT64(UNKNOWN),
+ mLock("nat64prefixes") {}
+
+// static
+already_AddRefed<NetworkConnectivityService>
+NetworkConnectivityService::GetSingleton() {
+ if (gConnService) {
+ return do_AddRef(gConnService);
+ }
+
+ RefPtr<NetworkConnectivityService> service = new NetworkConnectivityService();
+ service->Init();
+
+ gConnService = std::move(service);
+ ClearOnShutdown(&gConnService);
+ return do_AddRef(gConnService);
+}
+
+nsresult NetworkConnectivityService::Init() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ observerService->AddObserver(this, "network:captive-portal-connectivity",
+ false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetDNSv4(ConnectivityState* aState) {
+ NS_ENSURE_ARG(aState);
+ *aState = mDNSv4;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetDNSv6(ConnectivityState* aState) {
+ NS_ENSURE_ARG(aState);
+ *aState = mDNSv6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetIPv4(ConnectivityState* aState) {
+ NS_ENSURE_ARG(aState);
+ *aState = mIPv4;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetIPv6(ConnectivityState* aState) {
+ NS_ENSURE_ARG(aState);
+ *aState = mIPv6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetNAT64(ConnectivityState* aState) {
+ NS_ENSURE_ARG(aState);
+ *aState = mNAT64;
+ return NS_OK;
+}
+
+already_AddRefed<AddrInfo> NetworkConnectivityService::MapNAT64IPs(
+ AddrInfo* aNewRRSet) {
+ // Add prefixes only if there are no IPv6 addresses.
+ // Expect that if aNewRRSet has IPv6 addresses, they must come
+ // before IPv4 addresses.
+ if (aNewRRSet->Addresses().IsEmpty() ||
+ aNewRRSet->Addresses()[0].raw.family == PR_AF_INET6) {
+ return do_AddRef(aNewRRSet);
+ }
+
+ // Currently we only add prefixes to the first IP's clones.
+ uint32_t ip = aNewRRSet->Addresses()[0].inet.ip;
+ nsTArray<NetAddr> addresses = aNewRRSet->Addresses().Clone();
+
+ {
+ MutexAutoLock lock(mLock);
+ for (const auto& prefix : mNAT64Prefixes) {
+ NetAddr addr = NetAddr(prefix);
+
+ // Copy the IPv4 address to the end
+ addr.inet6.ip.u32[3] = ip;
+
+ // If we have both IPv4 and NAT64, we be could insourcing NAT64
+ // to avoid double NAT and improve performance. However, this
+ // breaks WebRTC, so we push it to the back.
+ addresses.AppendElement(addr);
+ }
+ }
+
+ auto builder = aNewRRSet->Build();
+ builder.SetAddresses(std::move(addresses));
+ return builder.Finish();
+}
+
+// Returns true if a prefix was read and saved to the argument
+static inline bool NAT64PrefixFromPref(NetAddr* prefix) {
+ nsAutoCString nat64PrefixPref;
+
+ nsresult rv = Preferences::GetCString(
+ "network.connectivity-service.nat64-prefix", nat64PrefixPref);
+ return !(NS_FAILED(rv) || nat64PrefixPref.IsEmpty() ||
+ NS_FAILED(prefix->InitFromString(nat64PrefixPref)) ||
+ prefix->raw.family != PR_AF_INET6);
+}
+
+static inline bool NAT64PrefixCompare(const NetAddr& prefix1,
+ const NetAddr& prefix2) {
+ // Compare the first 96 bits as 64 + 32
+ return prefix1.inet6.ip.u64[0] == prefix2.inet6.ip.u64[0] &&
+ prefix1.inet6.ip.u32[2] == prefix2.inet6.ip.u32[2];
+}
+
+void NetworkConnectivityService::PerformChecks() {
+ mDNSv4 = UNKNOWN;
+ mDNSv6 = UNKNOWN;
+
+ mIPv4 = UNKNOWN;
+ mIPv6 = UNKNOWN;
+
+ mNAT64 = UNKNOWN;
+
+ {
+ MutexAutoLock lock(mLock);
+ mNAT64Prefixes.Clear();
+
+ // NAT64 checks might be disabled.
+ // Since We can't guarantee a DNS response, we should set up
+ // NAT64 manually now if needed.
+
+ NetAddr priorityPrefix{};
+ bool havePrefix = NAT64PrefixFromPref(&priorityPrefix);
+ if (havePrefix) {
+ mNAT64Prefixes.AppendElement(priorityPrefix);
+ mNAT64 = OK;
+ }
+ }
+
+ RecheckDNS();
+ RecheckIPConnectivity();
+}
+
+static inline void NotifyObservers(const char* aTopic) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->NotifyObservers(nullptr, aTopic, nullptr);
+}
+
+void NetworkConnectivityService::SaveNAT64Prefixes(nsIDNSRecord* aRecord) {
+ nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord);
+ MutexAutoLock lock(mLock);
+ mNAT64Prefixes.Clear();
+
+ NetAddr priorityPrefix{};
+ bool havePrefix = NAT64PrefixFromPref(&priorityPrefix);
+ if (havePrefix) {
+ mNAT64 = OK;
+ mNAT64Prefixes.AppendElement(priorityPrefix);
+ }
+
+ if (!rec) {
+ if (!havePrefix) {
+ mNAT64 = NOT_AVAILABLE;
+ }
+ return;
+ }
+
+ mNAT64 = UNKNOWN;
+ NetAddr addr{};
+
+ // use port 80 as dummy value for NetAddr
+ while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) {
+ if (addr.raw.family != AF_INET6 || addr.IsIPAddrV4Mapped()) {
+ // These are not the kind of addresses we are looking for.
+ continue;
+ }
+
+ // RFC 7050 does not require the embedded IPv4 to be
+ // at the end of IPv6. In practice, and as we assume,
+ // it is always at the end.
+ // The embedded IP must be 192.0.0.170 or 192.0.0.171
+
+ // Clear the last bit to compare with the next one.
+ addr.inet6.ip.u8[15] &= ~(uint32_t)1;
+ if ((addr.inet6.ip.u8[12] != 192) || (addr.inet6.ip.u8[13] != 0) ||
+ (addr.inet6.ip.u8[14] != 0) || (addr.inet6.ip.u8[15] != 170)) {
+ continue;
+ }
+
+ mNAT64Prefixes.AppendElement(addr);
+ }
+
+ size_t length = mNAT64Prefixes.Length();
+ if (length == 0) {
+ mNAT64 = NOT_AVAILABLE;
+ return;
+ }
+
+ // Remove duplicates. Typically a DNS64 resolver sends every
+ // prefix twice with address with different last bits. We want
+ // a list of unique prefixes while reordering is not allowed.
+ // We must not handle the case with an element in-between
+ // two identical ones, which is never the case for a properly
+ // configured DNS64 resolver.
+
+ NetAddr prev = mNAT64Prefixes[0];
+
+ for (size_t i = 1; i < length; i++) {
+ if (NAT64PrefixCompare(prev, mNAT64Prefixes[i])) {
+ mNAT64Prefixes.RemoveElementAt(i);
+ i--;
+ length--;
+ } else {
+ prev = mNAT64Prefixes[i];
+ }
+ }
+
+ // The prioritized address might also appear in the record we received.
+
+ if (havePrefix) {
+ for (size_t i = 1; i < length; i++) {
+ if (NAT64PrefixCompare(priorityPrefix, mNAT64Prefixes[i])) {
+ mNAT64Prefixes.RemoveElementAt(i);
+ // It wouldn't appear more than once.
+ break;
+ }
+ }
+ }
+
+ mNAT64 = OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::OnLookupComplete(nsICancelable* aRequest,
+ nsIDNSRecord* aRecord,
+ nsresult aStatus) {
+ ConnectivityState state = NS_SUCCEEDED(aStatus) ? OK : NOT_AVAILABLE;
+
+ if (aRequest == mDNSv4Request) {
+ mDNSv4 = state;
+ mDNSv4Request = nullptr;
+ } else if (aRequest == mDNSv6Request) {
+ mDNSv6 = state;
+ mDNSv6Request = nullptr;
+ } else if (aRequest == mNAT64Request) {
+ mNAT64Request = nullptr;
+ SaveNAT64Prefixes(aRecord);
+ }
+
+ if (!mDNSv4Request && !mDNSv6Request && !mNAT64Request) {
+ NotifyObservers("network:connectivity-service:dns-checks-complete");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::RecheckDNS() {
+ bool enabled =
+ Preferences::GetBool("network.connectivity-service.enabled", false);
+ if (!enabled) {
+ return NS_OK;
+ }
+
+ if (nsIOService::UseSocketProcess()) {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent) {
+ Unused << parent->SendRecheckDNS();
+ }
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ OriginAttributes attrs;
+ nsAutoCString host;
+ Preferences::GetCString("network.connectivity-service.DNSv4.domain", host);
+
+ rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DISABLE_IPV6 |
+ nsIDNSService::RESOLVE_TRR_DISABLED_MODE,
+ nullptr, this, NS_GetCurrentThread(), attrs,
+ getter_AddRefs(mDNSv4Request));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Preferences::GetCString("network.connectivity-service.DNSv6.domain", host);
+ rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DISABLE_IPV4 |
+ nsIDNSService::RESOLVE_TRR_DISABLED_MODE,
+ nullptr, this, NS_GetCurrentThread(), attrs,
+ getter_AddRefs(mDNSv6Request));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (StaticPrefs::network_connectivity_service_nat64_check()) {
+ rv = dns->AsyncResolveNative("ipv4only.arpa"_ns,
+ nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DISABLE_IPV4 |
+ nsIDNSService::RESOLVE_TRR_DISABLED_MODE,
+ nullptr, this, NS_GetCurrentThread(), attrs,
+ getter_AddRefs(mNAT64Request));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "network:captive-portal-connectivity")) {
+ // Captive portal is cleared, so we redo the checks.
+ PerformChecks();
+ } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ if (mDNSv4Request) {
+ mDNSv4Request->Cancel(NS_ERROR_ABORT);
+ mDNSv4Request = nullptr;
+ }
+ if (mDNSv6Request) {
+ mDNSv6Request->Cancel(NS_ERROR_ABORT);
+ mDNSv6Request = nullptr;
+ }
+ if (mNAT64Request) {
+ mNAT64Request->Cancel(NS_ERROR_ABORT);
+ mNAT64Request = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ observerService->RemoveObserver(this,
+ "network:captive-portal-connectivity");
+ observerService->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ } else if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC) &&
+ !NS_LITERAL_STRING_FROM_CSTRING(NS_NETWORK_LINK_DATA_UNKNOWN)
+ .Equals(aData)) {
+ PerformChecks();
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIChannel> NetworkConnectivityService::SetupIPCheckChannel(
+ bool ipv4) {
+ nsresult rv;
+ nsAutoCString url;
+
+ if (ipv4) {
+ rv = Preferences::GetCString("network.connectivity-service.IPv4.url", url);
+ } else {
+ rv = Preferences::GetCString("network.connectivity-service.IPv6.url", url);
+ }
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), url);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIChannel> channel;
+ if (XRE_IsSocketProcess()) {
+ rv = DNSUtils::CreateChannelHelper(uri, getter_AddRefs(channel));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ channel->SetLoadFlags(
+ nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache
+ nsIRequest::INHIBIT_CACHING | // don't write the response to cache
+ nsIRequest::LOAD_ANONYMOUS);
+ } else {
+ rv = NS_NewChannel(
+ getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // nsICookieJarSettings
+ nullptr, // aPerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr,
+ nsIRequest::LOAD_BYPASS_CACHE | // don't read from the cache
+ nsIRequest::INHIBIT_CACHING | // don't write the response to cache
+ nsIRequest::LOAD_ANONYMOUS); // prevent privacy leaks
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ {
+ // Prevent HTTPS-Only Mode from upgrading the OCSP request.
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
+ httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT;
+ loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
+
+ // allow deprecated HTTP request from SystemPrincipal
+ loadInfo->SetAllowDeprecatedSystemRequests(true);
+ }
+ }
+
+ rv = channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(channel);
+ NS_ENSURE_TRUE(internalChan, nullptr);
+
+ if (ipv4) {
+ internalChan->SetIPv6Disabled();
+ } else {
+ internalChan->SetIPv4Disabled();
+ }
+
+ return channel.forget();
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::RecheckIPConnectivity() {
+ bool enabled =
+ Preferences::GetBool("network.connectivity-service.enabled", false);
+ if (!enabled) {
+ return NS_OK;
+ }
+
+ if (nsIOService::UseSocketProcess()) {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent) {
+ Unused << parent->SendRecheckIPConnectivity();
+ }
+ }
+
+ if (xpc::AreNonLocalConnectionsDisabled() &&
+ !Preferences::GetBool("network.captive-portal-service.testMode", false)) {
+ return NS_OK;
+ }
+
+ if (mIPv4Channel) {
+ mIPv4Channel->Cancel(NS_ERROR_ABORT);
+ mIPv4Channel = nullptr;
+ }
+ if (mIPv6Channel) {
+ mIPv6Channel->Cancel(NS_ERROR_ABORT);
+ mIPv6Channel = nullptr;
+ }
+
+ nsresult rv;
+ mHasNetworkId = false;
+ mCheckedNetworkId = false;
+ mIPv4Channel = SetupIPCheckChannel(/* ipv4 = */ true);
+ if (mIPv4Channel) {
+ rv = mIPv4Channel->AsyncOpen(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mIPv6Channel = SetupIPCheckChannel(/* ipv4 = */ false);
+ if (mIPv6Channel) {
+ rv = mIPv6Channel->AsyncOpen(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::OnStartRequest(nsIRequest* aRequest) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ if (aStatusCode == NS_ERROR_ABORT) {
+ return NS_OK;
+ }
+
+ ConnectivityState status = NS_FAILED(aStatusCode) ? NOT_AVAILABLE : OK;
+
+ if (aRequest == mIPv4Channel) {
+ mIPv4 = status;
+ mIPv4Channel = nullptr;
+
+ if (mIPv4 == nsINetworkConnectivityService::OK) {
+ Telemetry::AccumulateCategorical(
+ mHasNetworkId ? Telemetry::LABELS_NETWORK_ID_ONLINE::present
+ : Telemetry::LABELS_NETWORK_ID_ONLINE::absent);
+ LOG(("mHasNetworkId : %d\n", mHasNetworkId));
+ }
+ } else if (aRequest == mIPv6Channel) {
+ mIPv6 = status;
+ mIPv6Channel = nullptr;
+ }
+
+ if (!mIPv6Channel && !mIPv4Channel) {
+ NotifyObservers("network:connectivity-service:ip-checks-complete");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ nsAutoCString data;
+
+ // We perform this check here, instead of doing it in OnStopRequest in case
+ // a network down event occurs after the data has arrived but before we fire
+ // OnStopRequest. That would cause us to report a missing networkID, even
+ // though it was not empty while receiving data.
+ if (aRequest == mIPv4Channel && !mCheckedNetworkId) {
+ nsCOMPtr<nsINetworkLinkService> nls =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID);
+ nsAutoCString networkId;
+ if (nls) {
+ nls->GetNetworkID(networkId);
+ }
+ mHasNetworkId = !networkId.IsEmpty();
+ mCheckedNetworkId = true;
+ }
+
+ Unused << NS_ReadInputStreamToString(aInputStream, data, aCount);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkConnectivityService.h b/netwerk/base/NetworkConnectivityService.h
new file mode 100644
index 0000000000..6315fb192b
--- /dev/null
+++ b/netwerk/base/NetworkConnectivityService.h
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetworkConnectivityService_h_
+#define NetworkConnectivityService_h_
+
+#include "nsINetworkConnectivityService.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsIDNSListener.h"
+#include "nsIStreamListener.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+class NetworkConnectivityService : public nsINetworkConnectivityService,
+ public nsIObserver,
+ public nsIDNSListener,
+ public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKCONNECTIVITYSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ already_AddRefed<AddrInfo> MapNAT64IPs(AddrInfo* aNewRRSet);
+
+ static already_AddRefed<NetworkConnectivityService> GetSingleton();
+
+ private:
+ NetworkConnectivityService();
+ virtual ~NetworkConnectivityService() = default;
+
+ nsresult Init();
+ // Calls all the check methods
+ void PerformChecks();
+
+ void SaveNAT64Prefixes(nsIDNSRecord* aRecord);
+
+ already_AddRefed<nsIChannel> SetupIPCheckChannel(bool ipv4);
+
+ // Will be set to OK if the DNS request returned in IP of this type,
+ // NOT_AVAILABLE if that type of resolution is not available
+ // UNKNOWN if the check wasn't performed
+ Atomic<ConnectivityState, Relaxed> mDNSv4;
+ Atomic<ConnectivityState, Relaxed> mDNSv6;
+
+ Atomic<ConnectivityState, Relaxed> mIPv4;
+ Atomic<ConnectivityState, Relaxed> mIPv6;
+
+ Atomic<ConnectivityState, Relaxed> mNAT64;
+
+ nsTArray<NetAddr> mNAT64Prefixes;
+
+ nsCOMPtr<nsICancelable> mDNSv4Request;
+ nsCOMPtr<nsICancelable> mDNSv6Request;
+ nsCOMPtr<nsICancelable> mNAT64Request;
+
+ nsCOMPtr<nsIChannel> mIPv4Channel;
+ nsCOMPtr<nsIChannel> mIPv6Channel;
+
+ bool mCheckedNetworkId = false;
+ bool mHasNetworkId = false;
+
+ Mutex mLock MOZ_UNANNOTATED;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // NetworkConnectivityService_h_
diff --git a/netwerk/base/NetworkDataCountLayer.cpp b/netwerk/base/NetworkDataCountLayer.cpp
new file mode 100644
index 0000000000..bee8bd1ee5
--- /dev/null
+++ b/netwerk/base/NetworkDataCountLayer.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NetworkDataCountLayer.h"
+#include "nsSocketTransportService2.h"
+#include "prmem.h"
+#include "prio.h"
+
+namespace mozilla {
+namespace net {
+
+static PRDescIdentity sNetworkDataCountLayerIdentity;
+static PRIOMethods sNetworkDataCountLayerMethods;
+static PRIOMethods* sNetworkDataCountLayerMethodsPtr = nullptr;
+
+class NetworkDataCountSecret {
+ public:
+ NetworkDataCountSecret() = default;
+
+ uint64_t mSentBytes = 0;
+ uint64_t mReceivedBytes = 0;
+};
+
+static PRInt32 NetworkDataCountSend(PRFileDesc* fd, const void* buf,
+ PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sNetworkDataCountLayerIdentity);
+
+ NetworkDataCountSecret* secret =
+ reinterpret_cast<NetworkDataCountSecret*>(fd->secret);
+
+ PRInt32 rv =
+ (fd->lower->methods->send)(fd->lower, buf, amount, flags, timeout);
+ if (rv > 0) {
+ secret->mSentBytes += rv;
+ }
+ return rv;
+}
+
+static PRInt32 NetworkDataCountWrite(PRFileDesc* fd, const void* buf,
+ PRInt32 amount) {
+ return NetworkDataCountSend(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRInt32 NetworkDataCountRecv(PRFileDesc* fd, void* buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sNetworkDataCountLayerIdentity);
+
+ NetworkDataCountSecret* secret =
+ reinterpret_cast<NetworkDataCountSecret*>(fd->secret);
+
+ PRInt32 rv =
+ (fd->lower->methods->recv)(fd->lower, buf, amount, flags, timeout);
+ if (rv > 0) {
+ secret->mReceivedBytes += rv;
+ }
+ return rv;
+}
+
+static PRInt32 NetworkDataCountRead(PRFileDesc* fd, void* buf, PRInt32 amount) {
+ return NetworkDataCountRecv(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRStatus NetworkDataCountClose(PRFileDesc* fd) {
+ if (!fd) {
+ return PR_FAILURE;
+ }
+
+ PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
+
+ MOZ_RELEASE_ASSERT(layer && layer->identity == sNetworkDataCountLayerIdentity,
+ "NetworkDataCount Layer not on top of stack");
+
+ NetworkDataCountSecret* secret =
+ reinterpret_cast<NetworkDataCountSecret*>(layer->secret);
+ layer->secret = nullptr;
+ layer->dtor(layer);
+ delete secret;
+ return fd->methods->close(fd);
+}
+
+nsresult AttachNetworkDataCountLayer(PRFileDesc* fd) {
+ if (!sNetworkDataCountLayerMethodsPtr) {
+ sNetworkDataCountLayerIdentity =
+ PR_GetUniqueIdentity("NetworkDataCount Layer");
+ sNetworkDataCountLayerMethods = *PR_GetDefaultIOMethods();
+ sNetworkDataCountLayerMethods.send = NetworkDataCountSend;
+ sNetworkDataCountLayerMethods.write = NetworkDataCountWrite;
+ sNetworkDataCountLayerMethods.recv = NetworkDataCountRecv;
+ sNetworkDataCountLayerMethods.read = NetworkDataCountRead;
+ sNetworkDataCountLayerMethods.close = NetworkDataCountClose;
+ sNetworkDataCountLayerMethodsPtr = &sNetworkDataCountLayerMethods;
+ }
+
+ PRFileDesc* layer = PR_CreateIOLayerStub(sNetworkDataCountLayerIdentity,
+ sNetworkDataCountLayerMethodsPtr);
+
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NetworkDataCountSecret* secret = new NetworkDataCountSecret();
+
+ layer->secret = reinterpret_cast<PRFilePrivate*>(secret);
+
+ PRStatus status = PR_PushIOLayer(fd, PR_NSPR_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ delete secret;
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+void NetworkDataCountSent(PRFileDesc* fd, uint64_t& sentBytes) {
+ PRFileDesc* ndcFd = PR_GetIdentitiesLayer(fd, sNetworkDataCountLayerIdentity);
+ MOZ_RELEASE_ASSERT(ndcFd);
+
+ NetworkDataCountSecret* secret =
+ reinterpret_cast<NetworkDataCountSecret*>(ndcFd->secret);
+ sentBytes = secret->mSentBytes;
+}
+
+void NetworkDataCountReceived(PRFileDesc* fd, uint64_t& receivedBytes) {
+ PRFileDesc* ndcFd = PR_GetIdentitiesLayer(fd, sNetworkDataCountLayerIdentity);
+ MOZ_RELEASE_ASSERT(ndcFd);
+
+ NetworkDataCountSecret* secret =
+ reinterpret_cast<NetworkDataCountSecret*>(ndcFd->secret);
+ receivedBytes = secret->mReceivedBytes;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkDataCountLayer.h b/netwerk/base/NetworkDataCountLayer.h
new file mode 100644
index 0000000000..a5a0d8f0dc
--- /dev/null
+++ b/netwerk/base/NetworkDataCountLayer.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetworkDataCountLayer_h__
+#define NetworkDataCountLayer_h__
+
+#include "prerror.h"
+#include "prio.h"
+#include "ErrorList.h"
+
+namespace mozilla {
+namespace net {
+
+nsresult AttachNetworkDataCountLayer(PRFileDesc* fd);
+
+// Get amount of sent bytes.
+void NetworkDataCountSent(PRFileDesc* fd, uint64_t& sentBytes);
+
+// Get amount of received bytes.
+void NetworkDataCountReceived(PRFileDesc* fd, uint64_t& receivedBytes);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // NetworkDataCountLayer_h__
diff --git a/netwerk/base/NetworkInfoServiceImpl.h b/netwerk/base/NetworkInfoServiceImpl.h
new file mode 100644
index 0000000000..218f31cb0c
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceImpl.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+#include "nsTHashMap.h"
+
+namespace mozilla {
+namespace net {
+
+using AddrMapType = nsTHashMap<nsCStringHashKey, nsCString>;
+
+nsresult DoListAddresses(AddrMapType& aAddrMap);
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/PollableEvent.cpp b/netwerk/base/PollableEvent.cpp
new file mode 100644
index 0000000000..a99d6d88f5
--- /dev/null
+++ b/netwerk/base/PollableEvent.cpp
@@ -0,0 +1,399 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransportService2.h"
+#include "PollableEvent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/DNS.h"
+#include "prerror.h"
+#include "prio.h"
+#include "private/pprio.h"
+#include "prnetdb.h"
+
+#ifdef XP_WIN
+# include "ShutdownLayer.h"
+#else
+# include <fcntl.h>
+# define USEPIPE 1
+#endif
+
+namespace mozilla {
+namespace net {
+
+#ifndef USEPIPE
+static PRDescIdentity sPollableEventLayerIdentity;
+static PRIOMethods sPollableEventLayerMethods;
+static PRIOMethods* sPollableEventLayerMethodsPtr = nullptr;
+
+static void LazyInitSocket() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (sPollableEventLayerMethodsPtr) {
+ return;
+ }
+ sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer");
+ sPollableEventLayerMethods = *PR_GetDefaultIOMethods();
+ sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods;
+}
+
+static bool NewTCPSocketPair(PRFileDesc* fd[], bool aSetRecvBuff) {
+ // this is a replacement for PR_NewTCPSocketPair that manually
+ // sets the recv buffer to 64K. A windows bug (1248358)
+ // can result in using an incompatible rwin and window
+ // scale option on localhost pipes if not set before connect.
+
+ SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n",
+ aSetRecvBuff ? "with" : "without"));
+
+ PRFileDesc* listener = nullptr;
+ PRFileDesc* writer = nullptr;
+ PRFileDesc* reader = nullptr;
+ PRSocketOptionData recvBufferOpt;
+ recvBufferOpt.option = PR_SockOpt_RecvBufferSize;
+ recvBufferOpt.value.recv_buffer_size = 65535;
+
+ PRSocketOptionData nodelayOpt;
+ nodelayOpt.option = PR_SockOpt_NoDelay;
+ nodelayOpt.value.no_delay = true;
+
+ PRSocketOptionData noblockOpt;
+ noblockOpt.option = PR_SockOpt_Nonblocking;
+ noblockOpt.value.non_blocking = true;
+
+ listener = PR_OpenTCPSocket(PR_AF_INET);
+ if (!listener) {
+ goto failed;
+ }
+
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(listener, &recvBufferOpt);
+ }
+ PR_SetSocketOption(listener, &nodelayOpt);
+
+ PRNetAddr listenAddr;
+ memset(&listenAddr, 0, sizeof(listenAddr));
+ if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) ||
+ (PR_Bind(listener, &listenAddr) == PR_FAILURE) ||
+ (PR_GetSockName(listener, &listenAddr) ==
+ PR_FAILURE) || // learn the dynamic port
+ (PR_Listen(listener, 5) == PR_FAILURE)) {
+ goto failed;
+ }
+
+ writer = PR_OpenTCPSocket(PR_AF_INET);
+ if (!writer) {
+ goto failed;
+ }
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(writer, &recvBufferOpt);
+ }
+ PR_SetSocketOption(writer, &nodelayOpt);
+ PR_SetSocketOption(writer, &noblockOpt);
+ PRNetAddr writerAddr;
+ if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port),
+ &writerAddr) == PR_FAILURE) {
+ goto failed;
+ }
+
+ if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) {
+ if ((PR_GetError() != PR_IN_PROGRESS_ERROR) ||
+ (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) {
+ goto failed;
+ }
+ }
+ PR_SetFDInheritable(writer, false);
+
+ reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200));
+ if (!reader) {
+ goto failed;
+ }
+ PR_SetFDInheritable(reader, false);
+ if (aSetRecvBuff) {
+ PR_SetSocketOption(reader, &recvBufferOpt);
+ }
+ PR_SetSocketOption(reader, &nodelayOpt);
+ PR_SetSocketOption(reader, &noblockOpt);
+ PR_Close(listener);
+
+ fd[0] = reader;
+ fd[1] = writer;
+ return true;
+
+failed:
+ if (listener) {
+ PR_Close(listener);
+ }
+ if (reader) {
+ PR_Close(reader);
+ }
+ if (writer) {
+ PR_Close(writer);
+ }
+ return false;
+}
+
+#endif
+
+PollableEvent::PollableEvent()
+
+{
+ MOZ_COUNT_CTOR(PollableEvent);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // create pair of prfiledesc that can be used as a poll()ble
+ // signal. on windows use a localhost socket pair, and on
+ // unix use a pipe.
+#ifdef USEPIPE
+ SOCKET_LOG(("PollableEvent() using pipe\n"));
+ if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) {
+ // make the pipe non blocking. NSPR asserts at
+ // trying to use SockOpt here
+ PROsfd fd = PR_FileDesc2NativeHandle(mReadFD);
+ int flags = fcntl(fd, F_GETFL, 0);
+ (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ fd = PR_FileDesc2NativeHandle(mWriteFD);
+ flags = fcntl(fd, F_GETFL, 0);
+ (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ } else {
+ mReadFD = nullptr;
+ mWriteFD = nullptr;
+ SOCKET_LOG(("PollableEvent() pipe failed\n"));
+ }
+#else
+ SOCKET_LOG(("PollableEvent() using socket pair\n"));
+ PRFileDesc* fd[2];
+ LazyInitSocket();
+
+ // Try with a increased recv buffer first (bug 1248358).
+ if (NewTCPSocketPair(fd, true)) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+ // If the previous fails try without recv buffer increase (bug 1305436).
+ } else if (NewTCPSocketPair(fd, false)) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+ // If both fail, try the old version.
+ } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) {
+ mReadFD = fd[0];
+ mWriteFD = fd[1];
+
+ PRSocketOptionData socket_opt;
+ DebugOnly<PRStatus> status;
+ socket_opt.option = PR_SockOpt_NoDelay;
+ socket_opt.value.no_delay = true;
+ PR_SetSocketOption(mWriteFD, &socket_opt);
+ PR_SetSocketOption(mReadFD, &socket_opt);
+ socket_opt.option = PR_SockOpt_Nonblocking;
+ socket_opt.value.non_blocking = true;
+ status = PR_SetSocketOption(mWriteFD, &socket_opt);
+ MOZ_ASSERT(status == PR_SUCCESS);
+ status = PR_SetSocketOption(mReadFD, &socket_opt);
+ MOZ_ASSERT(status == PR_SUCCESS);
+ }
+
+ if (mReadFD && mWriteFD) {
+ // compatibility with LSPs such as McAfee that assume a NSPR
+ // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a
+ // nop.
+ PRFileDesc* topLayer = PR_CreateIOLayerStub(sPollableEventLayerIdentity,
+ sPollableEventLayerMethodsPtr);
+ if (topLayer) {
+ if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) {
+ topLayer->dtor(topLayer);
+ } else {
+ SOCKET_LOG(("PollableEvent() nspr layer ok\n"));
+ mReadFD = topLayer;
+ }
+ }
+
+ } else {
+ SOCKET_LOG(("PollableEvent() socketpair failed\n"));
+ }
+#endif
+
+ if (mReadFD && mWriteFD) {
+ // prime the system to deal with races invovled in [dc]tor cycle
+ SOCKET_LOG(("PollableEvent() ctor ok\n"));
+ mSignaled = true;
+ MarkFirstSignalTimestamp();
+ PR_Write(mWriteFD, "I", 1);
+ }
+}
+
+PollableEvent::~PollableEvent() {
+ MOZ_COUNT_DTOR(PollableEvent);
+ if (mWriteFD) {
+#if defined(XP_WIN)
+ AttachShutdownLayer(mWriteFD);
+#endif
+ PR_Close(mWriteFD);
+ }
+ if (mReadFD) {
+#if defined(XP_WIN)
+ AttachShutdownLayer(mReadFD);
+#endif
+ PR_Close(mReadFD);
+ }
+}
+
+// we do not record signals on the socket thread
+// because the socket thread can reliably look at its
+// own runnable queue before selecting a poll time
+// this is the "service the network without blocking" comment in
+// nsSocketTransportService2.cpp
+bool PollableEvent::Signal() {
+ SOCKET_LOG(("PollableEvent::Signal\n"));
+
+ if (!mWriteFD) {
+ SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n"));
+ return false;
+ }
+#ifndef XP_WIN
+ // On windows poll can hang and this became worse when we introduced the
+ // patch for bug 698882 (see also bug 1292181), therefore we reverted the
+ // behavior on windows to be as before bug 698882, e.g. write to the socket
+ // also if an event dispatch is on the socket thread and writing to the
+ // socket for each event. See bug 1292181.
+ if (OnSocketThread()) {
+ SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n"));
+ return true;
+ }
+#endif
+
+#ifndef XP_WIN
+ // To wake up the poll writing once is enough, but for Windows that can cause
+ // hangs so we will write for every event.
+ // For non-Windows systems it is enough to write just once.
+ if (mSignaled) {
+ return true;
+ }
+#endif
+
+ if (!mSignaled) {
+ mSignaled = true;
+ MarkFirstSignalTimestamp();
+ }
+
+ int32_t status = PR_Write(mWriteFD, "M", 1);
+ SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status));
+ if (status != 1) {
+ NS_WARNING("PollableEvent::Signal Failed\n");
+ SOCKET_LOG(("PollableEvent::Signal Failed\n"));
+ mSignaled = false;
+ mWriteFailed = true;
+ } else {
+ mWriteFailed = false;
+ }
+ return (status == 1);
+}
+
+bool PollableEvent::Clear() {
+ // necessary because of the "dont signal on socket thread" optimization
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ SOCKET_LOG(("PollableEvent::Clear\n"));
+
+ if (!mFirstSignalAfterClear.IsNull()) {
+ SOCKET_LOG(("PollableEvent::Clear time to signal %ums",
+ (uint32_t)(TimeStamp::NowLoRes() - mFirstSignalAfterClear)
+ .ToMilliseconds()));
+ }
+
+ mFirstSignalAfterClear = TimeStamp();
+ mSignalTimestampAdjusted = false;
+ mSignaled = false;
+
+ if (!mReadFD) {
+ SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n"));
+ return false;
+ }
+
+ char buf[2048];
+ int32_t status;
+#ifdef XP_WIN
+ // On Windows we are writing to the socket for each event, to be sure that we
+ // do not have any deadlock read from the socket as much as we can.
+ while (true) {
+ status = PR_Read(mReadFD, buf, 2048);
+ SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status));
+ if (status == 0) {
+ SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
+ return false;
+ }
+ if (status < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ return true;
+ } else {
+ SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
+ return false;
+ }
+ }
+ }
+#else
+ status = PR_Read(mReadFD, buf, 2048);
+ SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status));
+
+ if (status == 1) {
+ return true;
+ }
+ if (status == 0) {
+ SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
+ return false;
+ }
+ if (status > 1) {
+ MOZ_ASSERT(false);
+ SOCKET_LOG(("PollableEvent::Clear Unexpected events\n"));
+ Clear();
+ return true;
+ }
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ return true;
+ }
+ SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
+ return false;
+#endif // XP_WIN
+}
+
+void PollableEvent::MarkFirstSignalTimestamp() {
+ if (mFirstSignalAfterClear.IsNull()) {
+ SOCKET_LOG(("PollableEvent::MarkFirstSignalTimestamp"));
+ mFirstSignalAfterClear = TimeStamp::NowLoRes();
+ }
+}
+
+void PollableEvent::AdjustFirstSignalTimestamp() {
+ if (!mSignalTimestampAdjusted && !mFirstSignalAfterClear.IsNull()) {
+ SOCKET_LOG(("PollableEvent::AdjustFirstSignalTimestamp"));
+ mFirstSignalAfterClear = TimeStamp::NowLoRes();
+ mSignalTimestampAdjusted = true;
+ }
+}
+
+bool PollableEvent::IsSignallingAlive(TimeDuration const& timeout) {
+ if (mWriteFailed) {
+ return false;
+ }
+
+#ifdef DEBUG
+ // The timeout would be just a disturbance in a debug build.
+ return true;
+#else
+ if (!mSignaled || mFirstSignalAfterClear.IsNull() ||
+ timeout == TimeDuration()) {
+ return true;
+ }
+
+ TimeDuration delay = (TimeStamp::NowLoRes() - mFirstSignalAfterClear);
+ bool timedOut = delay > timeout;
+
+ return !timedOut;
+#endif // DEBUG
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/PollableEvent.h b/netwerk/base/PollableEvent.h
new file mode 100644
index 0000000000..6a17cd909d
--- /dev/null
+++ b/netwerk/base/PollableEvent.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PollableEvent_h__
+#define PollableEvent_h__
+
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+
+struct PRFileDesc;
+
+namespace mozilla {
+namespace net {
+
+// class must be called locked
+class PollableEvent {
+ public:
+ PollableEvent();
+ ~PollableEvent();
+
+ // Signal/Clear return false only if they fail
+ bool Signal();
+ // This is called only when we get non-null out_flags for the socket pair
+ bool Clear();
+ bool Valid() { return mWriteFD && mReadFD; }
+
+ // We want to detect if writing to one of the socket pair sockets takes
+ // too long to be received by the other socket from the pair.
+ // Hence, we remember the timestamp of the earliest write by a call to
+ // MarkFirstSignalTimestamp() from Signal(). After waking up from poll()
+ // we check how long it took get the 'readable' signal on the socket pair.
+ void MarkFirstSignalTimestamp();
+ // Called right before we enter poll() to exclude any possible delay between
+ // the earlist call to Signal() and entering poll() caused by processing
+ // of events dispatched to the socket transport thread.
+ void AdjustFirstSignalTimestamp();
+ // This returns false on following conditions:
+ // - PR_Write has failed
+ // - no out_flags were signalled on the socket pair for too long after
+ // the earliest Signal()
+ bool IsSignallingAlive(TimeDuration const& timeout);
+
+ PRFileDesc* PollableFD() { return mReadFD; }
+
+ private:
+ PRFileDesc* mWriteFD{nullptr};
+ PRFileDesc* mReadFD{nullptr};
+ bool mSignaled{false};
+ // true when PR_Write to the socket pair has failed (status < 1)
+ bool mWriteFailed{false};
+ // Set true after AdjustFirstSignalTimestamp() was called
+ // Set false after Clear() was called
+ // Ensures shifting the timestamp before entering poll() only once
+ // between Clear()'ings.
+ bool mSignalTimestampAdjusted{false};
+ // Timestamp of the first call to Signal() (or time we enter poll())
+ // that happened after the last Clear() call
+ TimeStamp mFirstSignalAfterClear;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp
new file mode 100644
index 0000000000..de19d0e06e
--- /dev/null
+++ b/netwerk/base/Predictor.cpp
@@ -0,0 +1,2438 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "Predictor.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsICacheStorage.h"
+#include "nsICachingChannel.h"
+#include "nsICancelable.h"
+#include "nsIChannel.h"
+#include "nsContentUtils.h"
+#include "nsIDNSService.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFile.h"
+#include "nsIHttpChannel.h"
+#include "nsIInputStream.h"
+#include "nsILoadContext.h"
+#include "nsILoadContextInfo.h"
+#include "nsILoadGroup.h"
+#include "nsINetworkPredictorVerifier.h"
+#include "nsIObserverService.h"
+#include "nsISpeculativeConnect.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
+
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoParent.h"
+
+#include "LoadContextInfo.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/net/NeckoChild.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ClearOnShutdown.h"
+
+#include "CacheControlParser.h"
+#include "ReferrerInfo.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace net {
+
+Predictor* Predictor::sSelf = nullptr;
+
+static LazyLogModule gPredictorLog("NetworkPredictor");
+
+#define PREDICTOR_LOG(args) \
+ MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args)
+
+#define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC)
+
+// All these time values are in sec
+static const uint32_t ONE_DAY = 86400U;
+static const uint32_t ONE_WEEK = 7U * ONE_DAY;
+static const uint32_t ONE_MONTH = 30U * ONE_DAY;
+static const uint32_t ONE_YEAR = 365U * ONE_DAY;
+
+// Version of metadata entries we expect
+static const uint32_t METADATA_VERSION = 1;
+
+// Flags available in entries
+// FLAG_PREFETCHABLE - we have determined that this item is eligible for
+// prefetch
+static const uint32_t FLAG_PREFETCHABLE = 1 << 0;
+
+// We save 12 bits in the "flags" section of our metadata for actual flags, the
+// rest are to keep track of a rolling count of which loads a resource has been
+// used on to determine if we can prefetch that resource or not;
+static const uint8_t kRollingLoadOffset = 12;
+static const int32_t kMaxPrefetchRollingLoadCount = 20;
+static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1);
+
+// ID Extensions for cache entries
+#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
+
+// Get the full origin (scheme, host, port) out of a URI (maybe should be part
+// of nsIURI instead?)
+static nsresult ExtractOrigin(nsIURI* uri, nsIURI** originUri) {
+ nsAutoCString s;
+ nsresult rv = nsContentUtils::GetWebExposedOriginSerialization(uri, s);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewURI(originUri, s);
+}
+
+// All URIs we get passed *must* be http or https if they're not null. This
+// helps ensure that.
+static bool IsNullOrHttp(nsIURI* uri) {
+ if (!uri) {
+ return true;
+ }
+
+ return uri->SchemeIs("http") || uri->SchemeIs("https");
+}
+
+// Listener for the speculative DNS requests we'll fire off, which just ignores
+// the result (since we're just trying to warm the cache). This also exists to
+// reduce round-trips to the main thread, by being something threadsafe the
+// Predictor can use.
+
+NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener);
+
+NS_IMETHODIMP
+Predictor::DNSListener::OnLookupComplete(nsICancelable* request,
+ nsIDNSRecord* rec, nsresult status) {
+ return NS_OK;
+}
+
+// Class to proxy important information from the initial predictor call through
+// the cache API and back into the internals of the predictor. We can't use the
+// predictor itself, as it may have multiple actions in-flight, and each action
+// has different parameters.
+NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback);
+
+Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
+ nsIURI* targetURI, nsIURI* sourceURI,
+ nsINetworkPredictorVerifier* verifier,
+ Predictor* predictor)
+ : mFullUri(fullUri),
+ mPredict(predict),
+ mTargetURI(targetURI),
+ mSourceURI(sourceURI),
+ mVerifier(verifier),
+ mStackCount(0),
+ mPredictor(predictor) {
+ mStartTime = TimeStamp::Now();
+ if (mPredict) {
+ mPredictReason = reason.mPredict;
+ } else {
+ mLearnReason = reason.mLearn;
+ }
+}
+
+Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
+ nsIURI* targetURI, nsIURI* sourceURI,
+ nsINetworkPredictorVerifier* verifier,
+ Predictor* predictor, uint8_t stackCount)
+ : mFullUri(fullUri),
+ mPredict(predict),
+ mTargetURI(targetURI),
+ mSourceURI(sourceURI),
+ mVerifier(verifier),
+ mStackCount(stackCount),
+ mPredictor(predictor) {
+ mStartTime = TimeStamp::Now();
+ if (mPredict) {
+ mPredictReason = reason.mPredict;
+ } else {
+ mLearnReason = reason.mLearn;
+ }
+}
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
+ nsresult result) {
+ MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
+
+ nsAutoCString targetURI, sourceURI;
+ mTargetURI->GetAsciiSpec(targetURI);
+ if (mSourceURI) {
+ mSourceURI->GetAsciiSpec(sourceURI);
+ }
+ PREDICTOR_LOG(
+ ("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
+ "mPredictReason=%d mLearnReason=%d mTargetURI=%s "
+ "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08" PRIx32,
+ this, entry, mFullUri, mPredict, mPredictReason, mLearnReason,
+ targetURI.get(), sourceURI.get(), mStackCount, isNew,
+ static_cast<uint32_t>(result)));
+ if (NS_FAILED(result)) {
+ PREDICTOR_LOG(
+ ("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08" PRIX32
+ "). Aborting.",
+ this, static_cast<uint32_t>(result)));
+ return NS_OK;
+ }
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME, mStartTime);
+ if (mPredict) {
+ bool predicted =
+ mPredictor->PredictInternal(mPredictReason, entry, isNew, mFullUri,
+ mTargetURI, mVerifier, mStackCount);
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREDICT_WORK_TIME,
+ mStartTime);
+ if (predicted) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime);
+ } else {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime);
+ }
+ } else {
+ mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI,
+ mSourceURI);
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_LEARN_WORK_TIME,
+ mStartTime);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Predictor, nsINetworkPredictor, nsIObserver,
+ nsISpeculativeConnectionOverrider, nsIInterfaceRequestor,
+ nsICacheEntryMetaDataVisitor, nsINetworkPredictorVerifier)
+
+Predictor::Predictor()
+
+{
+ MOZ_ASSERT(!sSelf, "multiple Predictor instances!");
+ sSelf = this;
+}
+
+Predictor::~Predictor() {
+ if (mInitialized) Shutdown();
+
+ sSelf = nullptr;
+}
+
+// Predictor::nsIObserver
+
+nsresult Predictor::InstallObserver() {
+ MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+void Predictor::RemoveObserver() {
+ MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+}
+
+NS_IMETHODIMP
+Predictor::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data_unicode) {
+ nsresult rv = NS_OK;
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor observing something off main thread!");
+
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ Shutdown();
+ }
+
+ return rv;
+}
+
+// Predictor::nsISpeculativeConnectionOverrider
+
+NS_IMETHODIMP
+Predictor::GetIgnoreIdle(bool* ignoreIdle) {
+ *ignoreIdle = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetParallelSpeculativeConnectLimit(
+ uint32_t* parallelSpeculativeConnectLimit) {
+ *parallelSpeculativeConnectLimit = 6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetIsFromPredictor(bool* isFromPredictor) {
+ *isFromPredictor = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::GetAllow1918(bool* allow1918) {
+ *allow1918 = false;
+ return NS_OK;
+}
+
+// Predictor::nsIInterfaceRequestor
+
+NS_IMETHODIMP
+Predictor::GetInterface(const nsIID& iid, void** result) {
+ return QueryInterface(iid, result);
+}
+
+// Predictor::nsICacheEntryMetaDataVisitor
+
+#define SEEN_META_DATA "predictor::seen"
+#define RESOURCE_META_DATA "predictor::resource-count"
+#define META_DATA_PREFIX "predictor::"
+
+static bool IsURIMetadataElement(const char* key) {
+ return StringBeginsWith(nsDependentCString(key),
+ nsLiteralCString(META_DATA_PREFIX)) &&
+ !nsLiteralCString(SEEN_META_DATA).Equals(key) &&
+ !nsLiteralCString(RESOURCE_META_DATA).Equals(key);
+}
+
+nsresult Predictor::OnMetaDataElement(const char* asciiKey,
+ const char* asciiValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(asciiKey)) {
+ // This isn't a bit of metadata we care about
+ return NS_OK;
+ }
+
+ nsCString key, value;
+ key.AssignASCII(asciiKey);
+ value.AssignASCII(asciiValue);
+ mKeysToOperateOn.AppendElement(key);
+ mValuesToOperateOn.AppendElement(value);
+
+ return NS_OK;
+}
+
+// Predictor::nsINetworkPredictor
+
+nsresult Predictor::Init() {
+ MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild());
+
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "Predictor::Init called off the main thread!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+
+ rv = InstallObserver();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLastStartupTime = mStartupTime = NOW_IN_SECONDS();
+
+ if (!mDNSListener) {
+ mDNSListener = new DNSListener();
+ }
+
+ mCacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewURI(getter_AddRefs(mStartupURI), "predictor://startup");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitialized = true;
+
+ return rv;
+}
+
+namespace {
+class PredictorLearnRunnable final : public Runnable {
+ public:
+ PredictorLearnRunnable(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason,
+ const OriginAttributes& oa)
+ : Runnable("PredictorLearnRunnable"),
+ mTargetURI(targetURI),
+ mSourceURI(sourceURI),
+ mReason(reason),
+ mOA(oa) {
+ MOZ_DIAGNOSTIC_ASSERT(targetURI, "Must have a target URI");
+ }
+
+ ~PredictorLearnRunnable() = default;
+
+ NS_IMETHOD Run() override {
+ if (!gNeckoChild) {
+ // This may have gone away between when this runnable was dispatched and
+ // when it actually runs, so let's be safe here, even though we asserted
+ // earlier.
+ PREDICTOR_LOG(("predictor::learn (async) gNeckoChild went away"));
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG(("predictor::learn (async) forwarding to parent"));
+ gNeckoChild->SendPredLearn(mTargetURI, mSourceURI, mReason, mOA);
+
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIURI> mTargetURI;
+ nsCOMPtr<nsIURI> mSourceURI;
+ PredictorLearnReason mReason;
+ const OriginAttributes mOA;
+};
+
+} // namespace
+
+void Predictor::Shutdown() {
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!");
+ return;
+ }
+
+ RemoveObserver();
+
+ mInitialized = false;
+}
+
+nsresult Predictor::Create(const nsIID& aIID, void** aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ RefPtr<Predictor> svc = new Predictor();
+ if (IsNeckoChild()) {
+ NeckoChild::InitNeckoChild();
+
+ // Child threads only need to be call into the public interface methods
+ // so we don't bother with initialization
+ return svc->QueryInterface(aIID, aResult);
+ }
+
+ rv = svc->Init();
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop"));
+ }
+
+ // We treat init failure the same as the service being disabled, since this
+ // is all an optimization anyway. No need to freak people out. That's why we
+ // gladly continue on QI'ing here.
+ rv = svc->QueryInterface(aIID, aResult);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+Predictor::Predict(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorPredictReason reason,
+ JS::Handle<JS::Value> originAttributes,
+ nsINetworkPredictorVerifier* verifier, JSContext* aCx) {
+ OriginAttributes attrs;
+
+ if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return PredictNative(targetURI, sourceURI, reason, attrs, verifier);
+}
+
+// Called from the main thread to initiate predictive actions
+NS_IMETHODIMP
+Predictor::PredictNative(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorPredictReason reason,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Predict"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" called on child process"));
+ // If two different threads are predicting concurently, this will be
+ // overwritten. Thankfully, we only use this in tests, which will
+ // overwrite mVerifier perhaps multiple times for each individual test;
+ // however, within each test, the multiple predict calls should have the
+ // same verifier.
+ if (verifier) {
+ PREDICTOR_LOG((" was given a verifier"));
+ mChildVerifier = verifier;
+ }
+ PREDICTOR_LOG((" forwarding to parent process"));
+ gNeckoChild->SendPredPredict(targetURI, sourceURI, reason, originAttributes,
+ verifier);
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_predictor_enabled()) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ if (originAttributes.mPrivateBrowsingId > 0) {
+ // Don't want to do anything in PB mode
+ PREDICTOR_LOG((" in PB mode"));
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ // Nothing we can do for non-HTTP[S] schemes
+ PREDICTOR_LOG((" got non-http[s] URI"));
+ return NS_OK;
+ }
+
+ // Ensure we've been given the appropriate arguments for the kind of
+ // prediction we're being asked to do
+ nsCOMPtr<nsIURI> uriKey = targetURI;
+ nsCOMPtr<nsIURI> originKey;
+ switch (reason) {
+ case nsINetworkPredictor::PREDICT_LINK:
+ if (!targetURI || !sourceURI) {
+ PREDICTOR_LOG((" link invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ // Link hover is a special case where we can predict without hitting the
+ // db, so let's go ahead and fire off that prediction here.
+ PredictForLink(targetURI, sourceURI, originAttributes, verifier);
+ return NS_OK;
+ case nsINetworkPredictor::PREDICT_LOAD:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" load invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ break;
+ case nsINetworkPredictor::PREDICT_STARTUP:
+ if (targetURI || sourceURI) {
+ PREDICTOR_LOG((" startup invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ uriKey = mStartupURI;
+ originKey = mStartupURI;
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Predictor::Reason argReason{};
+ argReason.mPredict = reason;
+
+ // First we open the regular cache entry, to ensure we don't gum up the works
+ // waiting on the less-important predictor-only cache entry
+ RefPtr<Predictor::Action> uriAction = new Predictor::Action(
+ Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, argReason,
+ targetURI, nullptr, verifier, this);
+ nsAutoCString uriKeyStr;
+ uriKey->GetAsciiSpec(uriKeyStr);
+ PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
+ reason, uriAction.get()));
+
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
+
+ nsresult rv = mCacheStorageService->DiskCacheStorage(
+ lci, getter_AddRefs(cacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t openFlags =
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
+ cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, openFlags, uriAction);
+
+ // Now we do the origin-only (and therefore predictor-only) entry
+ nsCOMPtr<nsIURI> targetOrigin;
+ rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!originKey) {
+ originKey = targetOrigin;
+ }
+
+ RefPtr<Predictor::Action> originAction = new Predictor::Action(
+ Predictor::Action::IS_ORIGIN, Predictor::Action::DO_PREDICT, argReason,
+ targetOrigin, nullptr, verifier, this);
+ nsAutoCString originKeyStr;
+ originKey->GetAsciiSpec(originKeyStr);
+ PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p",
+ originKeyStr.get(), reason, originAction.get()));
+ openFlags = nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ cacheDiskStorage->AsyncOpenURI(originKey,
+ nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION),
+ openFlags, originAction);
+
+ PREDICTOR_LOG((" predict returning"));
+ return NS_OK;
+}
+
+bool Predictor::PredictInternal(PredictorPredictReason reason,
+ nsICacheEntry* entry, bool isNew, bool fullUri,
+ nsIURI* targetURI,
+ nsINetworkPredictorVerifier* verifier,
+ uint8_t stackCount) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictInternal"));
+ bool rv = false;
+
+ nsCOMPtr<nsILoadContextInfo> lci;
+ entry->GetLoadContextInfo(getter_AddRefs(lci));
+
+ if (!lci) {
+ return rv;
+ }
+
+ if (reason == nsINetworkPredictor::PREDICT_LOAD) {
+ MaybeLearnForStartup(targetURI, fullUri, *lci->OriginAttributesPtr());
+ }
+
+ if (isNew) {
+ // nothing else we can do here
+ PREDICTOR_LOG((" new entry"));
+ return rv;
+ }
+
+ switch (reason) {
+ case nsINetworkPredictor::PREDICT_LOAD:
+ rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier);
+ break;
+ case nsINetworkPredictor::PREDICT_STARTUP:
+ rv = PredictForStartup(entry, fullUri, verifier);
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ MOZ_ASSERT(false, "Got unexpected value for prediction reason");
+ }
+
+ return rv;
+}
+
+void Predictor::PredictForLink(nsIURI* targetURI, nsIURI* sourceURI,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForLink"));
+ if (!mSpeculativeService) {
+ PREDICTOR_LOG((" missing speculative service"));
+ return;
+ }
+
+ if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) {
+ if (sourceURI->SchemeIs("https")) {
+ // We don't want to predict from an HTTPS page, to avoid info leakage
+ PREDICTOR_LOG((" Not predicting for link hover - on an SSL page"));
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(targetURI, originAttributes);
+
+ mSpeculativeService->SpeculativeConnect(targetURI, principal, nullptr, false);
+ if (verifier) {
+ PREDICTOR_LOG((" sending verification"));
+ verifier->OnPredictPreconnect(targetURI);
+ }
+}
+
+// This is the driver for prediction based on a new pageload.
+static const uint8_t MAX_PAGELOAD_DEPTH = 10;
+bool Predictor::PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI,
+ uint8_t stackCount, bool fullUri,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForPageload"));
+
+ if (stackCount > MAX_PAGELOAD_DEPTH) {
+ PREDICTOR_LOG((" exceeded recursion depth!"));
+ return false;
+ }
+
+ uint32_t lastLoad;
+ nsresult rv = entry->GetLastFetched(&lastLoad);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
+ PREDICTOR_LOG((" globalDegradation = %d", globalDegradation));
+
+ uint32_t loadCount;
+ rv = entry->GetFetchCount(&loadCount);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsILoadContextInfo> lci;
+
+ rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIURI> redirectURI;
+ if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation,
+ getter_AddRefs(redirectURI))) {
+ mPreconnects.AppendElement(redirectURI);
+ Predictor::Reason reason{};
+ reason.mPredict = nsINetworkPredictor::PREDICT_LOAD;
+ RefPtr<Predictor::Action> redirectAction = new Predictor::Action(
+ Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, reason,
+ redirectURI, nullptr, verifier, this, stackCount + 1);
+ nsAutoCString redirectUriString;
+ redirectURI->GetAsciiSpec(redirectUriString);
+
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ rv = mCacheStorageService->DiskCacheStorage(
+ lci, getter_AddRefs(cacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ PREDICTOR_LOG((" Predict redirect uri=%s action=%p",
+ redirectUriString.get(), redirectAction.get()));
+ uint32_t openFlags =
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
+ cacheDiskStorage->AsyncOpenURI(redirectURI, ""_ns, openFlags,
+ redirectAction);
+ return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
+ }
+
+ CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation,
+ fullUri);
+
+ return RunPredictions(targetURI, *lci->OriginAttributesPtr(), verifier);
+}
+
+// This is the driver for predicting at browser startup time based on pages that
+// have previously been loaded close to startup.
+bool Predictor::PredictForStartup(nsICacheEntry* entry, bool fullUri,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForStartup"));
+
+ nsCOMPtr<nsILoadContextInfo> lci;
+
+ nsresult rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime);
+ CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount,
+ globalDegradation, fullUri);
+ return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
+}
+
+// This calculates how much to degrade our confidence in our data based on
+// the last time this top-level resource was loaded. This "global degradation"
+// applies to *all* subresources we have associated with the top-level
+// resource. This will be in addition to any reduction in confidence we have
+// associated with a particular subresource.
+int32_t Predictor::CalculateGlobalDegradation(uint32_t lastLoad) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int32_t globalDegradation;
+ uint32_t delta = NOW_IN_SECONDS() - lastLoad;
+ if (delta < ONE_DAY) {
+ globalDegradation = StaticPrefs::network_predictor_page_degradation_day();
+ } else if (delta < ONE_WEEK) {
+ globalDegradation = StaticPrefs::network_predictor_page_degradation_week();
+ } else if (delta < ONE_MONTH) {
+ globalDegradation = StaticPrefs::network_predictor_page_degradation_month();
+ } else if (delta < ONE_YEAR) {
+ globalDegradation = StaticPrefs::network_predictor_page_degradation_year();
+ } else {
+ globalDegradation = StaticPrefs::network_predictor_page_degradation_max();
+ }
+
+ Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION,
+ globalDegradation);
+ return globalDegradation;
+}
+
+// This calculates our overall confidence that a particular subresource will be
+// loaded as part of a top-level load.
+// @param hitCount - the number of times we have loaded this subresource as part
+// of this top-level load
+// @param hitsPossible - the number of times we have performed this top-level
+// load
+// @param lastHit - the timestamp of the last time we loaded this subresource as
+// part of this top-level load
+// @param lastPossible - the timestamp of the last time we performed this
+// top-level load
+// @param globalDegradation - the degradation for this top-level load as
+// determined by CalculateGlobalDegradation
+int32_t Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
+ uint32_t lastHit, uint32_t lastPossible,
+ int32_t globalDegradation) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED>
+ predictionsCalculated;
+ ++predictionsCalculated;
+
+ if (!hitsPossible) {
+ return 0;
+ }
+
+ int32_t baseConfidence = (hitCount * 100) / hitsPossible;
+ int32_t maxConfidence = 100;
+ int32_t confidenceDegradation = 0;
+
+ if (lastHit < lastPossible) {
+ // We didn't load this subresource the last time this top-level load was
+ // performed, so let's not bother preconnecting (at the very least).
+ maxConfidence =
+ StaticPrefs::network_predictor_preconnect_min_confidence() - 1;
+
+ // Now calculate how much we want to degrade our confidence based on how
+ // long it's been between the last time we did this top-level load and the
+ // last time this top-level load included this subresource.
+ PRTime delta = lastPossible - lastHit;
+ if (delta == 0) {
+ confidenceDegradation = 0;
+ } else if (delta < ONE_DAY) {
+ confidenceDegradation =
+ StaticPrefs::network_predictor_subresource_degradation_day();
+ } else if (delta < ONE_WEEK) {
+ confidenceDegradation =
+ StaticPrefs::network_predictor_subresource_degradation_week();
+ } else if (delta < ONE_MONTH) {
+ confidenceDegradation =
+ StaticPrefs::network_predictor_subresource_degradation_month();
+ } else if (delta < ONE_YEAR) {
+ confidenceDegradation =
+ StaticPrefs::network_predictor_subresource_degradation_year();
+ } else {
+ confidenceDegradation =
+ StaticPrefs::network_predictor_subresource_degradation_max();
+ maxConfidence = 0;
+ }
+ }
+
+ // Calculate our confidence and clamp it to between 0 and maxConfidence
+ // (<= 100)
+ int32_t confidence =
+ baseConfidence - confidenceDegradation - globalDegradation;
+ confidence = std::max(confidence, 0);
+ confidence = std::min(confidence, maxConfidence);
+
+ Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence);
+ Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION,
+ confidenceDegradation);
+ Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence);
+ return confidence;
+}
+
+static void MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit,
+ const uint32_t flags, nsCString& newValue) {
+ newValue.Truncate();
+ newValue.AppendInt(METADATA_VERSION);
+ newValue.Append(',');
+ newValue.AppendInt(hitCount);
+ newValue.Append(',');
+ newValue.AppendInt(lastHit);
+ newValue.Append(',');
+ newValue.AppendInt(flags);
+}
+
+// On every page load, the rolling window gets shifted by one bit, leaving the
+// lowest bit at 0, to indicate that the subresource in question has not been
+// seen on the most recent page load. If, at some point later during the page
+// load, the subresource is seen again, we will then set the lowest bit to 1.
+// This is how we keep track of how many of the last n pageloads (for n <= 20) a
+// particular subresource has been seen. The rolling window is kept in the upper
+// 20 bits of the flags element of the metadata. This saves 12 bits for regular
+// old flags.
+void Predictor::UpdateRollingLoadCount(nsICacheEntry* entry,
+ const uint32_t flags, const char* key,
+ const uint32_t hitCount,
+ const uint32_t lastHit) {
+ // Extract just the rolling load count from the flags, shift it to clear the
+ // lowest bit, and put the new value with the existing flags.
+ uint32_t rollingLoadCount = flags & ~kFlagsMask;
+ rollingLoadCount <<= 1;
+ uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount;
+
+ // Finally, update the metadata on the cache entry.
+ nsAutoCString newValue;
+ MakeMetadataEntry(hitCount, lastHit, newFlags, newValue);
+ entry->SetMetaDataElement(key, newValue.BeginReading());
+}
+
+uint32_t Predictor::ClampedPrefetchRollingLoadCount() {
+ int32_t n = StaticPrefs::network_predictor_prefetch_rolling_load_count();
+ if (n < 0) {
+ return 0;
+ }
+ if (n > kMaxPrefetchRollingLoadCount) {
+ return kMaxPrefetchRollingLoadCount;
+ }
+ return n;
+}
+
+void Predictor::CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer,
+ uint32_t lastLoad, uint32_t loadCount,
+ int32_t globalDegradation, bool fullUri) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Since the visitor gets called under a cache lock, all we do there is get
+ // copies of the keys/values we care about, and then do the real work here
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToOperateOn = std::move(mKeysToOperateOn),
+ valuesToOperateOn = std::move(mValuesToOperateOn);
+
+ MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
+ for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
+ const char* key = keysToOperateOn[i].BeginReading();
+ const char* value = valuesToOperateOn[i].BeginReading();
+
+ nsCString uri;
+ uint32_t hitCount, lastHit, flags;
+ if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
+ // This failed, get rid of it so we don't waste space
+ entry->SetMetaDataElement(key, nullptr);
+ continue;
+ }
+
+ int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit,
+ lastLoad, globalDegradation);
+ if (fullUri) {
+ UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
+ }
+ PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key,
+ value, confidence));
+ PrefetchIgnoreReason reason = PREFETCH_OK;
+ if (!fullUri) {
+ // Not full URI - don't prefetch! No sense in it!
+ PREDICTOR_LOG((" forcing non-cacheability - not full URI"));
+ if (flags & FLAG_PREFETCHABLE) {
+ // This only applies if we had somehow otherwise marked this
+ // prefetchable.
+ reason = NOT_FULL_URI;
+ }
+ flags &= ~FLAG_PREFETCHABLE;
+ } else if (!referrer) {
+ // No referrer means we can't prefetch, so pretend it's non-cacheable,
+ // no matter what.
+ PREDICTOR_LOG((" forcing non-cacheability - no referrer"));
+ if (flags & FLAG_PREFETCHABLE) {
+ // This only applies if we had somehow otherwise marked this
+ // prefetchable.
+ reason = NO_REFERRER;
+ }
+ flags &= ~FLAG_PREFETCHABLE;
+ } else {
+ uint32_t expectedRollingLoadCount =
+ (1 << ClampedPrefetchRollingLoadCount()) - 1;
+ expectedRollingLoadCount <<= kRollingLoadOffset;
+ if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) {
+ PREDICTOR_LOG((" forcing non-cacheability - missed a load"));
+ if (flags & FLAG_PREFETCHABLE) {
+ // This only applies if we had somehow otherwise marked this
+ // prefetchable.
+ reason = MISSED_A_LOAD;
+ }
+ flags &= ~FLAG_PREFETCHABLE;
+ }
+ }
+
+ PREDICTOR_LOG((" setting up prediction"));
+ SetupPrediction(confidence, flags, uri, reason);
+ }
+}
+
+// (Maybe) adds a predictive action to the prediction runner, based on our
+// calculated confidence for the subresource in question.
+void Predictor::SetupPrediction(int32_t confidence, uint32_t flags,
+ const nsCString& uri,
+ PrefetchIgnoreReason earlyReason) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = NS_OK;
+ PREDICTOR_LOG(
+ ("SetupPrediction enable-prefetch=%d prefetch-min-confidence=%d "
+ "preconnect-min-confidence=%d preresolve-min-confidence=%d "
+ "flags=%d confidence=%d uri=%s",
+ StaticPrefs::network_predictor_enable_prefetch(),
+ StaticPrefs::network_predictor_prefetch_min_confidence(),
+ StaticPrefs::network_predictor_preconnect_min_confidence(),
+ StaticPrefs::network_predictor_preresolve_min_confidence(), flags,
+ confidence, uri.get()));
+
+ bool prefetchOk = !!(flags & FLAG_PREFETCHABLE);
+ PrefetchIgnoreReason reason = earlyReason;
+ if (prefetchOk && !StaticPrefs::network_predictor_enable_prefetch()) {
+ prefetchOk = false;
+ reason = PREFETCH_DISABLED;
+ } else if (prefetchOk && !ClampedPrefetchRollingLoadCount() &&
+ confidence <
+ StaticPrefs::network_predictor_prefetch_min_confidence()) {
+ prefetchOk = false;
+ if (!ClampedPrefetchRollingLoadCount()) {
+ reason = PREFETCH_DISABLED_VIA_COUNT;
+ } else {
+ reason = CONFIDENCE_TOO_LOW;
+ }
+ }
+
+ // prefetchOk == false and reason == PREFETCH_OK indicates that the reason
+ // we aren't prefetching this item is because it was marked un-prefetchable in
+ // our metadata. We already have separate telemetry on that decision, so we
+ // aren't going to accumulate more here. Right now we only care about why
+ // something we had marked prefetchable isn't being prefetched.
+ if (!prefetchOk && reason != PREFETCH_OK) {
+ Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_IGNORE_REASON, reason);
+ }
+
+ if (prefetchOk) {
+ nsCOMPtr<nsIURI> prefetchURI;
+ rv = NS_NewURI(getter_AddRefs(prefetchURI), uri);
+ if (NS_SUCCEEDED(rv)) {
+ mPrefetches.AppendElement(prefetchURI);
+ }
+ } else if (confidence >=
+ StaticPrefs::network_predictor_preconnect_min_confidence()) {
+ nsCOMPtr<nsIURI> preconnectURI;
+ rv = NS_NewURI(getter_AddRefs(preconnectURI), uri);
+ if (NS_SUCCEEDED(rv)) {
+ mPreconnects.AppendElement(preconnectURI);
+ }
+ } else if (confidence >=
+ StaticPrefs::network_predictor_preresolve_min_confidence()) {
+ nsCOMPtr<nsIURI> preresolveURI;
+ rv = NS_NewURI(getter_AddRefs(preresolveURI), uri);
+ if (NS_SUCCEEDED(rv)) {
+ mPreresolves.AppendElement(preresolveURI);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" NS_NewURI returned 0x%" PRIx32, static_cast<uint32_t>(rv)));
+ }
+}
+
+nsresult Predictor::Prefetch(nsIURI* uri, nsIURI* referrer,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier) {
+ nsAutoCString strUri, strReferrer;
+ uri->GetAsciiSpec(strUri);
+ referrer->GetAsciiSpec(strReferrer);
+ PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p",
+ strUri.get(), strReferrer.get(), verifier));
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(
+ getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */
+ nullptr, /* aPerformanceStorage */
+ nullptr, /* aLoadGroup */
+ nullptr, /* aCallbacks */
+ nsIRequest::LOAD_BACKGROUND);
+
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" NS_NewChannel failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ rv = loadInfo->SetOriginAttributes(originAttributes);
+
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" Set originAttributes into loadInfo failed rv=0x%" PRIX32,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ httpChannel = do_QueryInterface(channel);
+ if (!httpChannel) {
+ PREDICTOR_LOG((" Could not get HTTP Channel from new channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(referrer);
+ rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // XXX - set a header here to indicate this is a prefetch?
+
+ nsCOMPtr<nsIStreamListener> listener =
+ new PrefetchListener(verifier, uri, this);
+ PREDICTOR_LOG((" calling AsyncOpen listener=%p channel=%p", listener.get(),
+ channel.get()));
+ rv = channel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" AsyncOpen failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
+ }
+
+ return rv;
+}
+
+// Runs predictions that have been set up.
+bool Predictor::RunPredictions(nsIURI* referrer,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
+
+ PREDICTOR_LOG(("Predictor::RunPredictions"));
+
+ bool predicted = false;
+ uint32_t len, i;
+
+ nsTArray<nsCOMPtr<nsIURI>> prefetches = std::move(mPrefetches),
+ preconnects = std::move(mPreconnects),
+ preresolves = std::move(mPreresolves);
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS>
+ totalPredictions;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES> totalPrefetches;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS>
+ totalPreconnects;
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES>
+ totalPreresolves;
+
+ len = prefetches.Length();
+ for (i = 0; i < len; ++i) {
+ PREDICTOR_LOG((" doing prefetch"));
+ nsCOMPtr<nsIURI> uri = prefetches[i];
+ if (NS_SUCCEEDED(Prefetch(uri, referrer, originAttributes, verifier))) {
+ ++totalPredictions;
+ ++totalPrefetches;
+ predicted = true;
+ }
+ }
+
+ len = preconnects.Length();
+ for (i = 0; i < len; ++i) {
+ PREDICTOR_LOG((" doing preconnect"));
+ nsCOMPtr<nsIURI> uri = preconnects[i];
+ ++totalPredictions;
+ ++totalPreconnects;
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(uri, originAttributes);
+ mSpeculativeService->SpeculativeConnect(uri, principal, this, false);
+ predicted = true;
+ if (verifier) {
+ PREDICTOR_LOG((" sending preconnect verification"));
+ verifier->OnPredictPreconnect(uri);
+ }
+ }
+
+ len = preresolves.Length();
+ for (i = 0; i < len; ++i) {
+ nsCOMPtr<nsIURI> uri = preresolves[i];
+ ++totalPredictions;
+ ++totalPreresolves;
+ nsAutoCString hostname;
+ uri->GetAsciiHost(hostname);
+ PREDICTOR_LOG((" doing preresolve %s", hostname.get()));
+ nsCOMPtr<nsICancelable> tmpCancelable;
+ mDnsService->AsyncResolveNative(
+ hostname, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::RESOLVE_SPECULATE),
+ nullptr, mDNSListener, nullptr, originAttributes,
+ getter_AddRefs(tmpCancelable));
+
+ // Fetch HTTPS RR if needed.
+ if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
+ StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
+ mDnsService->AsyncResolveNative(
+ hostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::RESOLVE_SPECULATE),
+ nullptr, mDNSListener, nullptr, originAttributes,
+ getter_AddRefs(tmpCancelable));
+ }
+
+ predicted = true;
+ if (verifier) {
+ PREDICTOR_LOG((" sending preresolve verification"));
+ verifier->OnPredictDNS(uri);
+ }
+ }
+
+ return predicted;
+}
+
+// Find out if a top-level page is likely to redirect.
+bool Predictor::WouldRedirect(nsICacheEntry* entry, uint32_t loadCount,
+ uint32_t lastLoad, int32_t globalDegradation,
+ nsIURI** redirectURI) {
+ // TODO - not doing redirects for first go around
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return false;
+}
+
+NS_IMETHODIMP
+Predictor::Learn(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason,
+ JS::Handle<JS::Value> originAttributes, JSContext* aCx) {
+ OriginAttributes attrs;
+
+ if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return LearnNative(targetURI, sourceURI, reason, attrs);
+}
+
+// Called from the main thread to update the database
+NS_IMETHODIMP
+Predictor::LearnNative(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason,
+ const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Learn"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" called on child process"));
+
+ RefPtr<PredictorLearnRunnable> runnable = new PredictorLearnRunnable(
+ targetURI, sourceURI, reason, originAttributes);
+ SchedulerGroup::Dispatch(runnable.forget());
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_predictor_enabled()) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ if (originAttributes.mPrivateBrowsingId > 0) {
+ // Don't want to do anything in PB mode
+ PREDICTOR_LOG((" in PB mode"));
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ PREDICTOR_LOG((" got non-HTTP[S] URI"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIURI> targetOrigin;
+ nsCOMPtr<nsIURI> sourceOrigin;
+ nsCOMPtr<nsIURI> uriKey;
+ nsCOMPtr<nsIURI> originKey;
+ nsresult rv;
+
+ switch (reason) {
+ case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" load toplevel invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = targetURI;
+ originKey = targetOrigin;
+ break;
+ case nsINetworkPredictor::LEARN_STARTUP:
+ if (!targetURI || sourceURI) {
+ PREDICTOR_LOG((" startup invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = mStartupURI;
+ originKey = mStartupURI;
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
+ case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
+ if (!targetURI || !sourceURI) {
+ PREDICTOR_LOG((" redirect/subresource invalid URI state"));
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uriKey = sourceURI;
+ originKey = sourceOrigin;
+ break;
+ default:
+ PREDICTOR_LOG((" invalid reason"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts;
+ ++learnAttempts;
+
+ Predictor::Reason argReason{};
+ argReason.mLearn = reason;
+
+ // We always open the full uri (general cache) entry first, so we don't gum up
+ // the works waiting on predictor-only entries to open
+ RefPtr<Predictor::Action> uriAction = new Predictor::Action(
+ Predictor::Action::IS_FULL_URI, Predictor::Action::DO_LEARN, argReason,
+ targetURI, sourceURI, nullptr, this);
+ nsAutoCString uriKeyStr, targetUriStr, sourceUriStr;
+ uriKey->GetAsciiSpec(uriKeyStr);
+ targetURI->GetAsciiSpec(targetUriStr);
+ if (sourceURI) {
+ sourceURI->GetAsciiSpec(sourceUriStr);
+ }
+ PREDICTOR_LOG(
+ (" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
+ "action=%p",
+ uriKeyStr.get(), targetUriStr.get(), sourceUriStr.get(), reason,
+ uriAction.get()));
+
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
+
+ rv = mCacheStorageService->DiskCacheStorage(lci,
+ getter_AddRefs(cacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // For learning full URI things, we *always* open readonly and secretly, as we
+ // rely on actual pageloads to update the entry's metadata for us.
+ uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
+ // Learning for toplevel we want to open the full uri entry priority, since
+ // it's likely this entry will be used soon anyway, and we want this to be
+ // opened ASAP.
+ uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
+ }
+ cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, uriOpenFlags, uriAction);
+
+ // Now we open the origin-only (and therefore predictor-only) entry
+ RefPtr<Predictor::Action> originAction = new Predictor::Action(
+ Predictor::Action::IS_ORIGIN, Predictor::Action::DO_LEARN, argReason,
+ targetOrigin, sourceOrigin, nullptr, this);
+ nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr;
+ originKey->GetAsciiSpec(originKeyStr);
+ targetOrigin->GetAsciiSpec(targetOriginStr);
+ if (sourceOrigin) {
+ sourceOrigin->GetAsciiSpec(sourceOriginStr);
+ }
+ PREDICTOR_LOG(
+ (" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
+ "action=%p",
+ originKeyStr.get(), targetOriginStr.get(), sourceOriginStr.get(), reason,
+ originAction.get()));
+ uint32_t originOpenFlags;
+ if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
+ // This is the only case when we want to update the 'last used' metadata on
+ // the cache entry we're getting. This only applies to predictor-specific
+ // entries.
+ originOpenFlags =
+ nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
+ } else {
+ originOpenFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ }
+ cacheDiskStorage->AsyncOpenURI(originKey,
+ nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION),
+ originOpenFlags, originAction);
+
+ PREDICTOR_LOG(("Predictor::Learn returning"));
+ return NS_OK;
+}
+
+void Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry,
+ bool isNew, bool fullUri, nsIURI* targetURI,
+ nsIURI* sourceURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::LearnInternal"));
+
+ nsCString junk;
+ if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL &&
+ NS_FAILED(
+ entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) {
+ // This is an origin-only entry that we haven't seen before. Let's mark it
+ // as seen.
+ PREDICTOR_LOG((" marking new origin entry as seen"));
+ nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1");
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" failed to mark origin entry seen"));
+ return;
+ }
+
+ // Need to ensure someone else can get to the entry if necessary
+ entry->MetaDataReady();
+ return;
+ }
+
+ switch (reason) {
+ case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
+ // This case only exists to be used during tests - code outside the
+ // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL.
+ // The predictor xpcshell test needs this branch, however, because we
+ // have no real page loads in xpcshell, and this is how we fake it up
+ // so that all the work that normally happens behind the scenes in a
+ // page load can be done for testing purposes.
+ if (fullUri && StaticPrefs::network_predictor_doing_tests()) {
+ PREDICTOR_LOG(
+ (" WARNING - updating rolling load count. "
+ "If you see this outside tests, you did it wrong"));
+
+ // Since the visitor gets called under a cache lock, all we do there is
+ // get copies of the keys/values we care about, and then do the real
+ // work here
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToOperateOn = std::move(mKeysToOperateOn),
+ valuesToOperateOn = std::move(mValuesToOperateOn);
+
+ MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
+ for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
+ const char* key = keysToOperateOn[i].BeginReading();
+ const char* value = valuesToOperateOn[i].BeginReading();
+
+ nsCString uri;
+ uint32_t hitCount, lastHit, flags;
+ if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
+ // This failed, get rid of it so we don't waste space
+ entry->SetMetaDataElement(key, nullptr);
+ continue;
+ }
+ UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
+ }
+ } else {
+ PREDICTOR_LOG((" nothing to do for toplevel"));
+ }
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
+ if (fullUri) {
+ LearnForRedirect(entry, targetURI);
+ }
+ break;
+ case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
+ LearnForSubresource(entry, targetURI);
+ break;
+ case nsINetworkPredictor::LEARN_STARTUP:
+ LearnForStartup(entry, targetURI);
+ break;
+ default:
+ PREDICTOR_LOG((" unexpected reason value"));
+ MOZ_ASSERT(false, "Got unexpected value for learn reason!");
+ }
+}
+
+NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor)
+
+NS_IMETHODIMP
+Predictor::SpaceCleaner::OnMetaDataElement(const char* key, const char* value) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(key)) {
+ // This isn't a bit of metadata we care about
+ return NS_OK;
+ }
+
+ nsCString uri;
+ uint32_t hitCount, lastHit, flags;
+ bool ok =
+ mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags);
+
+ if (!ok) {
+ // Couldn't parse this one, just get rid of it
+ nsCString nsKey;
+ nsKey.AssignASCII(key);
+ mLongKeysToDelete.AppendElement(nsKey);
+ return NS_OK;
+ }
+
+ uint32_t uriLength = uri.Length();
+ if (uriLength > StaticPrefs::network_predictor_max_uri_length()) {
+ // Default to getting rid of URIs that are too long and were put in before
+ // we had our limit on URI length, in order to free up some space.
+ nsCString nsKey;
+ nsKey.AssignASCII(key);
+ mLongKeysToDelete.AppendElement(nsKey);
+ return NS_OK;
+ }
+
+ if (!mLRUKeyToDelete || lastHit < mLRUStamp) {
+ mLRUKeyToDelete = key;
+ mLRUStamp = lastHit;
+ }
+
+ return NS_OK;
+}
+
+void Predictor::SpaceCleaner::Finalize(nsICacheEntry* entry) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mLRUKeyToDelete) {
+ entry->SetMetaDataElement(mLRUKeyToDelete, nullptr);
+ }
+
+ for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) {
+ entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr);
+ }
+}
+
+// Called when a subresource has been hit from a top-level load. Uses the two
+// helper functions above to update the database appropriately.
+void Predictor::LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::LearnForSubresource"));
+
+ uint32_t lastLoad;
+ nsresult rv = entry->GetLastFetched(&lastLoad);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ uint32_t loadCount;
+ rv = entry->GetFetchCount(&loadCount);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCString key;
+ key.AssignLiteral(META_DATA_PREFIX);
+ nsCString uri;
+ targetURI->GetAsciiSpec(uri);
+ key.Append(uri);
+ if (uri.Length() > StaticPrefs::network_predictor_max_uri_length()) {
+ // We do this to conserve space/prevent OOMs
+ PREDICTOR_LOG((" uri too long!"));
+ entry->SetMetaDataElement(key.BeginReading(), nullptr);
+ return;
+ }
+
+ nsCString value;
+ rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value));
+
+ uint32_t hitCount, lastHit, flags;
+ bool isNewResource =
+ (NS_FAILED(rv) ||
+ !ParseMetaDataEntry(key.BeginReading(), value.BeginReading(), uri,
+ hitCount, lastHit, flags));
+
+ int32_t resourceCount = 0;
+ if (isNewResource) {
+ // This is a new addition
+ PREDICTOR_LOG((" new resource"));
+ nsCString s;
+ rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s));
+ if (NS_SUCCEEDED(rv)) {
+ resourceCount = atoi(s.BeginReading());
+ }
+ if (resourceCount >=
+ StaticPrefs::network_predictor_max_resources_per_entry()) {
+ RefPtr<Predictor::SpaceCleaner> cleaner =
+ new Predictor::SpaceCleaner(this);
+ entry->VisitMetaData(cleaner);
+ cleaner->Finalize(entry);
+ } else {
+ ++resourceCount;
+ }
+ nsAutoCString count;
+ count.AppendInt(resourceCount);
+ rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" failed to update resource count"));
+ return;
+ }
+ hitCount = 1;
+ flags = 0;
+ } else {
+ PREDICTOR_LOG((" existing resource"));
+ hitCount = std::min(hitCount + 1, loadCount);
+ }
+
+ // Update the rolling load count to mark this sub-resource as seen on the
+ // most-recent pageload so it can be eligible for prefetch (assuming all
+ // the other stars align).
+ flags |= (1 << kRollingLoadOffset);
+
+ nsCString newValue;
+ MakeMetadataEntry(hitCount, lastLoad, flags, newValue);
+ rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading());
+ PREDICTOR_LOG(
+ (" SetMetaDataElement -> 0x%08" PRIX32, static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv) && isNewResource) {
+ // Roll back the increment to the resource count we made above.
+ PREDICTOR_LOG((" rolling back resource count update"));
+ --resourceCount;
+ if (resourceCount == 0) {
+ entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr);
+ } else {
+ nsAutoCString count;
+ count.AppendInt(resourceCount);
+ entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
+ }
+ }
+}
+
+// This is called when a top-level loaded ended up redirecting to a different
+// URI so we can keep track of that fact.
+void Predictor::LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO - not doing redirects for first go around
+ PREDICTOR_LOG(("Predictor::LearnForRedirect"));
+}
+
+// This will add a page to our list of startup pages if it's being loaded
+// before our startup window has expired.
+void Predictor::MaybeLearnForStartup(nsIURI* uri, bool fullUri,
+ const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO - not doing startup for first go around
+ PREDICTOR_LOG(("Predictor::MaybeLearnForStartup"));
+}
+
+// Add information about a top-level load to our list of startup pages
+void Predictor::LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // These actually do the same set of work, just on different entries, so we
+ // can pass through to get the real work done here
+ PREDICTOR_LOG(("Predictor::LearnForStartup"));
+ LearnForSubresource(entry, targetURI);
+}
+
+bool Predictor::ParseMetaDataEntry(const char* key, const char* value,
+ nsCString& uri, uint32_t& hitCount,
+ uint32_t& lastHit, uint32_t& flags) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(
+ ("Predictor::ParseMetaDataEntry key=%s value=%s", key ? key : "", value));
+
+ const char* comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find first comma"));
+ return false;
+ }
+
+ uint32_t version = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" version -> %u", version));
+
+ if (version != METADATA_VERSION) {
+ PREDICTOR_LOG(
+ (" metadata version mismatch %u != %u", version, METADATA_VERSION));
+ return false;
+ }
+
+ value = comma + 1;
+ comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find second comma"));
+ return false;
+ }
+
+ hitCount = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" hitCount -> %u", hitCount));
+
+ value = comma + 1;
+ comma = strchr(value, ',');
+ if (!comma) {
+ PREDICTOR_LOG((" could not find third comma"));
+ return false;
+ }
+
+ lastHit = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" lastHit -> %u", lastHit));
+
+ value = comma + 1;
+ flags = static_cast<uint32_t>(atoi(value));
+ PREDICTOR_LOG((" flags -> %u", flags));
+
+ if (key) {
+ const char* uriStart = key + (sizeof(META_DATA_PREFIX) - 1);
+ uri.AssignASCII(uriStart);
+ PREDICTOR_LOG((" uri -> %s", uriStart));
+ } else {
+ uri.Truncate();
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+Predictor::Reset() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Predictor interface methods must be called on the main thread");
+
+ PREDICTOR_LOG(("Predictor::Reset"));
+
+ if (IsNeckoChild()) {
+ MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+ PREDICTOR_LOG((" forwarding to parent process"));
+ gNeckoChild->SendPredReset();
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_predictor_enabled()) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ RefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this);
+ PREDICTOR_LOG((" created a resetter"));
+ mCacheStorageService->AsyncVisitAllStorages(reset, true);
+ PREDICTOR_LOG((" Cache async launched, returning now"));
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Predictor::Resetter, nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor, nsICacheStorageVisitor);
+
+Predictor::Resetter::Resetter(Predictor* predictor)
+ : mEntriesToVisit(0), mPredictor(predictor) {}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
+ nsresult result) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(result)) {
+ // This can happen when we've tried to open an entry that doesn't exist for
+ // some non-reset operation, and then get reset shortly thereafter (as
+ // happens in some of our tests).
+ --mEntriesToVisit;
+ if (!mEntriesToVisit) {
+ Complete();
+ }
+ return NS_OK;
+ }
+
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToDelete = std::move(mKeysToDelete);
+
+ for (size_t i = 0; i < keysToDelete.Length(); ++i) {
+ const char* key = keysToDelete[i].BeginReading();
+ entry->SetMetaDataElement(key, nullptr);
+ }
+
+ --mEntriesToVisit;
+ if (!mEntriesToVisit) {
+ Complete();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnMetaDataElement(const char* asciiKey,
+ const char* asciiValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!StringBeginsWith(nsDependentCString(asciiKey),
+ nsLiteralCString(META_DATA_PREFIX))) {
+ // Not a metadata entry we care about, carry on
+ return NS_OK;
+ }
+
+ nsCString key;
+ key.AssignASCII(asciiKey);
+ mKeysToDelete.AppendElement(key);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount,
+ uint64_t consumption, uint64_t capacity,
+ nsIFile* diskDirectory) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryInfo(nsIURI* uri, const nsACString& idEnhance,
+ int64_t dataSize, int64_t altDataSize,
+ uint32_t fetchCount,
+ uint32_t lastModifiedTime,
+ uint32_t expirationTime, bool aPinned,
+ nsILoadContextInfo* aInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ // The predictor will only ever touch entries with no idEnhance ("") or an
+ // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that
+ // don't match that to avoid doing extra work.
+ if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) {
+ // This is an entry we own, so we can just doom it entirely
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ rv = mPredictor->mCacheStorageService->DiskCacheStorage(
+ aInfo, getter_AddRefs(cacheDiskStorage));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ cacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr);
+ } else if (idEnhance.IsEmpty()) {
+ // This is an entry we don't own, so we have to be a little more careful and
+ // just get rid of our own metadata entries. Append it to an array of things
+ // to operate on and then do the operations later so we don't end up calling
+ // Complete() multiple times/too soon.
+ ++mEntriesToVisit;
+ mURIsToVisit.AppendElement(uri);
+ mInfosToVisit.AppendElement(aInfo);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryVisitCompleted() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ nsTArray<nsCOMPtr<nsIURI>> urisToVisit = std::move(mURIsToVisit);
+
+ MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length());
+
+ nsTArray<nsCOMPtr<nsILoadContextInfo>> infosToVisit =
+ std::move(mInfosToVisit);
+
+ MOZ_ASSERT(mEntriesToVisit == infosToVisit.Length());
+
+ if (!mEntriesToVisit) {
+ Complete();
+ return NS_OK;
+ }
+
+ uint32_t entriesToVisit = urisToVisit.Length();
+ for (uint32_t i = 0; i < entriesToVisit; ++i) {
+ nsCString u;
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ rv = mPredictor->mCacheStorageService->DiskCacheStorage(
+ infosToVisit[i], getter_AddRefs(cacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ urisToVisit[i]->GetAsciiSpec(u);
+ rv = cacheDiskStorage->AsyncOpenURI(
+ urisToVisit[i], ""_ns,
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED,
+ this);
+ if (NS_FAILED(rv)) {
+ mEntriesToVisit--;
+ if (!mEntriesToVisit) {
+ Complete();
+ return NS_OK;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void Predictor::Resetter::Complete() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
+ return;
+ }
+
+ obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
+}
+
+// Helper functions to make using the predictor easier from native code
+
+static StaticRefPtr<nsINetworkPredictor> sPredictor;
+
+static nsresult EnsureGlobalPredictor(nsINetworkPredictor** aPredictor) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sPredictor) {
+ nsresult rv;
+ nsCOMPtr<nsINetworkPredictor> predictor =
+ do_GetService("@mozilla.org/network/predictor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ sPredictor = predictor;
+ ClearOnShutdown(&sPredictor);
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor = sPredictor.get();
+ predictor.forget(aPredictor);
+ return NS_OK;
+}
+
+nsresult PredictorPredict(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorPredictReason reason,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->PredictNative(targetURI, sourceURI, reason,
+ originAttributes, verifier);
+}
+
+nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason,
+ const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
+}
+
+nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason, nsILoadGroup* loadGroup) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ OriginAttributes originAttributes;
+
+ if (loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ loadContext = do_GetInterface(callbacks);
+
+ if (loadContext) {
+ loadContext->GetOriginAttributes(originAttributes);
+ }
+ }
+ }
+
+ return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
+}
+
+nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
+ PredictorLearnReason reason, dom::Document* document) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OriginAttributes originAttributes;
+
+ if (document) {
+ nsCOMPtr<nsIPrincipal> docPrincipal = document->NodePrincipal();
+
+ if (docPrincipal) {
+ originAttributes = docPrincipal->OriginAttributesRef();
+ }
+ }
+
+ return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
+}
+
+nsresult PredictorLearnRedirect(nsIURI* targetURI, nsIChannel* channel,
+ const OriginAttributes& originAttributes) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIURI> sourceURI;
+ nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool sameUri;
+ rv = targetURI->Equals(sourceURI, &sameUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (sameUri) {
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINetworkPredictor> predictor;
+ rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return predictor->LearnNative(targetURI, sourceURI,
+ nsINetworkPredictor::LEARN_LOAD_REDIRECT,
+ originAttributes);
+}
+
+// nsINetworkPredictorVerifier
+
+/**
+ * Call through to the child's verifier (only during tests)
+ */
+NS_IMETHODIMP
+Predictor::OnPredictPrefetch(nsIURI* aURI, uint32_t httpStatus) {
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child
+ // process will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictPrefetch(aURI, httpStatus);
+ }
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
+
+ for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictPrefetch(aURI, httpStatus)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::OnPredictPreconnect(nsIURI* aURI) {
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child
+ // process will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictPreconnect(aURI);
+ }
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
+
+ for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictPreconnect(aURI)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::OnPredictDNS(nsIURI* aURI) {
+ if (IsNeckoChild()) {
+ if (mChildVerifier) {
+ // Ideally, we'd assert here. But since we're slowly moving towards a
+ // world where we have multiple child processes, and only one child
+ // process will be likely to have a verifier, we have to play it safer.
+ return mChildVerifier->OnPredictDNS(aURI);
+ }
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
+
+ for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ if (!neckoParent->SendPredOnPredictDNS(aURI)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ return NS_OK;
+}
+
+// Predictor::PrefetchListener
+// nsISupports
+NS_IMPL_ISUPPORTS(Predictor::PrefetchListener, nsIStreamListener,
+ nsIRequestObserver)
+
+// nsIRequestObserver
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnStartRequest(nsIRequest* aRequest) {
+ mStartTime = TimeStamp::Now();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%" PRIX32, this,
+ static_cast<uint32_t>(aStatusCode)));
+ NS_ENSURE_ARG(aRequest);
+ if (NS_FAILED(aStatusCode)) {
+ return aStatusCode;
+ }
+ Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME,
+ mStartTime);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ PREDICTOR_LOG((" Could not get HTTP Channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(httpChannel);
+ if (!cachingChannel) {
+ PREDICTOR_LOG((" Could not get caching channel!"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+ uint32_t httpStatus;
+ rv = httpChannel->GetResponseStatus(&httpStatus);
+ if (NS_SUCCEEDED(rv) && httpStatus == 200) {
+ rv = cachingChannel->ForceCacheEntryValidFor(
+ StaticPrefs::network_predictor_prefetch_force_valid_for());
+ PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%" PRIX32,
+ StaticPrefs::network_predictor_prefetch_force_valid_for(),
+ static_cast<uint32_t>(rv)));
+ } else {
+ rv = cachingChannel->ForceCacheEntryValidFor(0);
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Not200);
+ PREDICTOR_LOG((" removing any forced validity rv=%" PRIX32,
+ static_cast<uint32_t>(rv)));
+ }
+
+ nsAutoCString reqName;
+ rv = aRequest->GetName(reqName);
+ if (NS_FAILED(rv)) {
+ reqName.AssignLiteral("<unknown>");
+ }
+
+ PREDICTOR_LOG((" request %s status %u", reqName.get(), httpStatus));
+
+ if (mVerifier) {
+ mVerifier->OnPredictPrefetch(mURI, httpStatus);
+ }
+
+ return rv;
+}
+
+// nsIStreamListener
+NS_IMETHODIMP
+Predictor::PrefetchListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ const uint32_t aCount) {
+ uint32_t result;
+ return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount,
+ &result);
+}
+
+// Miscellaneous Predictor
+
+void Predictor::UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI,
+ uint32_t httpStatus,
+ nsHttpRequestHead& requestHead,
+ nsHttpResponseHead* responseHead,
+ nsILoadContextInfo* lci, bool isTracking) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (lci && lci->IsPrivate()) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring"));
+ return;
+ }
+
+ if (!sourceURI || !targetURI) {
+ PREDICTOR_LOG(
+ ("Predictor::UpdateCacheability missing source or target uri"));
+ return;
+ }
+
+ if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri"));
+ return;
+ }
+
+ RefPtr<Predictor> self = sSelf;
+ if (self) {
+ nsAutoCString method;
+ requestHead.Method(method);
+
+ nsAutoCString vary;
+ Unused << responseHead->GetHeader(nsHttp::Vary, vary);
+
+ nsAutoCString cacheControlHeader;
+ Unused << responseHead->GetHeader(nsHttp::Cache_Control,
+ cacheControlHeader);
+ CacheControlParser cacheControl(cacheControlHeader);
+
+ self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, method,
+ *lci->OriginAttributesPtr(), isTracking,
+ !vary.IsEmpty(), cacheControl.NoStore());
+ }
+}
+
+void Predictor::UpdateCacheabilityInternal(
+ nsIURI* sourceURI, nsIURI* targetURI, uint32_t httpStatus,
+ const nsCString& method, const OriginAttributes& originAttributes,
+ bool isTracking, bool couldVary, bool isNoStore) {
+ PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus));
+
+ nsresult rv;
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return;
+ }
+
+ if (!StaticPrefs::network_predictor_enabled()) {
+ PREDICTOR_LOG((" not enabled"));
+ return;
+ }
+
+ nsCOMPtr<nsICacheStorage> cacheDiskStorage;
+
+ RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
+
+ rv = mCacheStorageService->DiskCacheStorage(lci,
+ getter_AddRefs(cacheDiskStorage));
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG((" cannot get disk cache storage"));
+ return;
+ }
+
+ uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED;
+ RefPtr<Predictor::CacheabilityAction> action =
+ new Predictor::CacheabilityAction(targetURI, httpStatus, method,
+ isTracking, couldVary, isNoStore, this);
+ nsAutoCString uri;
+ targetURI->GetAsciiSpec(uri);
+ PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get()));
+ cacheDiskStorage->AsyncOpenURI(sourceURI, ""_ns, openFlags, action);
+}
+
+NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction, nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor);
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry* entry,
+ uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+namespace {
+enum PrefetchDecisionReason {
+ PREFETCHABLE,
+ STATUS_NOT_200,
+ METHOD_NOT_GET,
+ URL_HAS_QUERY_STRING,
+ RESOURCE_IS_TRACKING,
+ RESOURCE_COULD_VARY,
+ RESOURCE_IS_NO_STORE
+};
+}
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry* entry,
+ bool isNew,
+ nsresult result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // This is being opened read-only, so isNew should always be false
+ MOZ_ASSERT(!isNew);
+
+ PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
+ if (NS_FAILED(result)) {
+ // Nothing to do
+ PREDICTOR_LOG((" nothing to do result=%" PRIX32 " isNew=%d",
+ static_cast<uint32_t>(result), isNew));
+ return NS_OK;
+ }
+
+ nsCString strTargetURI;
+ nsresult rv = mTargetURI->GetAsciiSpec(strTargetURI);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" GetAsciiSpec returned %" PRIx32, static_cast<uint32_t>(rv)));
+ return NS_OK;
+ }
+
+ rv = entry->VisitMetaData(this);
+ if (NS_FAILED(rv)) {
+ PREDICTOR_LOG(
+ (" VisitMetaData returned %" PRIx32, static_cast<uint32_t>(rv)));
+ return NS_OK;
+ }
+
+ nsTArray<nsCString> keysToCheck = std::move(mKeysToCheck),
+ valuesToCheck = std::move(mValuesToCheck);
+
+ bool hasQueryString = false;
+ nsAutoCString query;
+ if (NS_SUCCEEDED(mTargetURI->GetQuery(query)) && !query.IsEmpty()) {
+ hasQueryString = true;
+ }
+
+ MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length());
+ for (size_t i = 0; i < keysToCheck.Length(); ++i) {
+ const char* key = keysToCheck[i].BeginReading();
+ const char* value = valuesToCheck[i].BeginReading();
+ nsCString uri;
+ uint32_t hitCount, lastHit, flags;
+
+ if (!mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit,
+ flags)) {
+ PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value));
+ continue;
+ }
+
+ if (strTargetURI.Equals(uri)) {
+ bool prefetchable = true;
+ PrefetchDecisionReason reason = PREFETCHABLE;
+
+ if (mHttpStatus != 200) {
+ prefetchable = false;
+ reason = STATUS_NOT_200;
+ } else if (!mMethod.EqualsLiteral("GET")) {
+ prefetchable = false;
+ reason = METHOD_NOT_GET;
+ } else if (hasQueryString) {
+ prefetchable = false;
+ reason = URL_HAS_QUERY_STRING;
+ } else if (mIsTracking) {
+ prefetchable = false;
+ reason = RESOURCE_IS_TRACKING;
+ } else if (mCouldVary) {
+ prefetchable = false;
+ reason = RESOURCE_COULD_VARY;
+ } else if (mIsNoStore) {
+ // We don't set prefetchable = false yet, because we just want to know
+ // what kind of effect this would have on prefetching.
+ reason = RESOURCE_IS_NO_STORE;
+ }
+
+ Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_DECISION_REASON,
+ reason);
+
+ if (prefetchable) {
+ PREDICTOR_LOG((" marking %s cacheable", key));
+ flags |= FLAG_PREFETCHABLE;
+ } else {
+ PREDICTOR_LOG((" marking %s uncacheable", key));
+ flags &= ~FLAG_PREFETCHABLE;
+ }
+ nsCString newValue;
+ MakeMetadataEntry(hitCount, lastHit, flags, newValue);
+ entry->SetMetaDataElement(key, newValue.BeginReading());
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnMetaDataElement(const char* asciiKey,
+ const char* asciiValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsURIMetadataElement(asciiKey)) {
+ return NS_OK;
+ }
+
+ nsCString key, value;
+ key.AssignASCII(asciiKey);
+ value.AssignASCII(asciiValue);
+ mKeysToCheck.AppendElement(key);
+ mValuesToCheck.AppendElement(value);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/Predictor.h b/netwerk/base/Predictor.h
new file mode 100644
index 0000000000..a16e09adad
--- /dev/null
+++ b/netwerk/base/Predictor.h
@@ -0,0 +1,458 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Predictor_h
+#define mozilla_net_Predictor_h
+
+#include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
+
+#include "nsCOMPtr.h"
+#include "nsICacheEntry.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsIDNSListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserver.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIStreamListener.h"
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "mozilla/TimeStamp.h"
+
+class nsICacheStorage;
+class nsIDNSService;
+class nsIIOService;
+class nsILoadContextInfo;
+class nsITimer;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+
+class Predictor final : public nsINetworkPredictor,
+ public nsIObserver,
+ public nsISpeculativeConnectionOverrider,
+ public nsIInterfaceRequestor,
+ public nsICacheEntryMetaDataVisitor,
+ public nsINetworkPredictorVerifier {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKPREDICTOR
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSINETWORKPREDICTORVERIFIER
+
+ Predictor();
+
+ nsresult Init();
+ void Shutdown();
+ static nsresult Create(const nsIID& iid, void** result);
+
+ // Used to update whether a particular URI was cacheable or not.
+ // sourceURI and targetURI are the same as the arguments to Learn
+ // and httpStatus is the status code we got while loading targetURI.
+ static void UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI,
+ uint32_t httpStatus,
+ nsHttpRequestHead& requestHead,
+ nsHttpResponseHead* responseHead,
+ nsILoadContextInfo* lci, bool isTracking);
+
+ private:
+ virtual ~Predictor();
+
+ // Stores callbacks for a child process predictor (for test purposes)
+ nsCOMPtr<nsINetworkPredictorVerifier> mChildVerifier;
+
+ union Reason {
+ PredictorLearnReason mLearn;
+ PredictorPredictReason mPredict;
+ };
+
+ class DNSListener : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ DNSListener() = default;
+
+ private:
+ virtual ~DNSListener() = default;
+ };
+
+ class Action : public nsICacheEntryOpenCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+
+ Action(bool fullUri, bool predict, Reason reason, nsIURI* targetURI,
+ nsIURI* sourceURI, nsINetworkPredictorVerifier* verifier,
+ Predictor* predictor);
+ Action(bool fullUri, bool predict, Reason reason, nsIURI* targetURI,
+ nsIURI* sourceURI, nsINetworkPredictorVerifier* verifier,
+ Predictor* predictor, uint8_t stackCount);
+
+ static const bool IS_FULL_URI = true;
+ static const bool IS_ORIGIN = false;
+
+ static const bool DO_PREDICT = true;
+ static const bool DO_LEARN = false;
+
+ private:
+ virtual ~Action() = default;
+
+ bool mFullUri : 1;
+ bool mPredict : 1;
+ union {
+ PredictorPredictReason mPredictReason;
+ PredictorLearnReason mLearnReason;
+ };
+ nsCOMPtr<nsIURI> mTargetURI;
+ nsCOMPtr<nsIURI> mSourceURI;
+ nsCOMPtr<nsINetworkPredictorVerifier> mVerifier;
+ TimeStamp mStartTime;
+ uint8_t mStackCount;
+ RefPtr<Predictor> mPredictor;
+ };
+
+ class CacheabilityAction : public nsICacheEntryOpenCallback,
+ public nsICacheEntryMetaDataVisitor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+
+ CacheabilityAction(nsIURI* targetURI, uint32_t httpStatus,
+ const nsCString& method, bool isTracking, bool couldVary,
+ bool isNoStore, Predictor* predictor)
+ : mTargetURI(targetURI),
+ mHttpStatus(httpStatus),
+ mMethod(method),
+ mIsTracking(isTracking),
+ mCouldVary(couldVary),
+ mIsNoStore(isNoStore),
+ mPredictor(predictor) {}
+
+ private:
+ virtual ~CacheabilityAction() = default;
+
+ nsCOMPtr<nsIURI> mTargetURI;
+ uint32_t mHttpStatus;
+ nsCString mMethod;
+ bool mIsTracking;
+ bool mCouldVary;
+ bool mIsNoStore;
+ RefPtr<Predictor> mPredictor;
+ nsTArray<nsCString> mKeysToCheck;
+ nsTArray<nsCString> mValuesToCheck;
+ };
+
+ class Resetter : public nsICacheEntryOpenCallback,
+ public nsICacheEntryMetaDataVisitor,
+ public nsICacheStorageVisitor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+ NS_DECL_NSICACHESTORAGEVISITOR
+
+ explicit Resetter(Predictor* predictor);
+
+ private:
+ virtual ~Resetter() = default;
+
+ void Complete();
+
+ uint32_t mEntriesToVisit;
+ nsTArray<nsCString> mKeysToDelete;
+ RefPtr<Predictor> mPredictor;
+ nsTArray<nsCOMPtr<nsIURI>> mURIsToVisit;
+ nsTArray<nsCOMPtr<nsILoadContextInfo>> mInfosToVisit;
+ };
+
+ class SpaceCleaner : public nsICacheEntryMetaDataVisitor {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYMETADATAVISITOR
+
+ explicit SpaceCleaner(Predictor* predictor)
+ : mLRUStamp(0), mLRUKeyToDelete(nullptr), mPredictor(predictor) {}
+
+ void Finalize(nsICacheEntry* entry);
+
+ private:
+ virtual ~SpaceCleaner() = default;
+ uint32_t mLRUStamp;
+ const char* mLRUKeyToDelete;
+ nsTArray<nsCString> mLongKeysToDelete;
+ RefPtr<Predictor> mPredictor;
+ };
+
+ class PrefetchListener : public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ PrefetchListener(nsINetworkPredictorVerifier* verifier, nsIURI* uri,
+ Predictor* predictor)
+ : mVerifier(verifier), mURI(uri), mPredictor(predictor) {}
+
+ private:
+ virtual ~PrefetchListener() = default;
+
+ nsCOMPtr<nsINetworkPredictorVerifier> mVerifier;
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<Predictor> mPredictor;
+ TimeStamp mStartTime;
+ };
+
+ // Observer-related stuff
+ nsresult InstallObserver();
+ void RemoveObserver();
+
+ // Service startup utilities
+ void MaybeCleanupOldDBFiles();
+
+ // The guts of prediction
+
+ // This is the top-level driver for doing any prediction that needs
+ // information from the cache. Returns true if any predictions were queued up
+ // * reason - What kind of prediction this is/why this prediction is
+ // happening (pageload, startup)
+ // * entry - the cache entry with the information we need
+ // * isNew - whether or not the cache entry is brand new and empty
+ // * fullUri - whether we are doing predictions based on a full page URI, or
+ // just the origin of the page
+ // * targetURI - the URI that we are predicting based upon - IOW, the URI
+ // that is being loaded or being redirected to
+ // * verifier - used for testing to verify the expected predictions happen
+ // * stackCount - used to ensure we don't recurse too far trying to find the
+ // final redirection in a redirect chain
+ bool PredictInternal(PredictorPredictReason reason, nsICacheEntry* entry,
+ bool isNew, bool fullUri, nsIURI* targetURI,
+ nsINetworkPredictorVerifier* verifier,
+ uint8_t stackCount);
+
+ // Used when predicting because the user's mouse hovered over a link
+ // * targetURI - the URI target of the link
+ // * sourceURI - the URI of the page on which the link appears
+ // * originAttributes - the originAttributes for this prediction
+ // * verifier - used for testing to verify the expected predictions happen
+ void PredictForLink(nsIURI* targetURI, nsIURI* sourceURI,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier);
+
+ // Used when predicting because a page is being loaded (which may include
+ // being the target of a redirect). All arguments are the same as for
+ // PredictInternal. Returns true if any predictions were queued up.
+ bool PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI,
+ uint8_t stackCount, bool fullUri,
+ nsINetworkPredictorVerifier* verifier);
+
+ // Used when predicting pages that will be used near browser startup. All
+ // arguments are the same as for PredictInternal. Returns true if any
+ // predictions were queued up.
+ bool PredictForStartup(nsICacheEntry* entry, bool fullUri,
+ nsINetworkPredictorVerifier* verifier);
+
+ // Utilities related to prediction
+
+ // Used to update our rolling load count (how many of the last n loads was a
+ // partular resource loaded on?)
+ // * entry - cache entry of page we're loading
+ // * flags - value that contains our rolling count as the top 20 bits (but
+ // we may use fewer than those 20 bits for calculations)
+ // * key - metadata key that we will update on entry
+ // * hitCount - part of the metadata we need to preserve
+ // * lastHit - part of the metadata we need to preserve
+ void UpdateRollingLoadCount(nsICacheEntry* entry, const uint32_t flags,
+ const char* key, const uint32_t hitCount,
+ const uint32_t lastHit);
+
+ // Used to calculate how much to degrade our confidence for all resources
+ // on a particular page, because of how long ago the most recent load of that
+ // page was. Returns a value between 0 (very recent most recent load) and 100
+ // (very distant most recent load)
+ // * lastLoad - time stamp of most recent load of a page
+ int32_t CalculateGlobalDegradation(uint32_t lastLoad);
+
+ // Used to calculate how confident we are that a particular resource will be
+ // used. Returns a value between 0 (no confidence) and 100 (very confident)
+ // * hitCount - number of times this resource has been seen when loading
+ // this page
+ // * hitsPossible - number of times this page has been loaded
+ // * lastHit - timestamp of the last time this resource was seen when
+ // loading this page
+ // * lastPossible - timestamp of the last time this page was loaded
+ // * globalDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ int32_t CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
+ uint32_t lastHit, uint32_t lastPossible,
+ int32_t globalDegradation);
+
+ // Used to calculate all confidence values for all resources associated with a
+ // page.
+ // * entry - the cache entry with all necessary information about this page
+ // * referrer - the URI that we are loading (may be null)
+ // * lastLoad - timestamp of the last time this page was loaded
+ // * loadCount - number of times this page has been loaded
+ // * gloablDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ // * fullUri - whether we're predicting for a full URI or origin-only
+ void CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer,
+ uint32_t lastLoad, uint32_t loadCount,
+ int32_t globalDegradation, bool fullUri);
+
+ enum PrefetchIgnoreReason {
+ PREFETCH_OK,
+ NOT_FULL_URI,
+ NO_REFERRER,
+ MISSED_A_LOAD,
+ PREFETCH_DISABLED,
+ PREFETCH_DISABLED_VIA_COUNT,
+ CONFIDENCE_TOO_LOW
+ };
+
+ // Used to prepare any necessary prediction for a resource on a page
+ // * confidence - value calculated by CalculateConfidence for this resource
+ // * flags - the flags taken from the resource
+ // * uri - the ascii spec of the URI of the resource
+ void SetupPrediction(int32_t confidence, uint32_t flags, const nsCString& uri,
+ PrefetchIgnoreReason reason);
+
+ // Used to kick off a prefetch from RunPredictions if necessary
+ // * uri - the URI to prefetch
+ // * referrer - the URI of the referring page
+ // * originAttributes - the originAttributes of this prefetch
+ // * verifier - used for testing to ensure the expected prefetch happens
+ nsresult Prefetch(nsIURI* uri, nsIURI* referrer,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier);
+
+ // Used to actually perform any predictions set up via SetupPrediction.
+ // Returns true if any predictions were performed.
+ // * referrer - the URI we are predicting from
+ // * originAttributs - the originAttributes we are predicting from
+ // * verifier - used for testing to ensure the expected predictions happen
+ bool RunPredictions(nsIURI* referrer,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier* verifier);
+
+ // Used to guess whether a page will redirect to another page or not. Returns
+ // true if a redirection is likely.
+ // * entry - cache entry with all necessary information about this page
+ // * loadCount - number of times this page has been loaded
+ // * lastLoad - timestamp of the last time this page was loaded
+ // * globalDegradation - value calculated by CalculateGlobalDegradation for
+ // this page
+ // * redirectURI - if this returns true, the URI that is likely to be
+ // redirected to, otherwise null
+ bool WouldRedirect(nsICacheEntry* entry, uint32_t loadCount,
+ uint32_t lastLoad, int32_t globalDegradation,
+ nsIURI** redirectURI);
+
+ // The guts of learning information
+
+ // This is the top-level driver for doing any updating of our information in
+ // the cache
+ // * reason - why this learn is happening (pageload, startup, redirect)
+ // * entry - the cache entry with the information we need
+ // * isNew - whether or not the cache entry is brand new and empty
+ // * fullUri - whether we are doing predictions based on a full page URI, or
+ // just the origin of the page
+ // * targetURI - the URI that we are adding to our data - most often a
+ // resource loaded by a page the user navigated to
+ // * sourceURI - the URI that caused targetURI to be loaded, usually the
+ // page the user navigated to
+ void LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry,
+ bool isNew, bool fullUri, nsIURI* targetURI,
+ nsIURI* sourceURI);
+
+ // Used when learning about a resource loaded by a page
+ // * entry - the cache entry with information that needs updating
+ // * targetURI - the URI of the resource that was loaded by the page
+ void LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI);
+
+ // Used when learning about a redirect from one page to another
+ // * entry - the cache entry of the page that was redirected from
+ // * targetURI - the URI of the redirect target
+ void LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI);
+
+ // Used to learn about pages loaded close to browser startup. This results in
+ // LearnForStartup being called if we are, in fact, near browser startup
+ // * uri - the URI of a page that has been loaded (may not have been near
+ // browser startup)
+ // * fullUri - true if this is a full page uri, false if it's an origin
+ // * originAttributes - the originAttributes for this learning.
+ void MaybeLearnForStartup(nsIURI* uri, bool fullUri,
+ const OriginAttributes& originAttributes);
+
+ // Used in conjunction with MaybeLearnForStartup to learn about pages loaded
+ // close to browser startup
+ // * entry - the cache entry that stores the startup page list
+ // * targetURI - the URI of a page that was loaded near browser startup
+ void LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI);
+
+ // Used to parse the data we store in cache metadata
+ // * key - the cache metadata key
+ // * value - the cache metadata value
+ // * uri - (out) the ascii spec of the URI this metadata entry was about
+ // * hitCount - (out) the number of times this URI has been seen
+ // * lastHit - (out) timestamp of the last time this URI was seen
+ // * flags - (out) flags for this metadata entry
+ bool ParseMetaDataEntry(const char* key, const char* value, nsCString& uri,
+ uint32_t& hitCount, uint32_t& lastHit,
+ uint32_t& flags);
+
+ // Used to update whether a particular URI was cacheable or not.
+ // sourceURI and targetURI are the same as the arguments to Learn
+ // and httpStatus is the status code we got while loading targetURI.
+ void UpdateCacheabilityInternal(nsIURI* sourceURI, nsIURI* targetURI,
+ uint32_t httpStatus, const nsCString& method,
+ const OriginAttributes& originAttributes,
+ bool isTracking, bool couldVary,
+ bool isNoStore);
+
+ // Gets the pref value and clamps it within the acceptable range.
+ uint32_t ClampedPrefetchRollingLoadCount();
+
+ // Our state
+ bool mInitialized{false};
+
+ nsTArray<nsCString> mKeysToOperateOn;
+ nsTArray<nsCString> mValuesToOperateOn;
+
+ nsCOMPtr<nsICacheStorageService> mCacheStorageService;
+
+ nsCOMPtr<nsISpeculativeConnect> mSpeculativeService;
+
+ nsCOMPtr<nsIURI> mStartupURI;
+ uint32_t mStartupTime{0};
+ uint32_t mLastStartupTime{0};
+ int32_t mStartupCount{1};
+
+ nsCOMPtr<nsIDNSService> mDnsService;
+
+ RefPtr<DNSListener> mDNSListener;
+
+ nsTArray<nsCOMPtr<nsIURI>> mPrefetches;
+ nsTArray<nsCOMPtr<nsIURI>> mPreconnects;
+ nsTArray<nsCOMPtr<nsIURI>> mPreresolves;
+
+ static Predictor* sSelf;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Predictor_h
diff --git a/netwerk/base/PrivateBrowsingChannel.h b/netwerk/base/PrivateBrowsingChannel.h
new file mode 100644
index 0000000000..a2e224f092
--- /dev/null
+++ b/netwerk/base/PrivateBrowsingChannel.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sts=2 sw=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_PrivateBrowsingChannel_h__
+#define mozilla_net_PrivateBrowsingChannel_h__
+
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsCOMPtr.h"
+#include "nsILoadGroup.h"
+#include "nsILoadContext.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsNetUtil.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+template <class Channel>
+class PrivateBrowsingChannel : public nsIPrivateBrowsingChannel {
+ public:
+ PrivateBrowsingChannel()
+ : mPrivateBrowsingOverriden(false), mPrivateBrowsing(false) {}
+
+ NS_IMETHOD SetPrivate(bool aPrivate) override {
+ // Make sure that we don't have a load context
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(static_cast<Channel*>(this), loadContext);
+ MOZ_ASSERT(!loadContext);
+ if (loadContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mPrivateBrowsingOverriden = true;
+ mPrivateBrowsing = aPrivate;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetIsChannelPrivate(bool* aResult) override {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mPrivateBrowsing;
+ return NS_OK;
+ }
+
+ NS_IMETHOD IsPrivateModeOverriden(bool* aValue, bool* aResult) override {
+ NS_ENSURE_ARG_POINTER(aValue);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mPrivateBrowsingOverriden;
+ if (mPrivateBrowsingOverriden) {
+ *aValue = mPrivateBrowsing;
+ }
+ return NS_OK;
+ }
+
+ // Must be called every time the channel's callbacks or loadGroup is updated
+ void UpdatePrivateBrowsing() {
+ // once marked as private we never go un-private
+ if (mPrivateBrowsing) {
+ return;
+ }
+
+ auto channel = static_cast<Channel*>(this);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(channel, loadContext);
+ if (loadContext) {
+ mPrivateBrowsing = loadContext->UsePrivateBrowsing();
+ return;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ OriginAttributes attrs = loadInfo->GetOriginAttributes();
+ mPrivateBrowsing = attrs.mPrivateBrowsingId > 0;
+ }
+
+ bool CanSetCallbacks(nsIInterfaceRequestor* aCallbacks) const {
+ // Make sure that the private bit override flag is not set.
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ if (!aCallbacks) {
+ return true;
+ }
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(aCallbacks);
+ if (!loadContext) {
+ return true;
+ }
+ MOZ_ASSERT(!mPrivateBrowsingOverriden);
+ return !mPrivateBrowsingOverriden;
+ }
+
+ bool CanSetLoadGroup(nsILoadGroup* aLoadGroup) const {
+ // Make sure that the private bit override flag is not set.
+ // This is a fatal error in debug builds, and a runtime error in release
+ // builds.
+ if (!aLoadGroup) {
+ return true;
+ }
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ // From this point on, we just hand off the work to CanSetCallbacks,
+ // because the logic is exactly the same.
+ return CanSetCallbacks(callbacks);
+ }
+
+ protected:
+ bool mPrivateBrowsingOverriden;
+ bool mPrivateBrowsing;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/ProtocolHandlerInfo.cpp b/netwerk/base/ProtocolHandlerInfo.cpp
new file mode 100644
index 0000000000..432ff965ce
--- /dev/null
+++ b/netwerk/base/ProtocolHandlerInfo.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProtocolHandlerInfo.h"
+#include "StaticComponents.h"
+#include "nsIProtocolHandler.h"
+
+namespace mozilla::net {
+
+uint32_t ProtocolHandlerInfo::StaticProtocolFlags() const {
+ uint32_t flags = mInner.match(
+ [&](const xpcom::StaticProtocolHandler* handler) {
+ return handler->mProtocolFlags;
+ },
+ [&](const RuntimeProtocolHandler& handler) {
+ return handler.mProtocolFlags;
+ });
+#if !IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ MOZ_RELEASE_ASSERT(!(flags & nsIProtocolHandler::ORIGIN_IS_FULL_SPEC),
+ "ORIGIN_IS_FULL_SPEC is unsupported but used");
+#endif
+ return flags;
+}
+
+int32_t ProtocolHandlerInfo::DefaultPort() const {
+ return mInner.match(
+ [&](const xpcom::StaticProtocolHandler* handler) {
+ return handler->mDefaultPort;
+ },
+ [&](const RuntimeProtocolHandler& handler) {
+ return handler.mDefaultPort;
+ });
+}
+
+nsresult ProtocolHandlerInfo::DynamicProtocolFlags(nsIURI* aURI,
+ uint32_t* aFlags) const {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ // If we're querying dynamic flags, we'll need to fetch the actual xpcom
+ // component in order to check them.
+ if (HasDynamicFlags()) {
+ nsCOMPtr<nsIProtocolHandler> handler = Handler();
+ if (nsCOMPtr<nsIProtocolHandlerWithDynamicFlags> dynamic =
+ do_QueryInterface(handler)) {
+ nsresult rv = dynamic->GetFlagsForURI(aURI, aFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_DIAGNOSTIC_ASSERT(
+ (StaticProtocolFlags() & ~nsIProtocolHandler::DYNAMIC_URI_FLAGS) ==
+ (*aFlags & ~nsIProtocolHandler::DYNAMIC_URI_FLAGS),
+ "only DYNAMIC_URI_FLAGS may be changed by a "
+ "nsIProtocolHandlerWithDynamicFlags implementation");
+ return NS_OK;
+ }
+ }
+
+ // Otherwise, just check against static flags.
+ *aFlags = StaticProtocolFlags();
+ return NS_OK;
+}
+
+bool ProtocolHandlerInfo::HasDynamicFlags() const {
+ return mInner.match(
+ [&](const xpcom::StaticProtocolHandler* handler) {
+ return handler->mHasDynamicFlags;
+ },
+ [&](const RuntimeProtocolHandler&) { return false; });
+}
+
+already_AddRefed<nsIProtocolHandler> ProtocolHandlerInfo::Handler() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIProtocolHandler> retval;
+ mInner.match(
+ [&](const xpcom::StaticProtocolHandler* handler) {
+ retval = handler->Module().GetService();
+ },
+ [&](const RuntimeProtocolHandler& handler) {
+ retval = handler.mHandler.get();
+ });
+ return retval.forget();
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/base/ProtocolHandlerInfo.h b/netwerk/base/ProtocolHandlerInfo.h
new file mode 100644
index 0000000000..337dbddcfc
--- /dev/null
+++ b/netwerk/base/ProtocolHandlerInfo.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ProtocolHandlerInfo_h
+#define mozilla_net_ProtocolHandlerInfo_h
+
+#include "mozilla/Variant.h"
+#include "nsProxyRelease.h"
+#include "nsIProtocolHandler.h"
+
+namespace mozilla {
+namespace xpcom {
+struct StaticProtocolHandler;
+}
+
+namespace net {
+
+struct RuntimeProtocolHandler {
+ nsMainThreadPtrHandle<nsIProtocolHandler> mHandler;
+ uint32_t mProtocolFlags;
+ int32_t mDefaultPort;
+};
+
+// Information about a specific protocol handler.
+class ProtocolHandlerInfo {
+ public:
+ explicit ProtocolHandlerInfo(const xpcom::StaticProtocolHandler& aStatic)
+ : mInner(AsVariant(&aStatic)) {}
+ explicit ProtocolHandlerInfo(RuntimeProtocolHandler aDynamic)
+ : mInner(AsVariant(std::move(aDynamic))) {}
+
+ // Returns the statically known protocol-specific flags.
+ // See `nsIProtocolHandler` for valid values.
+ uint32_t StaticProtocolFlags() const;
+
+ // The port that this protocol normally uses.
+ // If a port does not make sense for the protocol (e.g., "about:") then -1
+ // will be returned.
+ int32_t DefaultPort() const;
+
+ // If true, `DynamicProtocolFlags()` may return a different value than
+ // `StaticProtocolFlags()` for flags in `DYNAMIC_URI_FLAGS`, due to a
+ // `nsIProtocolHandlerWithDynamicFlags` implementation.
+ bool HasDynamicFlags() const;
+
+ // Like `StaticProtocolFlags()` but also checks
+ // `nsIProtocolHandlerWithDynamicFlags` for uri-specific flags.
+ //
+ // NOTE: Only safe to call from the main thread.
+ nsresult DynamicProtocolFlags(nsIURI* aURI, uint32_t* aFlags) const
+ MOZ_REQUIRES(sMainThreadCapability);
+
+ // Get the main-thread-only nsIProtocolHandler instance.
+ already_AddRefed<nsIProtocolHandler> Handler() const
+ MOZ_REQUIRES(sMainThreadCapability);
+
+ private:
+ Variant<const xpcom::StaticProtocolHandler*, RuntimeProtocolHandler> mInner;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ProtocolHandlerInfo_h
diff --git a/netwerk/base/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp
new file mode 100644
index 0000000000..a93d040a14
--- /dev/null
+++ b/netwerk/base/ProxyAutoConfig.cpp
@@ -0,0 +1,943 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProxyAutoConfig.h"
+#include "nsICancelable.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsINamed.h"
+#include "nsThreadUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIURLParser.h"
+#include "nsJSUtils.h"
+#include "jsfriendapi.h"
+#include "js/CallAndConstruct.h" // JS_CallFunctionName
+#include "js/CompilationAndEvaluation.h" // JS::Compile
+#include "js/ContextOptions.h"
+#include "js/Initialization.h"
+#include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_GetProperty
+#include "js/PropertySpec.h"
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "js/Utility.h"
+#include "js/Warnings.h" // JS::SetWarningReporter
+#include "prnetdb.h"
+#include "nsITimer.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/net/ProxyAutoConfigChild.h"
+#include "mozilla/net/ProxyAutoConfigParent.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+
+#if defined(XP_MACOSX)
+# include "nsMacUtilsImpl.h"
+#endif
+
+#include "XPCSelfHostedShmem.h"
+
+namespace mozilla {
+namespace net {
+
+// These are some global helper symbols the PAC format requires that we provide
+// that are initialized as part of the global javascript context used for PAC
+// evaluations. Additionally dnsResolve(host) and myIpAddress() are supplied in
+// the same context but are implemented as c++ helpers. alert(msg) is similarly
+// defined.
+//
+// Per ProxyAutoConfig::Init, this data must be ASCII.
+
+static const char sAsciiPacUtils[] =
+#include "ascii_pac_utils.inc"
+ ;
+
+// sRunning is defined for the helper functions only while the
+// Javascript engine is running and the PAC object cannot be deleted
+// or reset.
+static Atomic<uint32_t, Relaxed>& RunningIndex() {
+ static Atomic<uint32_t, Relaxed> sRunningIndex(0xdeadbeef);
+ return sRunningIndex;
+}
+static ProxyAutoConfig* GetRunning() {
+ MOZ_ASSERT(RunningIndex() != 0xdeadbeef);
+ return static_cast<ProxyAutoConfig*>(PR_GetThreadPrivate(RunningIndex()));
+}
+
+static void SetRunning(ProxyAutoConfig* arg) {
+ MOZ_ASSERT(RunningIndex() != 0xdeadbeef);
+ MOZ_DIAGNOSTIC_ASSERT_IF(!arg, GetRunning() != nullptr);
+ MOZ_DIAGNOSTIC_ASSERT_IF(arg, GetRunning() == nullptr);
+ PR_SetThreadPrivate(RunningIndex(), arg);
+}
+
+// The PACResolver is used for dnsResolve()
+class PACResolver final : public nsIDNSListener,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit PACResolver(nsIEventTarget* aTarget)
+ : mStatus(NS_ERROR_FAILURE),
+ mMainThreadEventTarget(aTarget),
+ mMutex("PACResolver::Mutex") {}
+
+ // nsIDNSListener
+ NS_IMETHOD OnLookupComplete(nsICancelable* request, nsIDNSRecord* record,
+ nsresult status) override {
+ nsCOMPtr<nsITimer> timer;
+ {
+ MutexAutoLock lock(mMutex);
+ timer.swap(mTimer);
+ mRequest = nullptr;
+ }
+
+ if (timer) {
+ timer->Cancel();
+ }
+
+ mStatus = status;
+ mResponse = record;
+ return NS_OK;
+ }
+
+ // nsITimerCallback
+ NS_IMETHOD Notify(nsITimer* timer) override {
+ nsCOMPtr<nsICancelable> request;
+ {
+ MutexAutoLock lock(mMutex);
+ request.swap(mRequest);
+ mTimer = nullptr;
+ }
+ if (request) {
+ request->Cancel(NS_ERROR_NET_TIMEOUT);
+ }
+ return NS_OK;
+ }
+
+ // nsINamed
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("PACResolver");
+ return NS_OK;
+ }
+
+ nsresult mStatus;
+ nsCOMPtr<nsICancelable> mRequest;
+ nsCOMPtr<nsIDNSRecord> mResponse;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+ Mutex mMutex MOZ_UNANNOTATED;
+
+ private:
+ ~PACResolver() = default;
+};
+NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback, nsINamed)
+
+static void PACLogToConsole(nsString& aMessage) {
+ if (XRE_IsSocketProcess()) {
+ auto task = [message(aMessage)]() {
+ SocketProcessChild* child = SocketProcessChild::GetSingleton();
+ if (child) {
+ Unused << child->SendOnConsoleMessage(message);
+ }
+ };
+ if (NS_IsMainThread()) {
+ task();
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction("PACLogToConsole", task));
+ }
+ return;
+ }
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!consoleService) return;
+
+ consoleService->LogStringMessage(aMessage.get());
+}
+
+// Javascript errors and warnings are logged to the main error console
+static void PACLogErrorOrWarning(const nsAString& aKind,
+ JSErrorReport* aReport) {
+ nsString formattedMessage(u"PAC Execution "_ns);
+ formattedMessage += aKind;
+ formattedMessage += u": "_ns;
+ if (aReport->message()) {
+ formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str()));
+ }
+ formattedMessage += u" ["_ns;
+ formattedMessage.Append(aReport->linebuf(), aReport->linebufLength());
+ formattedMessage += u"]"_ns;
+ PACLogToConsole(formattedMessage);
+}
+
+static void PACWarningReporter(JSContext* aCx, JSErrorReport* aReport) {
+ MOZ_ASSERT(aReport);
+ MOZ_ASSERT(aReport->isWarning());
+
+ PACLogErrorOrWarning(u"Warning"_ns, aReport);
+}
+
+class MOZ_STACK_CLASS AutoPACErrorReporter {
+ JSContext* mCx;
+
+ public:
+ explicit AutoPACErrorReporter(JSContext* aCx) : mCx(aCx) {}
+ ~AutoPACErrorReporter() {
+ if (!JS_IsExceptionPending(mCx)) {
+ return;
+ }
+ JS::ExceptionStack exnStack(mCx);
+ if (!JS::StealPendingExceptionStack(mCx, &exnStack)) {
+ return;
+ }
+
+ JS::ErrorReportBuilder report(mCx);
+ if (!report.init(mCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
+ JS_ClearPendingException(mCx);
+ return;
+ }
+
+ PACLogErrorOrWarning(u"Error"_ns, report.report());
+ }
+};
+
+// timeout of 0 means the normal necko timeout strategy, otherwise the dns
+// request will be canceled after aTimeout milliseconds
+static bool PACResolve(const nsACString& aHostName, NetAddr* aNetAddr,
+ unsigned int aTimeout) {
+ if (!GetRunning()) {
+ NS_WARNING("PACResolve without a running ProxyAutoConfig object");
+ return false;
+ }
+
+ return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout);
+}
+
+ProxyAutoConfig::ProxyAutoConfig()
+
+{
+ MOZ_COUNT_CTOR(ProxyAutoConfig);
+}
+
+bool ProxyAutoConfig::ResolveAddress(const nsACString& aHostName,
+ NetAddr* aNetAddr, unsigned int aTimeout) {
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) return false;
+
+ RefPtr<PACResolver> helper = new PACResolver(mMainThreadEventTarget);
+ OriginAttributes attrs;
+
+ // When the PAC script attempts to resolve a domain, we must make sure we
+ // don't use TRR, otherwise the TRR channel might also attempt to resolve
+ // a name and we'll have a deadlock.
+ nsIDNSService::DNSFlags flags =
+ nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::GetFlagsFromTRRMode(nsIRequest::TRR_DISABLED_MODE);
+
+ if (NS_FAILED(dns->AsyncResolveNative(
+ aHostName, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags, nullptr,
+ helper, GetCurrentSerialEventTarget(), attrs,
+ getter_AddRefs(helper->mRequest)))) {
+ return false;
+ }
+
+ if (aTimeout && helper->mRequest) {
+ if (!mTimer) mTimer = NS_NewTimer();
+ if (mTimer) {
+ mTimer->SetTarget(mMainThreadEventTarget);
+ mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT);
+ helper->mTimer = mTimer;
+ }
+ }
+
+ // Spin the event loop of the pac thread until lookup is complete.
+ // nsPACman is responsible for keeping a queue and only allowing
+ // one PAC execution at a time even when it is called re-entrantly.
+ SpinEventLoopUntil("ProxyAutoConfig::ResolveAddress"_ns, [&, helper, this]() {
+ if (!helper->mRequest) {
+ return true;
+ }
+ if (this->mShutdown) {
+ NS_WARNING("mShutdown set with PAC request not cancelled");
+ MOZ_ASSERT(NS_FAILED(helper->mStatus));
+ return true;
+ }
+ return false;
+ });
+
+ if (NS_FAILED(helper->mStatus)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(helper->mResponse);
+ return !(!rec || NS_FAILED(rec->GetNextAddr(0, aNetAddr)));
+}
+
+static bool PACResolveToString(const nsACString& aHostName,
+ nsCString& aDottedDecimal,
+ unsigned int aTimeout) {
+ NetAddr netAddr;
+ if (!PACResolve(aHostName, &netAddr, aTimeout)) return false;
+
+ char dottedDecimal[128];
+ if (!netAddr.ToStringBuffer(dottedDecimal, sizeof(dottedDecimal))) {
+ return false;
+ }
+
+ aDottedDecimal.Assign(dottedDecimal);
+ return true;
+}
+
+// dnsResolve(host) javascript implementation
+static bool PACDnsResolve(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (NS_IsMainThread()) {
+ NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
+ return false;
+ }
+
+ if (!args.requireAtLeast(cx, "dnsResolve", 1)) return false;
+
+ // Previously we didn't check the type of the argument, so just converted it
+ // to string. A badly written PAC file oculd pass null or undefined here
+ // which could lead to odd results if there are any hosts called "null"
+ // on the network. See bug 1724345 comment 6.
+ if (!args[0].isString()) {
+ args.rval().setNull();
+ return true;
+ }
+
+ JS::Rooted<JSString*> arg1(cx);
+ arg1 = args[0].toString();
+
+ nsAutoJSString hostName;
+ nsAutoCString dottedDecimal;
+
+ if (!hostName.init(cx, arg1)) return false;
+ if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) {
+ JSString* dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ args.rval().setString(dottedDecimalString);
+ } else {
+ args.rval().setNull();
+ }
+
+ return true;
+}
+
+// myIpAddress() javascript implementation
+static bool PACMyIpAddress(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ if (NS_IsMainThread()) {
+ NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
+ return false;
+ }
+
+ if (!GetRunning()) {
+ NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object");
+ return false;
+ }
+
+ return GetRunning()->MyIPAddress(args);
+}
+
+// proxyAlert(msg) javascript implementation
+static bool PACProxyAlert(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "alert", 1)) return false;
+
+ JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0]));
+ if (!arg1) return false;
+
+ nsAutoJSString message;
+ if (!message.init(cx, arg1)) return false;
+
+ nsAutoString alertMessage;
+ alertMessage.AssignLiteral(u"PAC-alert: ");
+ alertMessage.Append(message);
+ PACLogToConsole(alertMessage);
+
+ args.rval().setUndefined(); /* return undefined */
+ return true;
+}
+
+static const JSFunctionSpec PACGlobalFunctions[] = {
+ JS_FN("dnsResolve", PACDnsResolve, 1, 0),
+
+ // a global "var pacUseMultihomedDNS = true;" will change behavior
+ // of myIpAddress to actively use DNS
+ JS_FN("myIpAddress", PACMyIpAddress, 0, 0),
+ JS_FN("alert", PACProxyAlert, 1, 0), JS_FS_END};
+
+// JSContextWrapper is a c++ object that manages the context for the JS engine
+// used on the PAC thread. It is initialized and destroyed on the PAC thread.
+class JSContextWrapper {
+ public:
+ static JSContextWrapper* Create(uint32_t aExtraHeapSize) {
+ JSContext* cx = JS_NewContext(JS::DefaultHeapMaxBytes + aExtraHeapSize);
+ if (NS_WARN_IF(!cx)) return nullptr;
+
+ JS::ContextOptionsRef(cx).setDisableIon().setDisableEvalSecurityChecks();
+
+ JSContextWrapper* entry = new JSContextWrapper(cx);
+ if (NS_FAILED(entry->Init())) {
+ delete entry;
+ return nullptr;
+ }
+
+ return entry;
+ }
+
+ JSContext* Context() const { return mContext; }
+
+ JSObject* Global() const { return mGlobal; }
+
+ ~JSContextWrapper() {
+ mGlobal = nullptr;
+
+ MOZ_COUNT_DTOR(JSContextWrapper);
+
+ if (mContext) {
+ JS_DestroyContext(mContext);
+ }
+ }
+
+ void SetOK() { mOK = true; }
+
+ bool IsOK() { return mOK; }
+
+ private:
+ JSContext* mContext;
+ JS::PersistentRooted<JSObject*> mGlobal;
+ bool mOK;
+
+ static const JSClass sGlobalClass;
+
+ explicit JSContextWrapper(JSContext* cx)
+ : mContext(cx), mGlobal(cx, nullptr), mOK(false) {
+ MOZ_COUNT_CTOR(JSContextWrapper);
+ }
+
+ nsresult Init() {
+ /*
+ * Not setting this will cause JS_CHECK_RECURSION to report false
+ * positives
+ */
+ JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024);
+
+ JS::SetWarningReporter(mContext, PACWarningReporter);
+
+ // When available, set the self-hosted shared memory to be read, so that
+ // we can decode the self-hosted content instead of parsing it.
+ {
+ auto& shm = xpc::SelfHostedShmem::GetSingleton();
+ JS::SelfHostedCache selfHostedContent = shm.Content();
+
+ if (!JS::InitSelfHostedCode(mContext, selfHostedContent)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ JS::RealmOptions options;
+ options.creationOptions().setNewCompartmentInSystemZone();
+ options.behaviors()
+ .setClampAndJitterTime(false)
+ .setReduceTimerPrecisionCallerType(
+ RTPCallerTypeToToken(RTPCallerType::Normal));
+ mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr,
+ JS::DontFireOnNewGlobalHook, options);
+ if (!mGlobal) {
+ JS_ClearPendingException(mContext);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ JS::Rooted<JSObject*> global(mContext, mGlobal);
+
+ JSAutoRealm ar(mContext, global);
+ AutoPACErrorReporter aper(mContext);
+ if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS_FireOnNewGlobalObject(mContext, global);
+
+ return NS_OK;
+ }
+};
+
+const JSClass JSContextWrapper::sGlobalClass = {"PACResolutionThreadGlobal",
+ JSCLASS_GLOBAL_FLAGS,
+ &JS::DefaultGlobalClassOps};
+
+void ProxyAutoConfig::SetThreadLocalIndex(uint32_t index) {
+ RunningIndex() = index;
+}
+
+nsresult ProxyAutoConfig::ConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData,
+ bool aIncludePath,
+ uint32_t aExtraHeapSize,
+ nsISerialEventTarget* aEventTarget) {
+ mShutdown = false; // Shutdown needs to be called prior to destruction
+
+ mPACURI = aPACURI;
+
+ // The full PAC script data is the concatenation of 1) the various functions
+ // exposed to PAC scripts in |sAsciiPacUtils| and 2) the user-provided PAC
+ // script data. Historically this was single-byte Latin-1 text (usually just
+ // ASCII, but bug 296163 has a real-world Latin-1 example). We now support
+ // UTF-8 if the full data validates as UTF-8, before falling back to Latin-1.
+ // (Technically this is a breaking change: intentional Latin-1 scripts that
+ // happen to be valid UTF-8 may have different behavior. We assume such cases
+ // are vanishingly rare.)
+ //
+ // Supporting both UTF-8 and Latin-1 requires that the functions exposed to
+ // PAC scripts be both UTF-8- and Latin-1-compatible: that is, they must be
+ // ASCII.
+ mConcatenatedPACData = sAsciiPacUtils;
+ if (!mConcatenatedPACData.Append(aPACScriptData, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mIncludePath = aIncludePath;
+ mExtraHeapSize = aExtraHeapSize;
+ mMainThreadEventTarget = aEventTarget;
+
+ if (!GetRunning()) return SetupJS();
+
+ mJSNeedsSetup = true;
+ return NS_OK;
+}
+
+nsresult ProxyAutoConfig::SetupJS() {
+ mJSNeedsSetup = false;
+ MOZ_DIAGNOSTIC_ASSERT(!GetRunning(), "JIT is running");
+ if (GetRunning()) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+#if defined(XP_MACOSX)
+ nsMacUtilsImpl::EnableTCSMIfAvailable();
+#endif
+
+ delete mJSContext;
+ mJSContext = nullptr;
+
+ if (mConcatenatedPACData.IsEmpty()) return NS_ERROR_FAILURE;
+
+ NS_GetCurrentThread()->SetCanInvokeJS(true);
+
+ mJSContext = JSContextWrapper::Create(mExtraHeapSize);
+ if (!mJSContext) return NS_ERROR_FAILURE;
+
+ JSContext* cx = mJSContext->Context();
+ JSAutoRealm ar(cx, mJSContext->Global());
+ AutoPACErrorReporter aper(cx);
+
+ // check if this is a data: uri so that we don't spam the js console with
+ // huge meaningless strings. this is not on the main thread, so it can't
+ // use nsIURI scheme methods
+ bool isDataURI =
+ nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5);
+
+ SetRunning(this);
+
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+
+ auto CompilePACScript = [this](JSContext* cx) -> JSScript* {
+ JS::CompileOptions options(cx);
+ options.setSkipFilenameValidation(true);
+ options.setFileAndLine(this->mPACURI.get(), 1);
+
+ // Per ProxyAutoConfig::Init, compile as UTF-8 if the full data is UTF-8,
+ // and otherwise inflate Latin-1 to UTF-16 and compile that.
+ const char* scriptData = this->mConcatenatedPACData.get();
+ size_t scriptLength = this->mConcatenatedPACData.Length();
+ if (mozilla::IsUtf8(mozilla::Span(scriptData, scriptLength))) {
+ JS::SourceText<Utf8Unit> srcBuf;
+ if (!srcBuf.init(cx, scriptData, scriptLength,
+ JS::SourceOwnership::Borrowed)) {
+ return nullptr;
+ }
+
+ return JS::Compile(cx, options, srcBuf);
+ }
+
+ // nsReadableUtils.h says that "ASCII" is a misnomer "for legacy reasons",
+ // and this handles not just ASCII but Latin-1 too.
+ NS_ConvertASCIItoUTF16 inflated(this->mConcatenatedPACData);
+
+ JS::SourceText<char16_t> source;
+ if (!source.init(cx, inflated.get(), inflated.Length(),
+ JS::SourceOwnership::Borrowed)) {
+ return nullptr;
+ }
+
+ return JS::Compile(cx, options, source);
+ };
+
+ JS::Rooted<JSScript*> script(cx, CompilePACScript(cx));
+ if (!script || !JS_ExecuteScript(cx, script)) {
+ nsString alertMessage(u"PAC file failed to install from "_ns);
+ if (isDataURI) {
+ alertMessage += u"data: URI"_ns;
+ } else {
+ alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
+ }
+ PACLogToConsole(alertMessage);
+ SetRunning(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+ SetRunning(nullptr);
+
+ mJSContext->SetOK();
+ nsString alertMessage(u"PAC file installed from "_ns);
+ if (isDataURI) {
+ alertMessage += u"data: URI"_ns;
+ } else {
+ alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
+ }
+ PACLogToConsole(alertMessage);
+
+ // we don't need these now
+ mConcatenatedPACData.Truncate();
+ mPACURI.Truncate();
+
+ return NS_OK;
+}
+
+void ProxyAutoConfig::GetProxyForURIWithCallback(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ std::function<void(nsresult aStatus, const nsACString& aResult)>&&
+ aCallback) {
+ nsAutoCString result;
+ nsresult status = GetProxyForURI(aTestURI, aTestHost, result);
+ aCallback(status, result);
+}
+
+nsresult ProxyAutoConfig::GetProxyForURI(const nsACString& aTestURI,
+ const nsACString& aTestHost,
+ nsACString& result) {
+ if (mJSNeedsSetup) SetupJS();
+
+ if (!mJSContext || !mJSContext->IsOK()) return NS_ERROR_NOT_AVAILABLE;
+
+ JSContext* cx = mJSContext->Context();
+ JSAutoRealm ar(cx, mJSContext->Global());
+ AutoPACErrorReporter aper(cx);
+
+ // the sRunning flag keeps a new PAC file from being installed
+ // while the event loop is spinning on a DNS function. Don't early return.
+ SetRunning(this);
+ mRunningHost = aTestHost;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCString clensedURI(aTestURI);
+
+ if (!mIncludePath) {
+ nsCOMPtr<nsIURLParser> urlParser =
+ do_GetService(NS_STDURLPARSER_CONTRACTID);
+ int32_t pathLen = 0;
+ if (urlParser) {
+ uint32_t schemePos;
+ int32_t schemeLen;
+ uint32_t authorityPos;
+ int32_t authorityLen;
+ uint32_t pathPos;
+ rv = urlParser->ParseURL(aTestURI.BeginReading(), aTestURI.Length(),
+ &schemePos, &schemeLen, &authorityPos,
+ &authorityLen, &pathPos, &pathLen);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ if (pathLen) {
+ // cut off the path but leave the initial slash
+ pathLen--;
+ }
+ aTestURI.Left(clensedURI, aTestURI.Length() - pathLen);
+ }
+ }
+
+ JS::Rooted<JSString*> uriString(
+ cx,
+ JS_NewStringCopyN(cx, clensedURI.BeginReading(), clensedURI.Length()));
+ JS::Rooted<JSString*> hostString(
+ cx, JS_NewStringCopyN(cx, aTestHost.BeginReading(), aTestHost.Length()));
+
+ if (uriString && hostString) {
+ JS::RootedValueArray<2> args(cx);
+ args[0].setString(uriString);
+ args[1].setString(hostString);
+
+ JS::Rooted<JS::Value> rval(cx);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+ bool ok = JS_CallFunctionName(cx, global, "FindProxyForURL", args, &rval);
+
+ if (ok && rval.isString()) {
+ nsAutoJSString pacString;
+ if (pacString.init(cx, rval.toString())) {
+ CopyUTF16toUTF8(pacString, result);
+ rv = NS_OK;
+ }
+ }
+ }
+
+ mRunningHost.Truncate();
+ SetRunning(nullptr);
+ return rv;
+}
+
+void ProxyAutoConfig::GC() {
+ if (!mJSContext || !mJSContext->IsOK()) return;
+
+ JSAutoRealm ar(mJSContext->Context(), mJSContext->Global());
+ JS_MaybeGC(mJSContext->Context());
+}
+
+ProxyAutoConfig::~ProxyAutoConfig() {
+ MOZ_COUNT_DTOR(ProxyAutoConfig);
+ MOZ_ASSERT(mShutdown, "Shutdown must be called before dtor.");
+ NS_ASSERTION(!mJSContext,
+ "~ProxyAutoConfig leaking JS context that "
+ "should have been deleted on pac thread");
+}
+
+void ProxyAutoConfig::Shutdown() {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread for shutdown");
+
+ if (NS_WARN_IF(GetRunning()) || mShutdown) {
+ return;
+ }
+
+ mShutdown = true;
+ delete mJSContext;
+ mJSContext = nullptr;
+}
+
+bool ProxyAutoConfig::SrcAddress(const NetAddr* remoteAddress,
+ nsCString& localAddress) {
+ PRFileDesc* fd;
+ fd = PR_OpenUDPSocket(remoteAddress->raw.family);
+ if (!fd) return false;
+
+ PRNetAddr prRemoteAddress;
+ NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress);
+ if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) {
+ PR_Close(fd);
+ return false;
+ }
+
+ PRNetAddr localName;
+ if (PR_GetSockName(fd, &localName) != PR_SUCCESS) {
+ PR_Close(fd);
+ return false;
+ }
+
+ PR_Close(fd);
+
+ char dottedDecimal[128];
+ if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) !=
+ PR_SUCCESS) {
+ return false;
+ }
+
+ localAddress.Assign(dottedDecimal);
+
+ return true;
+}
+
+// hostName is run through a dns lookup and then a udp socket is connected
+// to the result. If that all works, the local IP address of the socket is
+// returned to the javascript caller and |*aResult| is set to true. Otherwise
+// |*aResult| is set to false.
+bool ProxyAutoConfig::MyIPAddressTryHost(const nsACString& hostName,
+ unsigned int timeout,
+ const JS::CallArgs& aArgs,
+ bool* aResult) {
+ *aResult = false;
+
+ NetAddr remoteAddress;
+ nsAutoCString localDottedDecimal;
+ JSContext* cx = mJSContext->Context();
+
+ if (PACResolve(hostName, &remoteAddress, timeout) &&
+ SrcAddress(&remoteAddress, localDottedDecimal)) {
+ JSString* dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ *aResult = true;
+ aArgs.rval().setString(dottedDecimalString);
+ }
+ return true;
+}
+
+bool ProxyAutoConfig::MyIPAddress(const JS::CallArgs& aArgs) {
+ nsAutoCString remoteDottedDecimal;
+ nsAutoCString localDottedDecimal;
+ JSContext* cx = mJSContext->Context();
+ JS::Rooted<JS::Value> v(cx);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+
+ bool useMultihomedDNS =
+ JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) &&
+ !v.isUndefined() && ToBoolean(v);
+
+ // first, lookup the local address of a socket connected
+ // to the host of uri being resolved by the pac file. This is
+ // v6 safe.. but is the last step like that
+ bool rvalAssigned = false;
+ if (useMultihomedDNS) {
+ if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+ } else {
+ // we can still do the fancy multi homing thing if the host is a literal
+ if (HostIsIPLiteral(mRunningHost) &&
+ (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
+ rvalAssigned)) {
+ return rvalAssigned;
+ }
+ }
+
+ // next, look for a route to a public internet address that doesn't need DNS.
+ // This is the google anycast dns address, but it doesn't matter if it
+ // remains operable (as we don't contact it) as long as the address stays
+ // in commonly routed IP address space.
+ remoteDottedDecimal.AssignLiteral("8.8.8.8");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // finally, use the old algorithm based on the local hostname
+ nsAutoCString hostName;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ // without multihomedDNS use such a short timeout that we are basically
+ // just looking at the cache for raw dotted decimals
+ uint32_t timeout = useMultihomedDNS ? kTimeout : 1;
+ if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) &&
+ PACResolveToString(hostName, localDottedDecimal, timeout)) {
+ JSString* dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ aArgs.rval().setString(dottedDecimalString);
+ return true;
+ }
+
+ // next try a couple RFC 1918 variants.. maybe there is a
+ // local route
+ remoteDottedDecimal.AssignLiteral("192.168.0.1");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // more RFC 1918
+ remoteDottedDecimal.AssignLiteral("10.0.0.1");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // who knows? let's fallback to localhost
+ localDottedDecimal.AssignLiteral("127.0.0.1");
+ JSString* dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ aArgs.rval().setString(dottedDecimalString);
+ return true;
+}
+
+RemoteProxyAutoConfig::RemoteProxyAutoConfig() = default;
+
+RemoteProxyAutoConfig::~RemoteProxyAutoConfig() = default;
+
+nsresult RemoteProxyAutoConfig::Init(nsIThread* aPACThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ SocketProcessParent* socketProcessParent =
+ SocketProcessParent::GetSingleton();
+ if (!socketProcessParent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ ipc::Endpoint<PProxyAutoConfigParent> parent;
+ ipc::Endpoint<PProxyAutoConfigChild> child;
+ nsresult rv = PProxyAutoConfig::CreateEndpoints(
+ base::GetCurrentProcId(), socketProcessParent->OtherPid(), &parent,
+ &child);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Unused << socketProcessParent->SendInitProxyAutoConfigChild(std::move(child));
+ mProxyAutoConfigParent = new ProxyAutoConfigParent();
+ return aPACThread->Dispatch(
+ NS_NewRunnableFunction("ProxyAutoConfigParent::ProxyAutoConfigParent",
+ [proxyAutoConfigParent(mProxyAutoConfigParent),
+ endpoint{std::move(parent)}]() mutable {
+ proxyAutoConfigParent->Init(std::move(endpoint));
+ }));
+}
+
+nsresult RemoteProxyAutoConfig::ConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData,
+ bool aIncludePath,
+ uint32_t aExtraHeapSize,
+ nsISerialEventTarget*) {
+ Unused << mProxyAutoConfigParent->SendConfigurePAC(
+ aPACURI, aPACScriptData, aIncludePath, aExtraHeapSize);
+ return NS_OK;
+}
+
+void RemoteProxyAutoConfig::Shutdown() { mProxyAutoConfigParent->Close(); }
+
+void RemoteProxyAutoConfig::GC() {
+ // Do nothing. GC would be performed when there is not pending query in socket
+ // process.
+}
+
+void RemoteProxyAutoConfig::GetProxyForURIWithCallback(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ std::function<void(nsresult aStatus, const nsACString& aResult)>&&
+ aCallback) {
+ if (!mProxyAutoConfigParent->CanSend()) {
+ return;
+ }
+
+ mProxyAutoConfigParent->SendGetProxyForURI(
+ aTestURI, aTestHost,
+ [aCallback](std::tuple<nsresult, nsCString>&& aResult) {
+ auto [status, result] = aResult;
+ aCallback(status, result);
+ },
+ [aCallback](mozilla::ipc::ResponseRejectReason&& aReason) {
+ aCallback(NS_ERROR_FAILURE, ""_ns);
+ });
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ProxyAutoConfig.h b/netwerk/base/ProxyAutoConfig.h
new file mode 100644
index 0000000000..e01450eadd
--- /dev/null
+++ b/netwerk/base/ProxyAutoConfig.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ProxyAutoConfig_h__
+#define ProxyAutoConfig_h__
+
+#include <functional>
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+class nsIEventTarget;
+class nsITimer;
+class nsIThread;
+namespace JS {
+class CallArgs;
+} // namespace JS
+
+namespace mozilla {
+namespace net {
+
+class JSContextWrapper;
+class ProxyAutoConfigParent;
+union NetAddr;
+
+class ProxyAutoConfigBase {
+ public:
+ virtual ~ProxyAutoConfigBase() = default;
+ virtual nsresult Init(nsIThread* aPACThread) { return NS_OK; }
+ virtual nsresult ConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData,
+ bool aIncludePath, uint32_t aExtraHeapSize,
+ nsISerialEventTarget* aEventTarget) = 0;
+ virtual void SetThreadLocalIndex(uint32_t index) {}
+ virtual void Shutdown() = 0;
+ virtual void GC() = 0;
+ virtual void GetProxyForURIWithCallback(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ std::function<void(nsresult aStatus, const nsACString& aResult)>&&
+ aCallback) = 0;
+};
+
+// The ProxyAutoConfig class is meant to be created and run on a
+// non main thread. It synchronously resolves PAC files by blocking that
+// thread and running nested event loops. GetProxyForURI is not re-entrant.
+
+class ProxyAutoConfig : public ProxyAutoConfigBase {
+ public:
+ ProxyAutoConfig();
+ virtual ~ProxyAutoConfig();
+
+ nsresult ConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData, bool aIncludePath,
+ uint32_t aExtraHeapSize,
+ nsISerialEventTarget* aEventTarget) override;
+ void SetThreadLocalIndex(uint32_t index) override;
+ void Shutdown() override;
+ void GC() override;
+ bool MyIPAddress(const JS::CallArgs& aArgs);
+ bool ResolveAddress(const nsACString& aHostName, NetAddr* aNetAddr,
+ unsigned int aTimeout);
+
+ /**
+ * Get the proxy string for the specified URI. The proxy string is
+ * given by the following:
+ *
+ * result = proxy-spec *( proxy-sep proxy-spec )
+ * proxy-spec = direct-type | proxy-type LWS proxy-host [":" proxy-port]
+ * direct-type = "DIRECT"
+ * proxy-type = "PROXY" | "HTTP" | "HTTPS" | "SOCKS" | "SOCKS4" | "SOCKS5"
+ * proxy-sep = ";" LWS
+ * proxy-host = hostname | ipv4-address-literal
+ * proxy-port = <any 16-bit unsigned integer>
+ * LWS = *( SP | HT )
+ * SP = <US-ASCII SP, space (32)>
+ * HT = <US-ASCII HT, horizontal-tab (9)>
+ *
+ * NOTE: direct-type and proxy-type are case insensitive
+ * NOTE: SOCKS implies SOCKS4
+ *
+ * Examples:
+ * "PROXY proxy1.foo.com:8080; PROXY proxy2.foo.com:8080; DIRECT"
+ * "SOCKS socksproxy"
+ * "DIRECT"
+ *
+ * XXX add support for IPv6 address literals.
+ * XXX quote whatever the official standard is for PAC.
+ *
+ * @param aTestURI
+ * The URI as an ASCII string to test.
+ * @param aTestHost
+ * The ASCII hostname to test.
+ *
+ * @param result
+ * result string as defined above.
+ */
+ nsresult GetProxyForURI(const nsACString& aTestURI,
+ const nsACString& aTestHost, nsACString& result);
+
+ void GetProxyForURIWithCallback(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ std::function<void(nsresult aStatus, const nsACString& aResult)>&&
+ aCallback) override;
+
+ private:
+ // allow 665ms for myipaddress dns queries. That's 95th percentile.
+ const static unsigned int kTimeout = 665;
+
+ // used to compile the PAC file and setup the execution context
+ nsresult SetupJS();
+
+ bool SrcAddress(const NetAddr* remoteAddress, nsCString& localAddress);
+ bool MyIPAddressTryHost(const nsACString& hostName, unsigned int timeout,
+ const JS::CallArgs& aArgs, bool* aResult);
+
+ JSContextWrapper* mJSContext{nullptr};
+ bool mJSNeedsSetup{false};
+ bool mShutdown{true};
+ nsCString mConcatenatedPACData;
+ nsCString mPACURI;
+ bool mIncludePath{false};
+ uint32_t mExtraHeapSize{0};
+ nsCString mRunningHost;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+};
+
+class RemoteProxyAutoConfig : public ProxyAutoConfigBase {
+ public:
+ RemoteProxyAutoConfig();
+ virtual ~RemoteProxyAutoConfig();
+
+ nsresult Init(nsIThread* aPACThread) override;
+ nsresult ConfigurePAC(const nsACString& aPACURI,
+ const nsACString& aPACScriptData, bool aIncludePath,
+ uint32_t aExtraHeapSize,
+ nsISerialEventTarget* aEventTarget) override;
+ void Shutdown() override;
+ void GC() override;
+ void GetProxyForURIWithCallback(
+ const nsACString& aTestURI, const nsACString& aTestHost,
+ std::function<void(nsresult aStatus, const nsACString& aResult)>&&
+ aCallback) override;
+
+ private:
+ RefPtr<ProxyAutoConfigParent> mProxyAutoConfigParent;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // ProxyAutoConfig_h__
diff --git a/netwerk/base/ProxyConfig.h b/netwerk/base/ProxyConfig.h
new file mode 100644
index 0000000000..2deb1dd2a1
--- /dev/null
+++ b/netwerk/base/ProxyConfig.h
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_netwerk_base_proxy_config_h
+#define mozilla_netwerk_base_proxy_config_h
+
+#include <map>
+
+#include "nsCRT.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+// NOTE: This file is inspired by Chromium's code.
+// https://source.chromium.org/chromium/chromium/src/+/main:net/proxy_resolution/proxy_config.h.
+
+namespace mozilla {
+namespace net {
+
+// ProxyServer stores the {type, host, port} of a proxy server.
+// ProxyServer is immutable.
+class ProxyServer final {
+ public:
+ enum class ProxyType {
+ DIRECT = 0,
+ HTTP,
+ HTTPS,
+ SOCKS,
+ SOCKS4,
+ SOCKS5,
+ FTP,
+ // DEFAULT is a special type used on windows only.
+ DEFAULT
+ };
+
+ ProxyServer() = default;
+
+ ProxyServer(ProxyType aType, const nsACString& aHost, int32_t aPort)
+ : mType(aType), mHost(aHost), mPort(aPort) {}
+
+ const nsCString& Host() const { return mHost; }
+
+ int32_t Port() const { return mPort; }
+
+ ProxyType Type() const { return mType; }
+
+ void ToHostAndPortStr(nsACString& aOutput) {
+ aOutput.Truncate();
+ if (mType == ProxyType::DIRECT) {
+ return;
+ }
+
+ aOutput.Assign(mHost);
+ if (mPort != -1) {
+ aOutput.Append(':');
+ aOutput.AppendInt(mPort);
+ }
+ }
+
+ bool operator==(const ProxyServer& aOther) const {
+ return mType == aOther.mType && mHost == aOther.mHost &&
+ mPort == aOther.mPort;
+ }
+
+ bool operator!=(const ProxyServer& aOther) const {
+ return !(*this == aOther);
+ }
+
+ private:
+ ProxyType mType{ProxyType::DIRECT};
+ nsCString mHost;
+ int32_t mPort{-1};
+};
+
+// This class includes the information about proxy configuration.
+// It contains enabled proxy servers, exception list, and the url of PAC
+// script.
+class ProxyConfig {
+ public:
+ struct ProxyRules {
+ ProxyRules() = default;
+ ~ProxyRules() = default;
+
+ std::map<ProxyServer::ProxyType, ProxyServer> mProxyServers;
+ };
+
+ struct ProxyBypassRules {
+ ProxyBypassRules() = default;
+ ~ProxyBypassRules() = default;
+
+ CopyableTArray<nsCString> mExceptions;
+ };
+
+ ProxyConfig() = default;
+ ProxyConfig(const ProxyConfig& config);
+ ~ProxyConfig() = default;
+
+ ProxyRules& Rules() { return mRules; }
+
+ const ProxyRules& Rules() const { return mRules; }
+
+ ProxyBypassRules& ByPassRules() { return mBypassRules; }
+
+ const ProxyBypassRules& ByPassRules() const { return mBypassRules; }
+
+ void SetPACUrl(const nsACString& aUrl) { mPACUrl = aUrl; }
+
+ const nsCString& PACUrl() const { return mPACUrl; }
+
+ static ProxyServer::ProxyType ToProxyType(const char* aType) {
+ if (!aType) {
+ return ProxyServer::ProxyType::DIRECT;
+ }
+
+ if (nsCRT::strcasecmp(aType, "http") == 0) {
+ return ProxyServer::ProxyType::HTTP;
+ }
+ if (nsCRT::strcasecmp(aType, "https") == 0) {
+ return ProxyServer::ProxyType::HTTPS;
+ }
+ if (nsCRT::strcasecmp(aType, "socks") == 0) {
+ return ProxyServer::ProxyType::SOCKS;
+ }
+ if (nsCRT::strcasecmp(aType, "socks4") == 0) {
+ return ProxyServer::ProxyType::SOCKS4;
+ }
+ if (nsCRT::strcasecmp(aType, "socks5") == 0) {
+ return ProxyServer::ProxyType::SOCKS5;
+ }
+ if (nsCRT::strcasecmp(aType, "ftp") == 0) {
+ return ProxyServer::ProxyType::FTP;
+ }
+
+ return ProxyServer::ProxyType::DIRECT;
+ }
+
+ static void SetProxyResult(const char* aType, const nsACString& aHostPort,
+ nsACString& aResult) {
+ aResult.AssignASCII(aType);
+ aResult.Append(' ');
+ aResult.Append(aHostPort);
+ }
+
+ static void SetProxyResultDirect(nsACString& aResult) {
+ // For whatever reason, a proxy is not to be used.
+ aResult.AssignLiteral("DIRECT");
+ }
+
+ static void ProxyStrToResult(const nsACString& aSpecificProxy,
+ const nsACString& aDefaultProxy,
+ const nsACString& aSocksProxy,
+ nsACString& aResult) {
+ if (!aSpecificProxy.IsEmpty()) {
+ SetProxyResult("PROXY", aSpecificProxy,
+ aResult); // Protocol-specific proxy.
+ } else if (!aDefaultProxy.IsEmpty()) {
+ SetProxyResult("PROXY", aDefaultProxy, aResult); // Default proxy.
+ } else if (!aSocksProxy.IsEmpty()) {
+ SetProxyResult("SOCKS", aSocksProxy, aResult); // SOCKS proxy.
+ } else {
+ SetProxyResultDirect(aResult); // Direct connection.
+ }
+ }
+
+ void GetProxyString(const nsACString& aScheme, nsACString& aResult) {
+ SetProxyResultDirect(aResult);
+
+ nsAutoCString specificProxy;
+ nsAutoCString defaultProxy;
+ nsAutoCString socksProxy;
+ nsAutoCString prefix;
+ ToLowerCase(aScheme, prefix);
+ ProxyServer::ProxyType type = ProxyConfig::ToProxyType(prefix.get());
+ for (auto& [key, value] : mRules.mProxyServers) {
+ // Break the loop if we found a specific proxy.
+ if (key == type) {
+ value.ToHostAndPortStr(specificProxy);
+ break;
+ } else if (key == ProxyServer::ProxyType::DEFAULT) {
+ value.ToHostAndPortStr(defaultProxy);
+ } else if (key == ProxyServer::ProxyType::SOCKS) {
+ value.ToHostAndPortStr(socksProxy);
+ }
+ }
+
+ ProxyStrToResult(specificProxy, defaultProxy, socksProxy, aResult);
+ }
+
+ private:
+ nsCString mPACUrl;
+ ProxyRules mRules;
+ ProxyBypassRules mBypassRules;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_base_proxy_config_h
diff --git a/netwerk/base/RedirectChannelRegistrar.cpp b/netwerk/base/RedirectChannelRegistrar.cpp
new file mode 100644
index 0000000000..9aa687ed88
--- /dev/null
+++ b/netwerk/base/RedirectChannelRegistrar.cpp
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RedirectChannelRegistrar.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+StaticRefPtr<RedirectChannelRegistrar> RedirectChannelRegistrar::gSingleton;
+
+NS_IMPL_ISUPPORTS(RedirectChannelRegistrar, nsIRedirectChannelRegistrar)
+
+RedirectChannelRegistrar::RedirectChannelRegistrar()
+ : mRealChannels(32),
+ mParentChannels(32),
+ mLock("RedirectChannelRegistrar") {
+ MOZ_ASSERT(!gSingleton);
+}
+
+// static
+already_AddRefed<nsIRedirectChannelRegistrar>
+RedirectChannelRegistrar::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gSingleton) {
+ gSingleton = new RedirectChannelRegistrar();
+ ClearOnShutdown(&gSingleton);
+ }
+ return do_AddRef(gSingleton);
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::RegisterChannel(nsIChannel* channel, uint64_t id) {
+ MutexAutoLock lock(mLock);
+
+ mRealChannels.InsertOrUpdate(id, channel);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::GetRegisteredChannel(uint64_t id,
+ nsIChannel** _retval) {
+ MutexAutoLock lock(mLock);
+
+ if (!mRealChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::LinkChannels(uint64_t id, nsIParentChannel* channel,
+ nsIChannel** _retval) {
+ MutexAutoLock lock(mLock);
+
+ if (!mRealChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE;
+
+ mParentChannels.InsertOrUpdate(id, channel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::GetParentChannel(uint64_t id,
+ nsIParentChannel** _retval) {
+ MutexAutoLock lock(mLock);
+
+ if (!mParentChannels.Get(id, _retval)) return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::DeregisterChannels(uint64_t id) {
+ MutexAutoLock lock(mLock);
+
+ mRealChannels.Remove(id);
+ mParentChannels.Remove(id);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/RedirectChannelRegistrar.h b/netwerk/base/RedirectChannelRegistrar.h
new file mode 100644
index 0000000000..5041726043
--- /dev/null
+++ b/netwerk/base/RedirectChannelRegistrar.h
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RedirectChannelRegistrar_h__
+#define RedirectChannelRegistrar_h__
+
+#include "nsIRedirectChannelRegistrar.h"
+
+#include "nsIChannel.h"
+#include "nsIParentChannel.h"
+#include "nsInterfaceHashtable.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+class RedirectChannelRegistrar final : public nsIRedirectChannelRegistrar {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREDIRECTCHANNELREGISTRAR
+
+ RedirectChannelRegistrar();
+
+ private:
+ ~RedirectChannelRegistrar() = default;
+
+ public:
+ // Singleton accessor
+ static already_AddRefed<nsIRedirectChannelRegistrar> GetOrCreate();
+
+ protected:
+ using ChannelHashtable = nsInterfaceHashtable<nsUint64HashKey, nsIChannel>;
+ using ParentChannelHashtable =
+ nsInterfaceHashtable<nsUint64HashKey, nsIParentChannel>;
+
+ ChannelHashtable mRealChannels;
+ ParentChannelHashtable mParentChannels;
+ Mutex mLock MOZ_UNANNOTATED;
+
+ static StaticRefPtr<RedirectChannelRegistrar> gSingleton;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/RequestContextService.cpp b/netwerk/base/RequestContextService.cpp
new file mode 100644
index 0000000000..72331bdc1e
--- /dev/null
+++ b/netwerk/base/RequestContextService.cpp
@@ -0,0 +1,622 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDocShell.h"
+#include "mozilla/dom/Document.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIDocumentLoader.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsIXULRuntime.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "RequestContextService.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/PSpdyPush.h"
+
+#include "../protocol/http/nsHttpHandler.h"
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gRequestContextLog("RequestContext");
+#undef LOG
+#define LOG(args) MOZ_LOG(gRequestContextLog, LogLevel::Info, args)
+
+static StaticRefPtr<RequestContextService> gSingleton;
+
+// This is used to prevent adding tail pending requests after shutdown
+static bool sShutdown = false;
+
+// nsIRequestContext
+class RequestContext final : public nsIRequestContext,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTCONTEXT
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ explicit RequestContext(const uint64_t id);
+
+ private:
+ virtual ~RequestContext();
+
+ void ProcessTailQueue(nsresult aResult);
+ // Reschedules the timer if needed
+ void ScheduleUnblock();
+ // Hard-reschedules the timer
+ void RescheduleUntailTimer(TimeStamp const& now);
+
+ uint64_t mID;
+ Atomic<uint32_t> mBlockingTransactionCount;
+ UniquePtr<SpdyPushCache> mSpdyCache;
+
+ using PendingTailRequest = nsCOMPtr<nsIRequestTailUnblockCallback>;
+ // Number of known opened non-tailed requets
+ uint32_t mNonTailRequests;
+ // Queue of requests that have been tailed, when conditions are met
+ // we call each of them to unblock and drop the reference
+ nsTArray<PendingTailRequest> mTailQueue;
+ // Loosly scheduled timer, never scheduled further to the future than
+ // mUntailAt time
+ nsCOMPtr<nsITimer> mUntailTimer;
+ // Timestamp when the timer is expected to fire,
+ // always less than or equal to mUntailAt
+ TimeStamp mTimerScheduledAt;
+ // Timestamp when we want to actually untail queued requets based on
+ // the number of request count change in the past; iff this timestamp
+ // is set, we tail requests
+ TimeStamp mUntailAt;
+
+ // Timestamp of the navigation start time, set to Now() in BeginLoad().
+ // This is used to progressively lower the maximum delay time so that
+ // we can't get to a situation when a number of repetitive requests
+ // on the page causes forever tailing.
+ TimeStamp mBeginLoadTime;
+
+ // This member is true only between DOMContentLoaded notification and
+ // next document load beginning for this request context.
+ // Top level request contexts are recycled.
+ bool mAfterDOMContentLoaded;
+};
+
+NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext, nsITimerCallback, nsINamed)
+
+RequestContext::RequestContext(const uint64_t aID)
+ : mID(aID),
+ mBlockingTransactionCount(0),
+ mNonTailRequests(0),
+ mAfterDOMContentLoaded(false) {
+ LOG(("RequestContext::RequestContext this=%p id=%" PRIx64, this, mID));
+}
+
+RequestContext::~RequestContext() {
+ MOZ_ASSERT(mTailQueue.Length() == 0);
+
+ LOG(("RequestContext::~RequestContext this=%p blockers=%u", this,
+ static_cast<uint32_t>(mBlockingTransactionCount)));
+}
+
+NS_IMETHODIMP
+RequestContext::BeginLoad() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestContext::BeginLoad %p", this));
+
+ if (IsNeckoChild()) {
+ // Tailing is not supported on the child process
+ if (gNeckoChild) {
+ gNeckoChild->SendRequestContextLoadBegin(mID);
+ }
+ return NS_OK;
+ }
+
+ mAfterDOMContentLoaded = false;
+ mBeginLoadTime = TimeStamp::NowLoRes();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::DOMContentLoaded() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestContext::DOMContentLoaded %p", this));
+
+ if (IsNeckoChild()) {
+ // Tailing is not supported on the child process
+ if (gNeckoChild) {
+ gNeckoChild->SendRequestContextAfterDOMContentLoaded(mID);
+ }
+ return NS_OK;
+ }
+
+ if (mAfterDOMContentLoaded) {
+ // There is a possibility of a duplicate notification
+ return NS_OK;
+ }
+
+ mAfterDOMContentLoaded = true;
+
+ // Conditions for the delay calculation has changed.
+ ScheduleUnblock();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetBlockingTransactionCount(
+ uint32_t* aBlockingTransactionCount) {
+ NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
+ *aBlockingTransactionCount = mBlockingTransactionCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::AddBlockingTransaction() {
+ mBlockingTransactionCount++;
+ LOG(("RequestContext::AddBlockingTransaction this=%p blockers=%u", this,
+ static_cast<uint32_t>(mBlockingTransactionCount)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::RemoveBlockingTransaction(uint32_t* outval) {
+ NS_ENSURE_ARG_POINTER(outval);
+ mBlockingTransactionCount--;
+ LOG(("RequestContext::RemoveBlockingTransaction this=%p blockers=%u", this,
+ static_cast<uint32_t>(mBlockingTransactionCount)));
+ *outval = mBlockingTransactionCount;
+ return NS_OK;
+}
+
+SpdyPushCache* RequestContext::GetSpdyPushCache() { return mSpdyCache.get(); }
+
+void RequestContext::SetSpdyPushCache(SpdyPushCache* aSpdyPushCache) {
+ mSpdyCache = WrapUnique(aSpdyPushCache);
+}
+
+uint64_t RequestContext::GetID() { return mID; }
+
+NS_IMETHODIMP
+RequestContext::AddNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ++mNonTailRequests;
+ LOG(("RequestContext::AddNonTailRequest this=%p, cnt=%u", this,
+ mNonTailRequests));
+
+ ScheduleUnblock();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::RemoveNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mNonTailRequests > 0);
+
+ LOG(("RequestContext::RemoveNonTailRequest this=%p, cnt=%u", this,
+ mNonTailRequests - 1));
+
+ --mNonTailRequests;
+
+ ScheduleUnblock();
+ return NS_OK;
+}
+
+void RequestContext::ScheduleUnblock() {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gHttpHandler) {
+ return;
+ }
+
+ uint32_t quantum =
+ gHttpHandler->TailBlockingDelayQuantum(mAfterDOMContentLoaded);
+ uint32_t delayMax = gHttpHandler->TailBlockingDelayMax();
+ uint32_t totalMax = gHttpHandler->TailBlockingTotalMax();
+
+ if (!mBeginLoadTime.IsNull()) {
+ // We decrease the maximum delay progressively with the time since the page
+ // load begin. This seems like a reasonable and clear heuristic allowing us
+ // to start loading tailed requests in a deterministic time after the load
+ // has started.
+
+ uint32_t sinceBeginLoad = static_cast<uint32_t>(
+ (TimeStamp::NowLoRes() - mBeginLoadTime).ToMilliseconds());
+ uint32_t tillTotal = totalMax - std::min(sinceBeginLoad, totalMax);
+ uint32_t proportion = totalMax // values clamped between 0 and 60'000
+ ? (delayMax * tillTotal) / totalMax
+ : 0;
+ delayMax = std::min(delayMax, proportion);
+ }
+
+ CheckedInt<uint32_t> delay = quantum * mNonTailRequests;
+
+ if (!mAfterDOMContentLoaded) {
+ // Before DOMContentLoaded notification we want to make sure that tailed
+ // requests don't start when there is a short delay during which we may
+ // not have any active requests on the page happening.
+ delay += quantum;
+ }
+
+ if (!delay.isValid() || delay.value() > delayMax) {
+ delay = delayMax;
+ }
+
+ LOG(
+ ("RequestContext::ScheduleUnblock this=%p non-tails=%u tail-queue=%zu "
+ "delay=%u after-DCL=%d",
+ this, mNonTailRequests, mTailQueue.Length(), delay.value(),
+ mAfterDOMContentLoaded));
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ mUntailAt = now + TimeDuration::FromMilliseconds(delay.value());
+
+ if (mTimerScheduledAt.IsNull() || mUntailAt < mTimerScheduledAt) {
+ LOG(("RequestContext %p timer would fire too late, rescheduling", this));
+ RescheduleUntailTimer(now);
+ }
+}
+
+void RequestContext::RescheduleUntailTimer(TimeStamp const& now) {
+ MOZ_ASSERT(mUntailAt >= now);
+
+ if (mUntailTimer) {
+ mUntailTimer->Cancel();
+ }
+
+ if (!mTailQueue.Length()) {
+ mUntailTimer = nullptr;
+ mTimerScheduledAt = TimeStamp();
+ return;
+ }
+
+ TimeDuration interval = mUntailAt - now;
+ if (!mTimerScheduledAt.IsNull() && mUntailAt < mTimerScheduledAt) {
+ // When the number of untailed requests goes down,
+ // let's half the interval, since it's likely we would
+ // reschedule for a shorter time again very soon.
+ // This will likely save rescheduling this timer.
+ interval = interval / int64_t(2);
+ mTimerScheduledAt = mUntailAt - interval;
+ } else {
+ mTimerScheduledAt = mUntailAt;
+ }
+
+ uint32_t delay = interval.ToMilliseconds();
+ nsresult rv =
+ NS_NewTimerWithCallback(getter_AddRefs(mUntailTimer), this, delay,
+ nsITimer::TYPE_ONE_SHOT, nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not reschedule untail timer");
+ }
+
+ LOG(("RequestContext::RescheduleUntailTimer %p in %d", this, delay));
+}
+
+NS_IMETHODIMP
+RequestContext::Notify(nsITimer* timer) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(timer == mUntailTimer);
+ MOZ_ASSERT(!mTimerScheduledAt.IsNull());
+ MOZ_ASSERT(mTailQueue.Length());
+
+ mUntailTimer = nullptr;
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ if (mUntailAt > mTimerScheduledAt && mUntailAt > now) {
+ LOG(("RequestContext %p timer fired too soon, rescheduling", this));
+ RescheduleUntailTimer(now);
+ return NS_OK;
+ }
+
+ // Must drop to allow re-engage of the timer
+ mTimerScheduledAt = TimeStamp();
+
+ ProcessTailQueue(NS_OK);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::GetName(nsACString& aName) {
+ aName.AssignLiteral("RequestContext");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::IsContextTailBlocked(nsIRequestTailUnblockCallback* aRequest,
+ bool* aBlocked) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("RequestContext::IsContextTailBlocked this=%p, request=%p, queued=%zu",
+ this, aRequest, mTailQueue.Length()));
+
+ *aBlocked = false;
+
+ if (sShutdown) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ if (mUntailAt.IsNull()) {
+ LOG((" untail time passed"));
+ return NS_OK;
+ }
+
+ if (mAfterDOMContentLoaded && !mNonTailRequests) {
+ LOG((" after DOMContentLoaded and no untailed requests"));
+ return NS_OK;
+ }
+
+ if (!gHttpHandler) {
+ // Xpcshell tests may not have http handler
+ LOG((" missing gHttpHandler?"));
+ return NS_OK;
+ }
+
+ *aBlocked = true;
+ mTailQueue.AppendElement(aRequest);
+
+ LOG((" request queued"));
+
+ if (!mUntailTimer) {
+ ScheduleUnblock();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContext::CancelTailedRequest(nsIRequestTailUnblockCallback* aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool removed = mTailQueue.RemoveElement(aRequest);
+
+ LOG(("RequestContext::CancelTailedRequest %p req=%p removed=%d", this,
+ aRequest, removed));
+
+ // Stop untail timer if all tail requests are canceled.
+ if (removed && mTailQueue.IsEmpty()) {
+ if (mUntailTimer) {
+ mUntailTimer->Cancel();
+ mUntailTimer = nullptr;
+ }
+
+ // Must drop to allow re-engage of the timer
+ mTimerScheduledAt = TimeStamp();
+ }
+
+ return NS_OK;
+}
+
+void RequestContext::ProcessTailQueue(nsresult aResult) {
+ LOG(("RequestContext::ProcessTailQueue this=%p, queued=%zu, rv=%" PRIx32,
+ this, mTailQueue.Length(), static_cast<uint32_t>(aResult)));
+
+ if (mUntailTimer) {
+ mUntailTimer->Cancel();
+ mUntailTimer = nullptr;
+ }
+
+ // Must drop to stop tailing requests
+ mUntailAt = TimeStamp();
+
+ nsTArray<PendingTailRequest> queue = std::move(mTailQueue);
+
+ for (const auto& request : queue) {
+ LOG((" untailing %p", request.get()));
+ request->OnTailUnblock(aResult);
+ }
+}
+
+NS_IMETHODIMP
+RequestContext::CancelTailPendingRequests(nsresult aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(NS_FAILED(aResult));
+
+ ProcessTailQueue(aResult);
+ return NS_OK;
+}
+
+// nsIRequestContextService
+RequestContextService* RequestContextService::sSelf = nullptr;
+
+NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver)
+
+RequestContextService::RequestContextService() {
+ MOZ_ASSERT(!sSelf, "multiple rcs instances!");
+ MOZ_ASSERT(NS_IsMainThread());
+ sSelf = this;
+
+ nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
+ runtime->GetProcessID(&mRCIDNamespace);
+}
+
+RequestContextService::~RequestContextService() {
+ MOZ_ASSERT(NS_IsMainThread());
+ Shutdown();
+ sSelf = nullptr;
+}
+
+nsresult RequestContextService::Init() {
+ nsresult rv;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ obs->AddObserver(this, "content-document-interactive", false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void RequestContextService::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ // We need to do this to prevent the requests from being scheduled after
+ // shutdown.
+ for (const auto& data : mTable.Values()) {
+ data->CancelTailPendingRequests(NS_ERROR_ABORT);
+ }
+ mTable.Clear();
+ sShutdown = true;
+}
+
+/* static */
+already_AddRefed<nsIRequestContextService>
+RequestContextService::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sShutdown) {
+ return nullptr;
+ }
+
+ RefPtr<RequestContextService> svc;
+ if (gSingleton) {
+ svc = gSingleton;
+ } else {
+ svc = new RequestContextService();
+ nsresult rv = svc->Init();
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ gSingleton = svc;
+ ClearOnShutdown(&gSingleton);
+ }
+
+ return svc.forget();
+}
+
+NS_IMETHODIMP
+RequestContextService::GetRequestContext(const uint64_t rcID,
+ nsIRequestContext** rc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(rc);
+ *rc = nullptr;
+
+ if (sShutdown) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ if (!rcID) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *rc = do_AddRef(mTable.LookupOrInsertWith(rcID, [&] {
+ nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
+ return newSC;
+ })).take();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::GetRequestContextFromLoadGroup(nsILoadGroup* aLoadGroup,
+ nsIRequestContext** rc) {
+ nsresult rv;
+
+ uint64_t rcID;
+ rv = aLoadGroup->GetRequestContextID(&rcID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return GetRequestContext(rcID, rc);
+}
+
+NS_IMETHODIMP
+RequestContextService::NewRequestContext(nsIRequestContext** rc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(rc);
+ *rc = nullptr;
+
+ if (sShutdown) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ uint64_t rcID =
+ ((static_cast<uint64_t>(mRCIDNamespace) << 32) & 0xFFFFFFFF00000000LL) |
+ mNextRCID++;
+
+ nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
+ mTable.InsertOrUpdate(rcID, newSC);
+ newSC.swap(*rc);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::RemoveRequestContext(const uint64_t rcID) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mTable.Remove(rcID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestContextService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data_unicode) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ if (!strcmp("content-document-interactive", topic)) {
+ nsCOMPtr<dom::Document> document(do_QueryInterface(subject));
+ MOZ_ASSERT(document);
+ // We want this be triggered also for iframes, since those track their
+ // own request context ids.
+ if (!document) {
+ return NS_OK;
+ }
+ nsIDocShell* ds = document->GetDocShell();
+ // XML documents don't always have a docshell assigned
+ if (!ds) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocumentLoader> dl(do_QueryInterface(ds));
+ if (!dl) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsILoadGroup> lg;
+ dl->GetLoadGroup(getter_AddRefs(lg));
+ if (!lg) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIRequestContext> rc;
+ GetRequestContextFromLoadGroup(lg, getter_AddRefs(rc));
+ if (rc) {
+ rc->DOMContentLoaded();
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(false, "Unexpected observer topic");
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/base/RequestContextService.h b/netwerk/base/RequestContextService.h
new file mode 100644
index 0000000000..4aafdb8ed6
--- /dev/null
+++ b/netwerk/base/RequestContextService.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla__net__RequestContextService_h
+#define mozilla__net__RequestContextService_h
+
+#include "nsCOMPtr.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIObserver.h"
+#include "nsIRequestContext.h"
+
+namespace mozilla {
+namespace net {
+
+class RequestContextService final : public nsIRequestContextService,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTCONTEXTSERVICE
+ NS_DECL_NSIOBSERVER
+
+ static already_AddRefed<nsIRequestContextService> GetOrCreate();
+
+ private:
+ RequestContextService();
+ virtual ~RequestContextService();
+
+ nsresult Init();
+ void Shutdown();
+
+ static RequestContextService* sSelf;
+
+ nsInterfaceHashtable<nsUint64HashKey, nsIRequestContext> mTable;
+ uint32_t mRCIDNamespace{0};
+ uint32_t mNextRCID{1};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla__net__RequestContextService_h
diff --git a/netwerk/base/SSLTokensCache.cpp b/netwerk/base/SSLTokensCache.cpp
new file mode 100644
index 0000000000..cf739814f5
--- /dev/null
+++ b/netwerk/base/SSLTokensCache.cpp
@@ -0,0 +1,568 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SSLTokensCache.h"
+
+#include "CertVerifier.h"
+#include "CommonSocketControl.h"
+#include "TransportSecurityInfo.h"
+#include "mozilla/ArrayAlgorithm.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "nsIOService.h"
+#include "ssl.h"
+#include "sslexp.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gSSLTokensCacheLog("SSLTokensCache");
+#undef LOG
+#define LOG(args) MOZ_LOG(gSSLTokensCacheLog, mozilla::LogLevel::Debug, args)
+#undef LOG5_ENABLED
+#define LOG5_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gSSLTokensCacheLog, mozilla::LogLevel::Verbose)
+
+class ExpirationComparator {
+ public:
+ bool Equals(SSLTokensCache::TokenCacheRecord* a,
+ SSLTokensCache::TokenCacheRecord* b) const {
+ return a->mExpirationTime == b->mExpirationTime;
+ }
+ bool LessThan(SSLTokensCache::TokenCacheRecord* a,
+ SSLTokensCache::TokenCacheRecord* b) const {
+ return a->mExpirationTime < b->mExpirationTime;
+ }
+};
+
+SessionCacheInfo SessionCacheInfo::Clone() const {
+ SessionCacheInfo result;
+ result.mEVStatus = mEVStatus;
+ result.mCertificateTransparencyStatus = mCertificateTransparencyStatus;
+ result.mServerCertBytes = mServerCertBytes.Clone();
+ result.mSucceededCertChainBytes =
+ mSucceededCertChainBytes
+ ? Some(TransformIntoNewArray(
+ *mSucceededCertChainBytes,
+ [](const auto& element) { return element.Clone(); }))
+ : Nothing();
+ result.mIsBuiltCertChainRootBuiltInRoot = mIsBuiltCertChainRootBuiltInRoot;
+ result.mOverridableErrorCategory = mOverridableErrorCategory;
+ result.mFailedCertChainBytes =
+ mFailedCertChainBytes
+ ? Some(TransformIntoNewArray(
+ *mFailedCertChainBytes,
+ [](const auto& element) { return element.Clone(); }))
+ : Nothing();
+ return result;
+}
+
+StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance;
+StaticMutex SSLTokensCache::sLock;
+uint64_t SSLTokensCache::sRecordId = 0;
+
+SSLTokensCache::TokenCacheRecord::~TokenCacheRecord() {
+ if (!gInstance) {
+ return;
+ }
+
+ gInstance->OnRecordDestroyed(this);
+}
+
+uint32_t SSLTokensCache::TokenCacheRecord::Size() const {
+ uint32_t size = mToken.Length() + sizeof(mSessionCacheInfo.mEVStatus) +
+ sizeof(mSessionCacheInfo.mCertificateTransparencyStatus) +
+ mSessionCacheInfo.mServerCertBytes.Length() +
+ sizeof(mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot) +
+ sizeof(mSessionCacheInfo.mOverridableErrorCategory);
+ if (mSessionCacheInfo.mSucceededCertChainBytes) {
+ for (const auto& cert : mSessionCacheInfo.mSucceededCertChainBytes.ref()) {
+ size += cert.Length();
+ }
+ }
+ if (mSessionCacheInfo.mFailedCertChainBytes) {
+ for (const auto& cert : mSessionCacheInfo.mFailedCertChainBytes.ref()) {
+ size += cert.Length();
+ }
+ }
+ return size;
+}
+
+void SSLTokensCache::TokenCacheRecord::Reset() {
+ mToken.Clear();
+ mExpirationTime = 0;
+ mSessionCacheInfo.mEVStatus = psm::EVStatus::NotEV;
+ mSessionCacheInfo.mCertificateTransparencyStatus =
+ nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
+ mSessionCacheInfo.mServerCertBytes.Clear();
+ mSessionCacheInfo.mSucceededCertChainBytes.reset();
+ mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot.reset();
+ mSessionCacheInfo.mOverridableErrorCategory =
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET;
+ mSessionCacheInfo.mFailedCertChainBytes.reset();
+}
+
+uint32_t SSLTokensCache::TokenCacheEntry::Size() const {
+ uint32_t size = 0;
+ for (const auto& rec : mRecords) {
+ size += rec->Size();
+ }
+ return size;
+}
+
+void SSLTokensCache::TokenCacheEntry::AddRecord(
+ UniquePtr<SSLTokensCache::TokenCacheRecord>&& aRecord,
+ nsTArray<TokenCacheRecord*>& aExpirationArray) {
+ if (mRecords.Length() ==
+ StaticPrefs::network_ssl_tokens_cache_records_per_entry()) {
+ aExpirationArray.RemoveElement(mRecords[0].get());
+ mRecords.RemoveElementAt(0);
+ }
+
+ aExpirationArray.AppendElement(aRecord.get());
+ for (int32_t i = mRecords.Length() - 1; i >= 0; --i) {
+ if (aRecord->mExpirationTime > mRecords[i]->mExpirationTime) {
+ mRecords.InsertElementAt(i + 1, std::move(aRecord));
+ return;
+ }
+ }
+ mRecords.InsertElementAt(0, std::move(aRecord));
+}
+
+UniquePtr<SSLTokensCache::TokenCacheRecord>
+SSLTokensCache::TokenCacheEntry::RemoveWithId(uint64_t aId) {
+ for (int32_t i = mRecords.Length() - 1; i >= 0; --i) {
+ if (mRecords[i]->mId == aId) {
+ UniquePtr<TokenCacheRecord> record = std::move(mRecords[i]);
+ mRecords.RemoveElementAt(i);
+ return record;
+ }
+ }
+ return nullptr;
+}
+
+const UniquePtr<SSLTokensCache::TokenCacheRecord>&
+SSLTokensCache::TokenCacheEntry::Get() {
+ return mRecords[0];
+}
+
+NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter)
+
+// static
+nsresult SSLTokensCache::Init() {
+ StaticMutexAutoLock lock(sLock);
+
+ // SSLTokensCache should be only used in parent process and socket process.
+ // Ideally, parent process should not use this when socket process is enabled.
+ // However, some xpcsehll tests may need to create and use sockets directly,
+ // so we still allow to use this in parent process no matter socket process is
+ // enabled or not.
+ if (!(XRE_IsSocketProcess() || XRE_IsParentProcess())) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!gInstance);
+
+ gInstance = new SSLTokensCache();
+
+ RegisterWeakMemoryReporter(gInstance);
+
+ return NS_OK;
+}
+
+// static
+nsresult SSLTokensCache::Shutdown() {
+ StaticMutexAutoLock lock(sLock);
+
+ if (!gInstance) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ UnregisterWeakMemoryReporter(gInstance);
+
+ gInstance = nullptr;
+
+ return NS_OK;
+}
+
+SSLTokensCache::SSLTokensCache() { LOG(("SSLTokensCache::SSLTokensCache")); }
+
+SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); }
+
+// static
+nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen,
+ CommonSocketControl* aSocketControl) {
+ PRUint32 expirationTime;
+ SSLResumptionTokenInfo tokenInfo;
+ if (SSL_GetResumptionTokenInfo(aToken, aTokenLen, &tokenInfo,
+ sizeof(tokenInfo)) != SECSuccess) {
+ LOG((" cannot get expiration time from the token, NSS error %d",
+ PORT_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ expirationTime = tokenInfo.expirationTime;
+ SSL_DestroyResumptionTokenInfo(&tokenInfo);
+
+ return Put(aKey, aToken, aTokenLen, aSocketControl, expirationTime);
+}
+
+// static
+nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen,
+ CommonSocketControl* aSocketControl,
+ PRUint32 aExpirationTime) {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("SSLTokensCache::Put [key=%s, tokenLen=%u]",
+ PromiseFlatCString(aKey).get(), aTokenLen));
+
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!aSocketControl) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ nsresult rv = aSocketControl->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIX509Cert> cert;
+ securityInfo->GetServerCert(getter_AddRefs(cert));
+ if (!cert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<uint8_t> certBytes;
+ rv = cert->GetRawDER(certBytes);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Maybe<nsTArray<nsTArray<uint8_t>>> succeededCertChainBytes;
+ nsTArray<RefPtr<nsIX509Cert>> succeededCertArray;
+ rv = securityInfo->GetSucceededCertChain(succeededCertArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Maybe<bool> isBuiltCertChainRootBuiltInRoot;
+ if (!succeededCertArray.IsEmpty()) {
+ succeededCertChainBytes.emplace();
+ for (const auto& cert : succeededCertArray) {
+ nsTArray<uint8_t> rawCert;
+ nsresult rv = cert->GetRawDER(rawCert);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ succeededCertChainBytes->AppendElement(std::move(rawCert));
+ }
+
+ bool builtInRoot = false;
+ rv = securityInfo->GetIsBuiltCertChainRootBuiltInRoot(&builtInRoot);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ isBuiltCertChainRootBuiltInRoot.emplace(builtInRoot);
+ }
+
+ bool isEV;
+ rv = securityInfo->GetIsExtendedValidation(&isEV);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint16_t certificateTransparencyStatus;
+ rv = securityInfo->GetCertificateTransparencyStatus(
+ &certificateTransparencyStatus);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory;
+ rv = securityInfo->GetOverridableErrorCategory(&overridableErrorCategory);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Maybe<nsTArray<nsTArray<uint8_t>>> failedCertChainBytes;
+ nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
+ rv = securityInfo->GetFailedCertChain(failedCertArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!failedCertArray.IsEmpty()) {
+ failedCertChainBytes.emplace();
+ for (const auto& cert : failedCertArray) {
+ nsTArray<uint8_t> rawCert;
+ nsresult rv = cert->GetRawDER(rawCert);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ failedCertChainBytes->AppendElement(std::move(rawCert));
+ }
+ }
+
+ auto makeUniqueRecord = [&]() {
+ auto rec = MakeUnique<TokenCacheRecord>();
+ rec->mKey = aKey;
+ rec->mExpirationTime = aExpirationTime;
+ MOZ_ASSERT(rec->mToken.IsEmpty());
+ rec->mToken.AppendElements(aToken, aTokenLen);
+ rec->mId = ++sRecordId;
+ rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes);
+ rec->mSessionCacheInfo.mSucceededCertChainBytes =
+ std::move(succeededCertChainBytes);
+ if (isEV) {
+ rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV;
+ }
+ rec->mSessionCacheInfo.mCertificateTransparencyStatus =
+ certificateTransparencyStatus;
+ rec->mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot =
+ std::move(isBuiltCertChainRootBuiltInRoot);
+ rec->mSessionCacheInfo.mOverridableErrorCategory = overridableErrorCategory;
+ rec->mSessionCacheInfo.mFailedCertChainBytes =
+ std::move(failedCertChainBytes);
+ return rec;
+ };
+
+ TokenCacheEntry* const cacheEntry =
+ gInstance->mTokenCacheRecords.WithEntryHandle(aKey, [&](auto&& entry) {
+ if (!entry) {
+ auto rec = makeUniqueRecord();
+ auto cacheEntry = MakeUnique<TokenCacheEntry>();
+ cacheEntry->AddRecord(std::move(rec), gInstance->mExpirationArray);
+ entry.Insert(std::move(cacheEntry));
+ } else {
+ // To make sure the cache size is synced, we take away the size of
+ // whole entry and add it back later.
+ gInstance->mCacheSize -= entry.Data()->Size();
+ entry.Data()->AddRecord(makeUniqueRecord(),
+ gInstance->mExpirationArray);
+ }
+
+ return entry->get();
+ });
+
+ gInstance->mCacheSize += cacheEntry->Size();
+
+ gInstance->LogStats();
+
+ gInstance->EvictIfNecessary();
+
+ return NS_OK;
+}
+
+// static
+nsresult SSLTokensCache::Get(const nsACString& aKey, nsTArray<uint8_t>& aToken,
+ SessionCacheInfo& aResult, uint64_t* aTokenId) {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("SSLTokensCache::Get [key=%s]", PromiseFlatCString(aKey).get()));
+
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return gInstance->GetLocked(aKey, aToken, aResult, aTokenId);
+}
+
+nsresult SSLTokensCache::GetLocked(const nsACString& aKey,
+ nsTArray<uint8_t>& aToken,
+ SessionCacheInfo& aResult,
+ uint64_t* aTokenId) {
+ sLock.AssertCurrentThreadOwns();
+
+ TokenCacheEntry* cacheEntry = nullptr;
+
+ if (mTokenCacheRecords.Get(aKey, &cacheEntry)) {
+ if (cacheEntry->RecordCount() == 0) {
+ MOZ_ASSERT(false, "Found a cacheEntry with no records");
+ mTokenCacheRecords.Remove(aKey);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ const UniquePtr<TokenCacheRecord>& rec = cacheEntry->Get();
+ aToken = rec->mToken.Clone();
+ aResult = rec->mSessionCacheInfo.Clone();
+ if (aTokenId) {
+ *aTokenId = rec->mId;
+ }
+ mCacheSize -= rec->Size();
+ cacheEntry->RemoveWithId(rec->mId);
+ if (cacheEntry->RecordCount() == 0) {
+ mTokenCacheRecords.Remove(aKey);
+ }
+ return NS_OK;
+ }
+
+ LOG((" token not found"));
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+// static
+nsresult SSLTokensCache::Remove(const nsACString& aKey, uint64_t aId) {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("SSLTokensCache::Remove [key=%s]", PromiseFlatCString(aKey).get()));
+
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return gInstance->RemoveLocked(aKey, aId);
+}
+
+nsresult SSLTokensCache::RemoveLocked(const nsACString& aKey, uint64_t aId) {
+ sLock.AssertCurrentThreadOwns();
+
+ LOG(("SSLTokensCache::RemoveLocked [key=%s, id=%" PRIu64 "]",
+ PromiseFlatCString(aKey).get(), aId));
+
+ TokenCacheEntry* cacheEntry;
+ if (!mTokenCacheRecords.Get(aKey, &cacheEntry)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ UniquePtr<TokenCacheRecord> rec = cacheEntry->RemoveWithId(aId);
+ if (!rec) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mCacheSize -= rec->Size();
+ if (cacheEntry->RecordCount() == 0) {
+ mTokenCacheRecords.Remove(aKey);
+ }
+
+ // Release the record immediately, so mExpirationArray can be also updated.
+ rec = nullptr;
+
+ LogStats();
+
+ return NS_OK;
+}
+
+// static
+nsresult SSLTokensCache::RemoveAll(const nsACString& aKey) {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("SSLTokensCache::RemoveAll [key=%s]", PromiseFlatCString(aKey).get()));
+
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return gInstance->RemovAllLocked(aKey);
+}
+
+nsresult SSLTokensCache::RemovAllLocked(const nsACString& aKey) {
+ sLock.AssertCurrentThreadOwns();
+
+ LOG(("SSLTokensCache::RemovAllLocked [key=%s]",
+ PromiseFlatCString(aKey).get()));
+
+ UniquePtr<TokenCacheEntry> cacheEntry;
+ if (!mTokenCacheRecords.Remove(aKey, &cacheEntry)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mCacheSize -= cacheEntry->Size();
+ cacheEntry = nullptr;
+
+ LogStats();
+
+ return NS_OK;
+}
+
+void SSLTokensCache::OnRecordDestroyed(TokenCacheRecord* aRec) {
+ mExpirationArray.RemoveElement(aRec);
+}
+
+void SSLTokensCache::EvictIfNecessary() {
+ // kilobytes to bytes
+ uint32_t capacity = StaticPrefs::network_ssl_tokens_cache_capacity() << 10;
+ if (mCacheSize <= capacity) {
+ return;
+ }
+
+ LOG(("SSLTokensCache::EvictIfNecessary - evicting"));
+
+ mExpirationArray.Sort(ExpirationComparator());
+
+ while (mCacheSize > capacity && mExpirationArray.Length() > 0) {
+ DebugOnly<nsresult> rv =
+ RemoveLocked(mExpirationArray[0]->mKey, mExpirationArray[0]->mId);
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "mExpirationArray and mTokenCacheRecords are out of sync!");
+ }
+}
+
+void SSLTokensCache::LogStats() {
+ if (!LOG5_ENABLED()) {
+ return;
+ }
+ LOG(("SSLTokensCache::LogStats [count=%zu, cacheSize=%u]",
+ mExpirationArray.Length(), mCacheSize));
+ for (const auto& ent : mTokenCacheRecords.Values()) {
+ const UniquePtr<TokenCacheRecord>& rec = ent->Get();
+ LOG(("key=%s count=%d", rec->mKey.get(), ent->RecordCount()));
+ }
+}
+
+size_t SSLTokensCache::SizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = mallocSizeOf(this);
+
+ n += mTokenCacheRecords.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += mExpirationArray.ShallowSizeOfExcludingThis(mallocSizeOf);
+
+ for (uint32_t i = 0; i < mExpirationArray.Length(); ++i) {
+ n += mallocSizeOf(mExpirationArray[i]);
+ n += mExpirationArray[i]->mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
+ n += mExpirationArray[i]->mToken.ShallowSizeOfExcludingThis(mallocSizeOf);
+ }
+
+ return n;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(SSLTokensCacheMallocSizeOf)
+
+NS_IMETHODIMP
+SSLTokensCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ StaticMutexAutoLock lock(sLock);
+
+ MOZ_COLLECT_REPORT("explicit/network/ssl-tokens-cache", KIND_HEAP,
+ UNITS_BYTES,
+ SizeOfIncludingThis(SSLTokensCacheMallocSizeOf),
+ "Memory used for the SSL tokens cache.");
+
+ return NS_OK;
+}
+
+// static
+void SSLTokensCache::Clear() {
+ LOG(("SSLTokensCache::Clear"));
+
+ StaticMutexAutoLock lock(sLock);
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return;
+ }
+
+ gInstance->mExpirationArray.Clear();
+ gInstance->mTokenCacheRecords.Clear();
+ gInstance->mCacheSize = 0;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/SSLTokensCache.h b/netwerk/base/SSLTokensCache.h
new file mode 100644
index 0000000000..b335457526
--- /dev/null
+++ b/netwerk/base/SSLTokensCache.h
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SSLTokensCache_h_
+#define SSLTokensCache_h_
+
+#include "CertVerifier.h" // For EVStatus
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPtr.h"
+#include "nsClassHashtable.h"
+#include "nsIMemoryReporter.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsTArray.h"
+#include "nsTHashMap.h"
+#include "nsXULAppAPI.h"
+
+class CommonSocketControl;
+
+namespace mozilla {
+namespace net {
+
+struct SessionCacheInfo {
+ SessionCacheInfo Clone() const;
+
+ psm::EVStatus mEVStatus = psm::EVStatus::NotEV;
+ uint16_t mCertificateTransparencyStatus =
+ nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
+ nsTArray<uint8_t> mServerCertBytes;
+ Maybe<nsTArray<nsTArray<uint8_t>>> mSucceededCertChainBytes;
+ Maybe<bool> mIsBuiltCertChainRootBuiltInRoot;
+ nsITransportSecurityInfo::OverridableErrorCategory mOverridableErrorCategory;
+ Maybe<nsTArray<nsTArray<uint8_t>>> mFailedCertChainBytes;
+};
+
+class SSLTokensCache : public nsIMemoryReporter {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ friend class ExpirationComparator;
+
+ static nsresult Init();
+ static nsresult Shutdown();
+
+ static nsresult Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen, CommonSocketControl* aSocketControl);
+ static nsresult Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen, CommonSocketControl* aSocketControl,
+ PRUint32 aExpirationTime);
+ static nsresult Get(const nsACString& aKey, nsTArray<uint8_t>& aToken,
+ SessionCacheInfo& aResult, uint64_t* aTokenId = nullptr);
+ static nsresult Remove(const nsACString& aKey, uint64_t aId);
+ static nsresult RemoveAll(const nsACString& aKey);
+ static void Clear();
+
+ private:
+ SSLTokensCache();
+ virtual ~SSLTokensCache();
+
+ nsresult RemoveLocked(const nsACString& aKey, uint64_t aId);
+ nsresult RemovAllLocked(const nsACString& aKey);
+ nsresult GetLocked(const nsACString& aKey, nsTArray<uint8_t>& aToken,
+ SessionCacheInfo& aResult, uint64_t* aTokenId);
+
+ void EvictIfNecessary();
+ void LogStats();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ static mozilla::StaticRefPtr<SSLTokensCache> gInstance;
+ static StaticMutex sLock MOZ_UNANNOTATED;
+ static uint64_t sRecordId;
+
+ uint32_t mCacheSize{0}; // Actual cache size in bytes
+
+ class TokenCacheRecord {
+ public:
+ ~TokenCacheRecord();
+
+ uint32_t Size() const;
+ void Reset();
+
+ nsCString mKey;
+ PRUint32 mExpirationTime = 0;
+ nsTArray<uint8_t> mToken;
+ SessionCacheInfo mSessionCacheInfo;
+ // An unique id to identify the record. Mostly used when we want to remove a
+ // record from TokenCacheEntry.
+ uint64_t mId = 0;
+ };
+
+ class TokenCacheEntry {
+ public:
+ uint32_t Size() const;
+ // Add a record into |mRecords|. To make sure |mRecords| is sorted, we
+ // iterate |mRecords| everytime to find a right place to insert the new
+ // record.
+ void AddRecord(UniquePtr<TokenCacheRecord>&& aRecord,
+ nsTArray<TokenCacheRecord*>& aExpirationArray);
+ // This function returns the first record in |mRecords|.
+ const UniquePtr<TokenCacheRecord>& Get();
+ UniquePtr<TokenCacheRecord> RemoveWithId(uint64_t aId);
+ uint32_t RecordCount() const { return mRecords.Length(); }
+ const nsTArray<UniquePtr<TokenCacheRecord>>& Records() { return mRecords; }
+
+ private:
+ // The records in this array are ordered by the expiration time.
+ nsTArray<UniquePtr<TokenCacheRecord>> mRecords;
+ };
+
+ void OnRecordDestroyed(TokenCacheRecord* aRec);
+
+ nsClassHashtable<nsCStringHashKey, TokenCacheEntry> mTokenCacheRecords;
+ nsTArray<TokenCacheRecord*> mExpirationArray;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // SSLTokensCache_h_
diff --git a/netwerk/base/ShutdownLayer.cpp b/netwerk/base/ShutdownLayer.cpp
new file mode 100644
index 0000000000..db62ea691d
--- /dev/null
+++ b/netwerk/base/ShutdownLayer.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "ShutdownLayer.h"
+#include "prerror.h"
+#include "private/pprio.h"
+#include "prmem.h"
+#include <winsock2.h>
+
+static PRDescIdentity sWinSockShutdownLayerIdentity;
+static PRIOMethods sWinSockShutdownLayerMethods;
+static PRIOMethods* sWinSockShutdownLayerMethodsPtr = nullptr;
+
+namespace mozilla {
+namespace net {
+
+extern PRDescIdentity nsNamedPipeLayerIdentity;
+
+} // namespace net
+} // namespace mozilla
+
+PRStatus WinSockClose(PRFileDesc* aFd) {
+ MOZ_RELEASE_ASSERT(aFd->identity == sWinSockShutdownLayerIdentity,
+ "Windows shutdown layer not on the top of the stack");
+
+ PROsfd osfd = PR_FileDesc2NativeHandle(aFd);
+ if (osfd != -1) {
+ shutdown(osfd, SD_BOTH);
+ }
+
+ aFd->identity = PR_INVALID_IO_LAYER;
+
+ if (aFd->lower) {
+ return aFd->lower->methods->close(aFd->lower);
+ } else {
+ return PR_SUCCESS;
+ }
+}
+
+nsresult mozilla::net::AttachShutdownLayer(PRFileDesc* aFd) {
+ if (!sWinSockShutdownLayerMethodsPtr) {
+ sWinSockShutdownLayerIdentity =
+ PR_GetUniqueIdentity("windows shutdown call layer");
+ sWinSockShutdownLayerMethods = *PR_GetDefaultIOMethods();
+ sWinSockShutdownLayerMethods.close = WinSockClose;
+ sWinSockShutdownLayerMethodsPtr = &sWinSockShutdownLayerMethods;
+ }
+
+ if (mozilla::net::nsNamedPipeLayerIdentity &&
+ PR_GetIdentitiesLayer(aFd, mozilla::net::nsNamedPipeLayerIdentity)) {
+ // Do not attach shutdown layer on named pipe layer,
+ // it is for PR_NSPR_IO_LAYER only.
+ return NS_OK;
+ }
+
+ PRFileDesc* layer;
+ PRStatus status;
+
+ layer = PR_CreateIOLayerStub(sWinSockShutdownLayerIdentity,
+ sWinSockShutdownLayerMethodsPtr);
+ if (!layer) {
+ return NS_OK;
+ }
+
+ status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
diff --git a/netwerk/base/ShutdownLayer.h b/netwerk/base/ShutdownLayer.h
new file mode 100644
index 0000000000..2bf68fdcdc
--- /dev/null
+++ b/netwerk/base/ShutdownLayer.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SHUTDOWNLAYER_H___
+#define SHUTDOWNLAYER_H___
+
+#include "nscore.h"
+#include "prio.h"
+
+namespace mozilla {
+namespace net {
+
+// This is only for windows. This layer will be attached jus before PR_CLose
+// is call and it will only call shutdown(sock).
+extern nsresult AttachShutdownLayer(PRFileDesc* fd);
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* SHUTDOWN_H___ */
diff --git a/netwerk/base/SimpleBuffer.cpp b/netwerk/base/SimpleBuffer.cpp
new file mode 100644
index 0000000000..46b8654efa
--- /dev/null
+++ b/netwerk/base/SimpleBuffer.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SimpleBuffer.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+nsresult SimpleBuffer::Write(char* src, size_t len) {
+ NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ while (len > 0) {
+ SimpleBufferPage* p = mBufferList.getLast();
+ if (p && (p->mWriteOffset == SimpleBufferPage::kSimpleBufferPageSize)) {
+ // no room.. make a new page
+ p = nullptr;
+ }
+ if (!p) {
+ p = new (fallible) SimpleBufferPage();
+ if (!p) {
+ mStatus = NS_ERROR_OUT_OF_MEMORY;
+ return mStatus;
+ }
+ mBufferList.insertBack(p);
+ }
+ size_t roomOnPage =
+ SimpleBufferPage::kSimpleBufferPageSize - p->mWriteOffset;
+ size_t toWrite = std::min(roomOnPage, len);
+ memcpy(p->mBuffer + p->mWriteOffset, src, toWrite);
+ src += toWrite;
+ len -= toWrite;
+ p->mWriteOffset += toWrite;
+ mAvailable += toWrite;
+ }
+ return NS_OK;
+}
+
+size_t SimpleBuffer::Read(char* dest, size_t maxLen) {
+ NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
+ if (NS_FAILED(mStatus)) {
+ return 0;
+ }
+
+ size_t rv = 0;
+ for (SimpleBufferPage* p = mBufferList.getFirst(); p && (rv < maxLen);
+ p = mBufferList.getFirst()) {
+ size_t avail = p->mWriteOffset - p->mReadOffset;
+ size_t toRead = std::min(avail, (maxLen - rv));
+ memcpy(dest + rv, p->mBuffer + p->mReadOffset, toRead);
+ rv += toRead;
+ p->mReadOffset += toRead;
+ if (p->mReadOffset == p->mWriteOffset) {
+ p->remove();
+ delete p;
+ }
+ }
+
+ MOZ_ASSERT(mAvailable >= rv);
+ mAvailable -= rv;
+ return rv;
+}
+
+size_t SimpleBuffer::Available() {
+ NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
+ return NS_SUCCEEDED(mStatus) ? mAvailable : 0;
+}
+
+void SimpleBuffer::Clear() {
+ NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
+ SimpleBufferPage* p;
+ while ((p = mBufferList.popFirst())) {
+ delete p;
+ }
+ mAvailable = 0;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/SimpleBuffer.h b/netwerk/base/SimpleBuffer.h
new file mode 100644
index 0000000000..031f59afe5
--- /dev/null
+++ b/netwerk/base/SimpleBuffer.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SimpleBuffer_h__
+#define SimpleBuffer_h__
+
+/*
+ This class is similar to a nsPipe except it does not have any locking, stores
+ an unbounded amount of data, can only be used on one thread, and has much
+ simpler result code semantics to deal with.
+*/
+
+#include "prtypes.h"
+#include "ErrorList.h"
+#include "mozilla/LinkedList.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace net {
+
+class SimpleBufferPage : public LinkedListElement<SimpleBufferPage> {
+ public:
+ SimpleBufferPage() = default;
+ static const size_t kSimpleBufferPageSize = 32000;
+
+ private:
+ friend class SimpleBuffer;
+ char mBuffer[kSimpleBufferPageSize]{0};
+ size_t mReadOffset{0};
+ size_t mWriteOffset{0};
+};
+
+class SimpleBuffer {
+ public:
+ SimpleBuffer() = default;
+ ~SimpleBuffer() = default;
+
+ nsresult Write(char* src, size_t len); // return OK or OUT_OF_MEMORY
+ size_t Read(char* dest, size_t maxLen); // return bytes read
+ size_t Available();
+ void Clear();
+
+ private:
+ NS_DECL_OWNINGTHREAD
+
+ nsresult mStatus{NS_OK};
+ AutoCleanLinkedList<SimpleBufferPage> mBufferList;
+ size_t mAvailable{0};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/SimpleChannel.cpp b/netwerk/base/SimpleChannel.cpp
new file mode 100644
index 0000000000..6297331135
--- /dev/null
+++ b/netwerk/base/SimpleChannel.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SimpleChannel.h"
+
+#include "nsBaseChannel.h"
+#include "nsIChannel.h"
+#include "nsIChildChannel.h"
+#include "nsICancelable.h"
+#include "nsIInputStream.h"
+#include "nsIRequest.h"
+#include "nsISupportsImpl.h"
+#include "nsNetUtil.h"
+
+#include "mozilla/Try.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/PSimpleChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+SimpleChannel::SimpleChannel(UniquePtr<SimpleChannelCallbacks>&& aCallbacks)
+ : mCallbacks(std::move(aCallbacks)) {
+ EnableSynthesizedProgressEvents(true);
+}
+
+nsresult SimpleChannel::OpenContentStream(bool async,
+ nsIInputStream** streamOut,
+ nsIChannel** channel) {
+ NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIInputStream> stream;
+ MOZ_TRY_VAR(stream, mCallbacks->OpenContentStream(async, this));
+ MOZ_ASSERT(stream);
+
+ mCallbacks = nullptr;
+
+ *channel = nullptr;
+ stream.forget(streamOut);
+ return NS_OK;
+}
+
+nsresult SimpleChannel::BeginAsyncRead(nsIStreamListener* listener,
+ nsIRequest** request,
+ nsICancelable** cancelableRequest) {
+ NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED);
+
+ RequestOrReason res = mCallbacks->StartAsyncRead(listener, this);
+
+ if (res.isErr()) {
+ return res.propagateErr();
+ }
+
+ mCallbacks = nullptr;
+
+ RequestOrCancelable value = res.unwrap();
+
+ if (value.is<NotNullRequest>()) {
+ nsCOMPtr<nsIRequest> req = value.as<NotNullRequest>().get();
+ req.forget(request);
+ } else if (value.is<NotNullCancelable>()) {
+ nsCOMPtr<nsICancelable> cancelable = value.as<NotNullCancelable>().get();
+ cancelable.forget(cancelableRequest);
+ } else {
+ MOZ_ASSERT_UNREACHABLE(
+ "StartAsyncRead didn't return a NotNull nsIRequest or nsICancelable.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(SimpleChannelChild, SimpleChannel, nsIChildChannel)
+
+SimpleChannelChild::SimpleChannelChild(
+ UniquePtr<SimpleChannelCallbacks>&& aCallbacks)
+ : SimpleChannel(std::move(aCallbacks)) {}
+
+NS_IMETHODIMP
+SimpleChannelChild::ConnectParent(uint32_t aId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::dom::ContentChild* cc =
+ static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Reference freed in DeallocPSimpleChannelChild.
+ if (!gNeckoChild->SendPSimpleChannelConstructor(do_AddRef(this).take(),
+ aId)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ if (CanSend()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ nsresult rv;
+ rv = AsyncOpen(aListener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (CanSend()) {
+ Unused << Send__delete__(this);
+ }
+ return NS_OK;
+}
+
+already_AddRefed<nsIChannel> NS_NewSimpleChannelInternal(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ UniquePtr<SimpleChannelCallbacks>&& aCallbacks) {
+ RefPtr<SimpleChannel> chan;
+ if (IsNeckoChild()) {
+ chan = new SimpleChannelChild(std::move(aCallbacks));
+ } else {
+ chan = new SimpleChannel(std::move(aCallbacks));
+ }
+
+ chan->SetURI(aURI);
+
+ MOZ_ALWAYS_SUCCEEDS(chan->SetLoadInfo(aLoadInfo));
+
+ return chan.forget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/SimpleChannel.h b/netwerk/base/SimpleChannel.h
new file mode 100644
index 0000000000..d469adf65d
--- /dev/null
+++ b/netwerk/base/SimpleChannel.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SimpleChannel_h
+#define SimpleChannel_h
+
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/UniquePtr.h"
+#include "nsBaseChannel.h"
+#include "nsIChildChannel.h"
+#include "mozilla/net/PSimpleChannelChild.h"
+#include "nsCOMPtr.h"
+
+class nsIChannel;
+class nsIInputStream;
+class nsILoadInfo;
+class nsIRequest;
+class nsIStreamListener;
+class nsIURI;
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+
+using InputStreamOrReason = Result<nsCOMPtr<nsIInputStream>, nsresult>;
+using NotNullRequest = NotNull<nsCOMPtr<nsIRequest>>;
+using NotNullCancelable = NotNull<nsCOMPtr<nsICancelable>>;
+using RequestOrCancelable = Variant<NotNullRequest, NotNullCancelable>;
+using RequestOrReason = Result<RequestOrCancelable, nsresult>;
+
+namespace net {
+
+class SimpleChannelCallbacks {
+ public:
+ virtual InputStreamOrReason OpenContentStream(bool async,
+ nsIChannel* channel) = 0;
+
+ virtual RequestOrReason StartAsyncRead(nsIStreamListener* stream,
+ nsIChannel* channel) = 0;
+
+ virtual ~SimpleChannelCallbacks() = default;
+};
+
+template <typename F1, typename F2, typename T>
+class SimpleChannelCallbacksImpl final : public SimpleChannelCallbacks {
+ public:
+ SimpleChannelCallbacksImpl(F1&& aStartAsyncRead, F2&& aOpenContentStream,
+ T* context)
+ : mStartAsyncRead(aStartAsyncRead),
+ mOpenContentStream(aOpenContentStream),
+ mContext(context) {}
+
+ virtual ~SimpleChannelCallbacksImpl() = default;
+
+ virtual InputStreamOrReason OpenContentStream(bool async,
+ nsIChannel* channel) override {
+ return mOpenContentStream(async, channel, mContext);
+ }
+
+ virtual RequestOrReason StartAsyncRead(nsIStreamListener* listener,
+ nsIChannel* channel) override {
+ return mStartAsyncRead(listener, channel, mContext);
+ }
+
+ private:
+ F1 mStartAsyncRead;
+ F2 mOpenContentStream;
+ RefPtr<T> mContext;
+};
+
+class SimpleChannel : public nsBaseChannel {
+ public:
+ explicit SimpleChannel(UniquePtr<SimpleChannelCallbacks>&& aCallbacks);
+
+ protected:
+ virtual ~SimpleChannel() = default;
+
+ virtual nsresult OpenContentStream(bool async, nsIInputStream** streamOut,
+ nsIChannel** channel) override;
+
+ virtual nsresult BeginAsyncRead(nsIStreamListener* listener,
+ nsIRequest** request,
+ nsICancelable** cancelableRequest) override;
+
+ private:
+ UniquePtr<SimpleChannelCallbacks> mCallbacks;
+};
+
+class SimpleChannelChild final : public SimpleChannel,
+ public nsIChildChannel,
+ public PSimpleChannelChild {
+ public:
+ explicit SimpleChannelChild(UniquePtr<SimpleChannelCallbacks>&& aCallbacks);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICHILDCHANNEL
+
+ private:
+ virtual ~SimpleChannelChild() = default;
+};
+
+already_AddRefed<nsIChannel> NS_NewSimpleChannelInternal(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ UniquePtr<SimpleChannelCallbacks>&& aCallbacks);
+
+} // namespace net
+} // namespace mozilla
+
+/**
+ * Creates a simple channel which wraps an input stream created by the given
+ * callbacks. The callbacks are not called until the underlying AsyncOpen or
+ * Open methods are called, and correspond to the nsBaseChannel::StartAsyncRead
+ * and nsBaseChannel::OpenContentStream methods of the same names.
+ *
+ * The last two arguments of each callback are the created channel instance,
+ * and the ref-counted context object passed to NS_NewSimpleChannel. A strong
+ * reference to that object is guaranteed to be kept alive until after a
+ * callback successfully completes.
+ */
+template <typename T, typename F1, typename F2>
+inline already_AddRefed<nsIChannel> NS_NewSimpleChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, T* context, F1&& aStartAsyncRead,
+ F2&& aOpenContentStream) {
+ using namespace mozilla;
+
+ auto callbacks = MakeUnique<net::SimpleChannelCallbacksImpl<F1, F2, T>>(
+ std::forward<F1>(aStartAsyncRead), std::forward<F2>(aOpenContentStream),
+ context);
+
+ return net::NS_NewSimpleChannelInternal(aURI, aLoadInfo,
+ std::move(callbacks));
+}
+
+template <typename T, typename F1>
+inline already_AddRefed<nsIChannel> NS_NewSimpleChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ T* context,
+ F1&& aStartAsyncRead) {
+ using namespace mozilla;
+
+ auto openContentStream = [](bool async, nsIChannel* channel, T* context) {
+ return Err(NS_ERROR_NOT_IMPLEMENTED);
+ };
+
+ return NS_NewSimpleChannel(aURI, aLoadInfo, context,
+ std::forward<F1>(aStartAsyncRead),
+ std::move(openContentStream));
+}
+
+#endif // SimpleChannel_h
diff --git a/netwerk/base/SimpleChannelParent.cpp b/netwerk/base/SimpleChannelParent.cpp
new file mode 100644
index 0000000000..3a58e14843
--- /dev/null
+++ b/netwerk/base/SimpleChannelParent.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SimpleChannelParent.h"
+#include "mozilla/Assertions.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+
+#ifdef FUZZING_SNAPSHOT
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) (void)__VA_ARGS__
+#else
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) MOZ_ALWAYS_SUCCEEDS(__VA_ARGS__)
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(SimpleChannelParent, nsIParentChannel, nsIStreamListener)
+
+bool SimpleChannelParent::Init(const uint64_t& aChannelId) {
+ nsCOMPtr<nsIChannel> channel;
+
+ MOZ_ALWAYS_SUCCEEDS_FUZZING(
+ NS_LinkRedirectChannels(aChannelId, this, getter_AddRefs(channel)));
+
+ return true;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aPrefix) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::Delete() {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void SimpleChannelParent::ActorDestroy(ActorDestroyReason aWhy) {}
+
+NS_IMETHODIMP
+SimpleChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ // We don't have a way to prevent nsBaseChannel from calling AsyncOpen on
+ // the created nsSimpleChannel. We don't have anywhere to send the data in the
+ // parent, so abort the binding.
+ return NS_BINDING_ABORTED;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // See above.
+ MOZ_ASSERT(NS_FAILED(aStatusCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SimpleChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // See above.
+ MOZ_CRASH("Should never be called");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/SimpleChannelParent.h b/netwerk/base/SimpleChannelParent.h
new file mode 100644
index 0000000000..6a8f2c867b
--- /dev/null
+++ b/netwerk/base/SimpleChannelParent.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_SIMPLECHANNELPARENT_H
+#define NS_SIMPLECHANNELPARENT_H
+
+#include "nsIParentChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PSimpleChannelParent.h"
+
+namespace mozilla {
+namespace net {
+
+// In order to support HTTP redirects, we need to implement the HTTP
+// redirection API, which requires a class that implements nsIParentChannel
+// and which calls NS_LinkRedirectChannels.
+class SimpleChannelParent : public nsIParentChannel,
+ public PSimpleChannelParent {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER; // semicolon for clang-format bug 1629756
+
+ [[nodiscard]] bool Init(const uint64_t& aChannelId);
+
+ private:
+ ~SimpleChannelParent() = default;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* NS_SIMPLECHANNELPARENT_H */
diff --git a/netwerk/base/TLSServerSocket.cpp b/netwerk/base/TLSServerSocket.cpp
new file mode 100644
index 0000000000..131ea50573
--- /dev/null
+++ b/netwerk/base/TLSServerSocket.cpp
@@ -0,0 +1,446 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TLSServerSocket.h"
+
+#include "mozilla/net/DNS.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDependentSubstring.h"
+#include "nsIServerSocket.h"
+#include "nsIX509Cert.h"
+#include "nsIX509CertDB.h"
+#include "nsNetCID.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransport2.h"
+#include "nsThreadUtils.h"
+#include "ScopedNSSTypes.h"
+#include "ssl.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS_INHERITED(TLSServerSocket, nsServerSocket, nsITLSServerSocket)
+
+nsresult TLSServerSocket::SetSocketDefaults() {
+ // Set TLS options on the listening socket
+ mFD = SSL_ImportFD(nullptr, mFD);
+ if (NS_WARN_IF(!mFD)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ SSL_OptionSet(mFD, SSL_SECURITY, true);
+ SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_CLIENT, false);
+ SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_SERVER, true);
+ SSL_OptionSet(mFD, SSL_NO_CACHE, true);
+
+ // We don't currently notify the server API consumer of renegotiation events
+ // (to revalidate peer certs, etc.), so disable it for now.
+ SSL_OptionSet(mFD, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER);
+
+ SetSessionTickets(true);
+ SetRequestClientCertificate(REQUEST_NEVER);
+
+ return NS_OK;
+}
+
+void TLSServerSocket::CreateClientTransport(PRFileDesc* aClientFD,
+ const NetAddr& aClientAddr) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsresult rv;
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport;
+ if (NS_WARN_IF(!trans)) {
+ mCondition = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ RefPtr<TLSServerConnectionInfo> info = new TLSServerConnectionInfo();
+ info->mServerSocket = this;
+ info->mTransport = trans;
+ nsCOMPtr<nsIInterfaceRequestor> infoInterfaceRequestor(info);
+ rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr,
+ infoInterfaceRequestor);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mCondition = rv;
+ return;
+ }
+
+ // Override the default peer certificate validation, so that server consumers
+ // can make their own choice after the handshake completes.
+ SSL_AuthCertificateHook(aClientFD, AuthCertificateHook, nullptr);
+ // Once the TLS handshake has completed, the server consumer is notified and
+ // has access to various TLS state details.
+ // It's safe to pass info here because the socket transport holds it as
+ // |mSecInfo| which keeps it alive for the lifetime of the socket.
+ SSL_HandshakeCallback(aClientFD, TLSServerConnectionInfo::HandshakeCallback,
+ info);
+
+ // Notify the consumer of the new client so it can manage the streams.
+ // Security details aren't known yet. The security observer will be notified
+ // later when they are ready.
+ nsCOMPtr<nsIServerSocket> serverSocket =
+ do_QueryInterface(NS_ISUPPORTS_CAST(nsITLSServerSocket*, this));
+ mListener->OnSocketAccepted(serverSocket, trans);
+}
+
+nsresult TLSServerSocket::OnSocketListen() {
+ if (NS_WARN_IF(!mServerCert)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ UniqueCERTCertificate cert(mServerCert->GetCert());
+ if (NS_WARN_IF(!cert)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ UniqueSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert.get(), nullptr));
+ if (NS_WARN_IF(!key)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ SSLKEAType certKEA = NSS_FindCertKEAType(cert.get());
+
+ nsresult rv =
+ MapSECStatus(SSL_ConfigSecureServer(mFD, cert.get(), key.get(), certKEA));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+SECStatus TLSServerSocket::AuthCertificateHook(void* arg, PRFileDesc* fd,
+ PRBool checksig,
+ PRBool isServer) {
+ // Allow any client cert here, server consumer code can decide whether it's
+ // okay after being notified of the new client socket.
+ return SECSuccess;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket::nsITLSServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+TLSServerSocket::GetServerCert(nsIX509Cert** aCert) {
+ if (NS_WARN_IF(!aCert)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aCert = do_AddRef(mServerCert).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetServerCert(nsIX509Cert* aCert) {
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ mServerCert = aCert;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetSessionTickets(bool aEnabled) {
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ SSL_OptionSet(mFD, SSL_ENABLE_SESSION_TICKETS, aEnabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetRequestClientCertificate(uint32_t aMode) {
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ SSL_OptionSet(mFD, SSL_REQUEST_CERTIFICATE, aMode != REQUEST_NEVER);
+
+ switch (aMode) {
+ case REQUEST_ALWAYS:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NO_ERROR);
+ break;
+ case REQUIRE_FIRST_HANDSHAKE:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_FIRST_HANDSHAKE);
+ break;
+ case REQUIRE_ALWAYS:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_ALWAYS);
+ break;
+ default:
+ SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetVersionRange(uint16_t aMinVersion, uint16_t aMaxVersion) {
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ SSLVersionRange range = {aMinVersion, aMaxVersion};
+ if (SSL_VersionRangeSet(mFD, &range) != SECSuccess) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerConnectionInfo
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class TLSServerSecurityObserverProxy final
+ : public nsITLSServerSecurityObserver {
+ ~TLSServerSecurityObserverProxy() = default;
+
+ public:
+ explicit TLSServerSecurityObserverProxy(
+ nsITLSServerSecurityObserver* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsITLSServerSecurityObserver>(
+ "TLSServerSecurityObserverProxy::mListener", aListener)) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSERVERSECURITYOBSERVER
+
+ class OnHandshakeDoneRunnable : public Runnable {
+ public:
+ OnHandshakeDoneRunnable(
+ const nsMainThreadPtrHandle<nsITLSServerSecurityObserver>& aListener,
+ nsITLSServerSocket* aServer, nsITLSClientStatus* aStatus)
+ : Runnable(
+ "net::TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable"),
+ mListener(aListener),
+ mServer(aServer),
+ mStatus(aStatus) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener;
+ nsCOMPtr<nsITLSServerSocket> mServer;
+ nsCOMPtr<nsITLSClientStatus> mStatus;
+ };
+
+ private:
+ nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener;
+};
+
+NS_IMPL_ISUPPORTS(TLSServerSecurityObserverProxy, nsITLSServerSecurityObserver)
+
+NS_IMETHODIMP
+TLSServerSecurityObserverProxy::OnHandshakeDone(nsITLSServerSocket* aServer,
+ nsITLSClientStatus* aStatus) {
+ RefPtr<OnHandshakeDoneRunnable> r =
+ new OnHandshakeDoneRunnable(mListener, aServer, aStatus);
+ return NS_DispatchToMainThread(r);
+}
+
+NS_IMETHODIMP
+TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable::Run() {
+ mListener->OnHandshakeDone(mServer, mStatus);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(TLSServerConnectionInfo, nsITLSServerConnectionInfo,
+ nsITLSClientStatus, nsIInterfaceRequestor)
+
+TLSServerConnectionInfo::~TLSServerConnectionInfo() {
+ RefPtr<nsITLSServerSecurityObserver> observer;
+ {
+ MutexAutoLock lock(mLock);
+ observer = ToRefPtr(std::move(mSecurityObserver));
+ }
+
+ if (observer) {
+ NS_ReleaseOnMainThread("TLSServerConnectionInfo::mSecurityObserver",
+ observer.forget());
+ }
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::SetSecurityObserver(
+ nsITLSServerSecurityObserver* aObserver) {
+ {
+ MutexAutoLock lock(mLock);
+ if (!aObserver) {
+ mSecurityObserver = nullptr;
+ return NS_OK;
+ }
+
+ mSecurityObserver = new TLSServerSecurityObserverProxy(aObserver);
+ // Call `OnHandshakeDone` if TLS handshake is already completed.
+ if (mTlsVersionUsed != TLS_VERSION_UNKNOWN) {
+ nsCOMPtr<nsITLSServerSocket> serverSocket;
+ GetServerSocket(getter_AddRefs(serverSocket));
+ mSecurityObserver->OnHandshakeDone(serverSocket, this);
+ mSecurityObserver = nullptr;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetServerSocket(nsITLSServerSocket** aSocket) {
+ if (NS_WARN_IF(!aSocket)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aSocket = do_AddRef(mServerSocket).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetStatus(nsITLSClientStatus** aStatus) {
+ if (NS_WARN_IF(!aStatus)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aStatus = do_AddRef(this).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetPeerCert(nsIX509Cert** aCert) {
+ if (NS_WARN_IF(!aCert)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aCert = do_AddRef(mPeerCert).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetTlsVersionUsed(int16_t* aTlsVersionUsed) {
+ if (NS_WARN_IF(!aTlsVersionUsed)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aTlsVersionUsed = mTlsVersionUsed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetCipherName(nsACString& aCipherName) {
+ aCipherName.Assign(mCipherName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetKeyLength(uint32_t* aKeyLength) {
+ if (NS_WARN_IF(!aKeyLength)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aKeyLength = mKeyLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetMacLength(uint32_t* aMacLength) {
+ if (NS_WARN_IF(!aMacLength)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aMacLength = mMacLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetInterface(const nsIID& aIID, void** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsITLSServerConnectionInfo))) {
+ *aResult = static_cast<nsITLSServerConnectionInfo*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+
+ return NS_NOINTERFACE;
+}
+
+// static
+void TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD, void* aArg) {
+ RefPtr<TLSServerConnectionInfo> info =
+ static_cast<TLSServerConnectionInfo*>(aArg);
+ nsISocketTransport* transport = info->mTransport;
+ // No longer needed outside this function, so clear the weak ref
+ info->mTransport = nullptr;
+ nsresult rv = info->HandshakeCallback(aFD);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ transport->Close(rv);
+ }
+}
+
+nsresult TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD) {
+ nsresult rv;
+
+ UniqueCERTCertificate clientCert(SSL_PeerCertificate(aFD));
+ if (clientCert) {
+ nsCOMPtr<nsIX509CertDB> certDB =
+ do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIX509Cert> clientCertPSM;
+ nsTArray<uint8_t> clientCertBytes;
+ clientCertBytes.AppendElements(clientCert->derCert.data,
+ clientCert->derCert.len);
+ rv = certDB->ConstructX509(clientCertBytes, getter_AddRefs(clientCertPSM));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mPeerCert = clientCertPSM;
+ }
+
+ SSLChannelInfo channelInfo;
+ rv = MapSECStatus(SSL_GetChannelInfo(aFD, &channelInfo, sizeof(channelInfo)));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ SSLCipherSuiteInfo cipherInfo;
+ rv = MapSECStatus(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
+ sizeof(cipherInfo)));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mCipherName.Assign(cipherInfo.cipherSuiteName);
+ mKeyLength = cipherInfo.effectiveKeyBits;
+ mMacLength = cipherInfo.macBits;
+
+ // Notify consumer code that handshake is complete
+ nsCOMPtr<nsITLSServerSecurityObserver> observer;
+ {
+ MutexAutoLock lock(mLock);
+ mTlsVersionUsed = channelInfo.protocolVersion;
+ if (!mSecurityObserver) {
+ return NS_OK;
+ }
+ mSecurityObserver.swap(observer);
+ }
+ nsCOMPtr<nsITLSServerSocket> serverSocket;
+ GetServerSocket(getter_AddRefs(serverSocket));
+ observer->OnHandshakeDone(serverSocket, this);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/TLSServerSocket.h b/netwerk/base/TLSServerSocket.h
new file mode 100644
index 0000000000..c8f4380d46
--- /dev/null
+++ b/netwerk/base/TLSServerSocket.h
@@ -0,0 +1,81 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_TLSServerSocket_h
+#define mozilla_net_TLSServerSocket_h
+
+#include "nsIInterfaceRequestor.h"
+#include "nsITLSServerSocket.h"
+#include "nsServerSocket.h"
+#include "nsString.h"
+#include "mozilla/Mutex.h"
+#include "seccomon.h"
+
+namespace mozilla {
+namespace net {
+
+class TLSServerSocket final : public nsServerSocket, public nsITLSServerSocket {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSISERVERSOCKET(nsServerSocket::)
+ NS_DECL_NSITLSSERVERSOCKET
+
+ // Override methods from nsServerSocket
+ virtual void CreateClientTransport(PRFileDesc* clientFD,
+ const NetAddr& clientAddr) override;
+ virtual nsresult SetSocketDefaults() override;
+ virtual nsresult OnSocketListen() override;
+
+ TLSServerSocket() = default;
+
+ private:
+ virtual ~TLSServerSocket() = default;
+
+ static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd,
+ PRBool checksig, PRBool isServer);
+
+ nsCOMPtr<nsIX509Cert> mServerCert;
+};
+
+class TLSServerConnectionInfo : public nsITLSServerConnectionInfo,
+ public nsITLSClientStatus,
+ public nsIInterfaceRequestor {
+ friend class TLSServerSocket;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSERVERCONNECTIONINFO
+ NS_DECL_NSITLSCLIENTSTATUS
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ TLSServerConnectionInfo() = default;
+
+ private:
+ virtual ~TLSServerConnectionInfo();
+
+ static void HandshakeCallback(PRFileDesc* aFD, void* aArg);
+ nsresult HandshakeCallback(PRFileDesc* aFD);
+
+ RefPtr<TLSServerSocket> mServerSocket;
+ // Weak ref to the transport, to avoid cycles since the transport holds a
+ // reference to the TLSServerConnectionInfo object. This is not handed out to
+ // anyone, and is only used in HandshakeCallback to close the transport in
+ // case of an error. After this, it's set to nullptr.
+ nsISocketTransport* mTransport{nullptr};
+ nsCOMPtr<nsIX509Cert> mPeerCert;
+ int16_t mTlsVersionUsed{TLS_VERSION_UNKNOWN};
+ nsCString mCipherName;
+ uint32_t mKeyLength{0};
+ uint32_t mMacLength{0};
+ // lock protects access to mSecurityObserver
+ mozilla::Mutex mLock{"TLSServerConnectionInfo.mLock"};
+ nsCOMPtr<nsITLSServerSecurityObserver> mSecurityObserver
+ MOZ_GUARDED_BY(mLock);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TLSServerSocket_h
diff --git a/netwerk/base/TRRLoadInfo.cpp b/netwerk/base/TRRLoadInfo.cpp
new file mode 100644
index 0000000000..920e7623a7
--- /dev/null
+++ b/netwerk/base/TRRLoadInfo.cpp
@@ -0,0 +1,865 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TRRLoadInfo.h"
+#include "mozilla/dom/ClientSource.h"
+#include "nsContentUtils.h"
+#include "nsIRedirectHistoryEntry.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(TRRLoadInfo, nsILoadInfo)
+
+TRRLoadInfo::TRRLoadInfo(nsIURI* aResultPrincipalURI,
+ nsContentPolicyType aContentPolicyType)
+ : mResultPrincipalURI(aResultPrincipalURI),
+ mInternalContentPolicyType(aContentPolicyType) {}
+
+already_AddRefed<nsILoadInfo> TRRLoadInfo::Clone() const {
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new TRRLoadInfo(mResultPrincipalURI, mInternalContentPolicyType);
+
+ return loadInfo.forget();
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsIPrincipal* TRRLoadInfo::VirtualGetLoadingPrincipal() { return nullptr; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsIPrincipal* TRRLoadInfo::TriggeringPrincipal() { return nullptr; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsIPrincipal* TRRLoadInfo::PrincipalToInherit() { return nullptr; }
+
+nsIPrincipal* TRRLoadInfo::FindPrincipalToInherit(nsIChannel* aChannel) {
+ return nullptr;
+}
+
+const nsID& TRRLoadInfo::GetSandboxedNullPrincipalID() {
+ return mSandboxedNullPrincipalID;
+}
+
+void TRRLoadInfo::ResetSandboxedNullPrincipalID() {}
+
+nsIPrincipal* TRRLoadInfo::GetTopLevelPrincipal() { return nullptr; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTriggeringRemoteType(nsACString& aTriggeringRemoteType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetTriggeringRemoteType(const nsACString& aTriggeringRemoteType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadingDocument(Document** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsINode* TRRLoadInfo::LoadingNode() { return nullptr; }
+
+already_AddRefed<nsISupports> TRRLoadInfo::ContextForTopLevelLoad() {
+ return nullptr;
+}
+
+already_AddRefed<nsISupports> TRRLoadInfo::GetLoadingContext() {
+ return nullptr;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadingContextXPCOM(nsISupports** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSecurityFlags(nsSecurityFlags* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSandboxFlags(uint32_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+TRRLoadInfo::GetTriggeringSandboxFlags(uint32_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+TRRLoadInfo::SetTriggeringSandboxFlags(uint32_t aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTriggeringWindowId(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+TRRLoadInfo::SetTriggeringWindowId(uint64_t aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTriggeringStorageAccess(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+TRRLoadInfo::SetTriggeringStorageAccess(bool aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSecurityMode(uint32_t* aFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsInThirdPartyContext(bool aIsInThirdPartyContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsThirdPartyContextToTopWindow(
+ bool* aIsThirdPartyContextToTopWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsThirdPartyContextToTopWindow(
+ bool aIsThirdPartyContextToTopWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetCookiePolicy(uint32_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetCookieJarSettings(nsICookieJarSettings** aCookieJarSettings) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetCookieJarSettings(nsICookieJarSettings* aCookieJarSettings) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetStoragePermission(
+ nsILoadInfo::StoragePermissionState* aHasStoragePermission) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetStoragePermission(
+ nsILoadInfo::StoragePermissionState aHasStoragePermission) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+const Maybe<RFPTarget>& TRRLoadInfo::GetOverriddenFingerprintingSettings() {
+ return mOverriddenFingerprintingSettings;
+}
+
+void TRRLoadInfo::SetOverriddenFingerprintingSettings(RFPTarget aTargets) {}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsMetaRefresh(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsMetaRefresh(bool aResult) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetForceInheritPrincipal(bool* aInheritPrincipal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetForceInheritPrincipalOverruleOwner(bool* aInheritPrincipal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadingSandboxed(bool* aLoadingSandboxed) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetAboutBlankInherits(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetAllowChrome(bool* aResult) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetDisallowScript(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetDontFollowRedirects(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadErrorPage(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsFormSubmission(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsFormSubmission(bool aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSendCSPViolationEvents(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetSendCSPViolationEvents(bool aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult) {
+ // We have to use nsContentPolicyType because ExtContentPolicyType is not
+ // visible from xpidl.
+ *aResult = static_cast<nsContentPolicyType>(
+ nsContentUtils::InternalContentPolicyTypeToExternal(
+ mInternalContentPolicyType));
+ return NS_OK;
+}
+
+nsContentPolicyType TRRLoadInfo::InternalContentPolicyType() {
+ return mInternalContentPolicyType;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBlockAllMixedContent(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetUpgradeInsecureRequests(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBrowserUpgradeInsecureRequests(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBrowserDidUpgradeInsecureRequests(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetBrowserDidUpgradeInsecureRequests(
+ bool aBrowserDidUpgradeInsecureRequests) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBrowserWouldUpgradeInsecureRequests(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetForceAllowDataURI(bool* aForceAllowDataURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetAllowInsecureRedirectToDataURI(
+ bool aAllowInsecureRedirectToDataURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetAllowInsecureRedirectToDataURI(
+ bool* aAllowInsecureRedirectToDataURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetSkipContentPolicyCheckForWebRequest(bool aSkip) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSkipContentPolicyCheckForWebRequest(bool* aSkip) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetOriginalFrameSrcLoad(bool aOriginalFrameSrcLoad) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetOriginalFrameSrcLoad(bool* aOriginalFrameSrcLoad) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetForceInheritPrincipalDropped(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetInnerWindowID(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBrowsingContextID(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetWorkerAssociatedBrowsingContextID(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetWorkerAssociatedBrowsingContextID(uint64_t aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetFrameBrowsingContextID(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTargetBrowsingContextID(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBrowsingContext(dom::BrowsingContext** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetWorkerAssociatedBrowsingContext(
+ dom::BrowsingContext** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetFrameBrowsingContext(dom::BrowsingContext** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTargetBrowsingContext(dom::BrowsingContext** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::ResetPrincipalToInheritToNullPrincipal() {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult TRRLoadInfo::GetOriginAttributes(
+ mozilla::OriginAttributes* aOriginAttributes) {
+ NS_ENSURE_ARG(aOriginAttributes);
+ *aOriginAttributes = mOriginAttributes;
+ return NS_OK;
+}
+
+nsresult TRRLoadInfo::SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetInitialSecurityCheckDone(bool aInitialSecurityCheckDone) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetInitialSecurityCheckDone(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::AppendRedirectHistoryEntry(nsIChannel* aChannelToDeriveFrom,
+ bool aIsInternalRedirect) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetRedirectChainIncludingInternalRedirects(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aChain) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>&
+TRRLoadInfo::RedirectChainIncludingInternalRedirects() {
+ return mEmptyRedirectChain;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetRedirectChain(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aChain) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>&
+TRRLoadInfo::RedirectChain() {
+ return mEmptyRedirectChain;
+}
+
+const nsTArray<nsCOMPtr<nsIPrincipal>>& TRRLoadInfo::AncestorPrincipals() {
+ return mEmptyPrincipals;
+}
+
+const nsTArray<uint64_t>& TRRLoadInfo::AncestorBrowsingContextIDs() {
+ return mEmptyBrowsingContextIDs;
+}
+
+void TRRLoadInfo::SetCorsPreflightInfo(const nsTArray<nsCString>& aHeaders,
+ bool aForcePreflight) {}
+
+const nsTArray<nsCString>& TRRLoadInfo::CorsUnsafeHeaders() {
+ return mCorsUnsafeHeaders;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetForcePreflight(bool* aForcePreflight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsPreflight(bool* aIsPreflight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetLoadTriggeredFromExternal(bool aLoadTriggeredFromExternal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadTriggeredFromExternal(bool* aLoadTriggeredFromExternal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetServiceWorkerTaintingSynthesized(
+ bool* aServiceWorkerTaintingSynthesized) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetTainting(uint32_t* aTaintingOut) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::MaybeIncreaseTainting(uint32_t aTainting) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void TRRLoadInfo::SynthesizeServiceWorkerTainting(LoadTainting aTainting) {}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetDocumentHasUserInteracted(bool* aDocumentHasUserInteracted) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetDocumentHasUserInteracted(bool aDocumentHasUserInteracted) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ bool* aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ bool aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetNeedForCheckingAntiTrackingHeuristic(bool* aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetNeedForCheckingAntiTrackingHeuristic(bool aValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetCspNonce(nsAString& aCspNonce) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetCspNonce(const nsAString& aCspNonce) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIntegrityMetadata(nsAString& aIntegrityMetadata) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsTopLevelLoad(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsFromProcessingFrameAttributes(
+ bool* aIsFromProcessingFrameAttributes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsMediaRequest(bool aIsMediaRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsMediaRequest(bool* aIsMediaRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsMediaInitialRequest(bool aIsMediaInitialRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsMediaInitialRequest(bool* aIsMediaInitialRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsFromObjectOrEmbed(bool aIsFromObjectOrEmbed) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsFromObjectOrEmbed(bool* aIsFromObjectOrEmbed) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetShouldSkipCheckForBrokenURLOrZeroSized(
+ bool* aShouldSkipCheckForBrokenURLOrZeroSized) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetResultPrincipalURI(nsIURI** aURI) {
+ nsCOMPtr<nsIURI> uri = mResultPrincipalURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetResultPrincipalURI(nsIURI* aURI) {
+ mResultPrincipalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetChannelCreationOriginalURI(nsIURI** aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetChannelCreationOriginalURI(nsIURI* aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetRequestBlockingReason(uint32_t aReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+TRRLoadInfo::GetRequestBlockingReason(uint32_t* aReason) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void TRRLoadInfo::SetClientInfo(const ClientInfo& aClientInfo) {}
+
+const Maybe<ClientInfo>& TRRLoadInfo::GetClientInfo() { return mClientInfo; }
+
+void TRRLoadInfo::GiveReservedClientSource(
+ UniquePtr<ClientSource>&& aClientSource) {}
+
+UniquePtr<ClientSource> TRRLoadInfo::TakeReservedClientSource() {
+ return nullptr;
+}
+
+void TRRLoadInfo::SetReservedClientInfo(const ClientInfo& aClientInfo) {}
+
+void TRRLoadInfo::OverrideReservedClientInfoInParent(
+ const ClientInfo& aClientInfo) {}
+
+const Maybe<ClientInfo>& TRRLoadInfo::GetReservedClientInfo() {
+ return mReservedClientInfo;
+}
+
+void TRRLoadInfo::SetInitialClientInfo(const ClientInfo& aClientInfo) {}
+
+const Maybe<ClientInfo>& TRRLoadInfo::GetInitialClientInfo() {
+ return mInitialClientInfo;
+}
+
+void TRRLoadInfo::SetController(const ServiceWorkerDescriptor& aServiceWorker) {
+}
+
+void TRRLoadInfo::ClearController() {}
+
+const Maybe<ServiceWorkerDescriptor>& TRRLoadInfo::GetController() {
+ return mController;
+}
+
+void TRRLoadInfo::SetPerformanceStorage(
+ PerformanceStorage* aPerformanceStorage) {}
+
+PerformanceStorage* TRRLoadInfo::GetPerformanceStorage() { return nullptr; }
+
+NS_IMETHODIMP
+TRRLoadInfo::GetCspEventListener(nsICSPEventListener** aCSPEventListener) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+already_AddRefed<nsIContentSecurityPolicy> TRRLoadInfo::GetCsp() {
+ return nullptr;
+}
+
+already_AddRefed<nsIContentSecurityPolicy> TRRLoadInfo::GetPreloadCsp() {
+ return nullptr;
+}
+
+already_AddRefed<nsIContentSecurityPolicy> TRRLoadInfo::GetCspToInherit() {
+ return nullptr;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetHttpsOnlyStatus(uint32_t* aHttpsOnlyStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetHttpsOnlyStatus(uint32_t aHttpsOnlyStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetHstsStatus(bool* aHstsStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetHstsStatus(bool aHstsStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetHasValidUserGestureActivation(
+ bool* aHasValidUserGestureActivation) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetHasValidUserGestureActivation(
+ bool aHasValidUserGestureActivation) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetInternalContentPolicyType(nsContentPolicyType* aResult) {
+ *aResult = mInternalContentPolicyType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetAllowDeprecatedSystemRequests(
+ bool* aAllowDeprecatedSystemRequests) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetAllowDeprecatedSystemRequests(
+ bool aAllowDeprecatedSystemRequests) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsUserTriggeredSave(bool* aIsUserTriggeredSave) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsUserTriggeredSave(bool aIsUserTriggeredSave) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsInDevToolsContext(bool* aIsInDevToolsContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsInDevToolsContext(bool aIsInDevToolsContext) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetParserCreatedScript(bool* aParserCreatedScript) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetParserCreatedScript(bool aParserCreatedScript) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetLoadingEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetLoadingEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ bool* aIsOriginTrialCoepCredentiallessEnabledForTopLevel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ bool aIsOriginTrialCoepCredentiallessEnabledForTopLevel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetUnstrippedURI(nsIURI** aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetUnstrippedURI(nsIURI* aURI) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+nsIInterceptionInfo* TRRLoadInfo::InterceptionInfo() { return nullptr; }
+void TRRLoadInfo::SetInterceptionInfo(nsIInterceptionInfo* aPrincipla) {}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetHasInjectedCookieForCookieBannerHandling(
+ bool* aHasInjectedCookieForCookieBannerHandling) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetHasInjectedCookieForCookieBannerHandling(
+ bool aHasInjectedCookieForCookieBannerHandling) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetWasSchemelessInput(bool* aWasSchemelessInput) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetWasSchemelessInput(bool aWasSchemelessInput) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/TRRLoadInfo.h b/netwerk/base/TRRLoadInfo.h
new file mode 100644
index 0000000000..2e08abe100
--- /dev/null
+++ b/netwerk/base/TRRLoadInfo.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TRRLoadInfo_h
+#define mozilla_TRRLoadInfo_h
+
+#include "nsILoadInfo.h"
+#include "nsIURI.h"
+#include "nsTArray.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/OriginAttributes.h"
+
+namespace mozilla {
+namespace net {
+
+// TRRLoadInfo is designed to be used by TRRServiceChannel only. Most of
+// nsILoadInfo functions are not implemented since TRRLoadInfo needs to
+// support off main thread.
+class TRRLoadInfo final : public nsILoadInfo {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSILOADINFO
+
+ TRRLoadInfo(nsIURI* aResultPrincipalURI,
+ nsContentPolicyType aContentPolicyType);
+
+ already_AddRefed<nsILoadInfo> Clone() const;
+
+ private:
+ virtual ~TRRLoadInfo() = default;
+
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsContentPolicyType mInternalContentPolicyType;
+ OriginAttributes mOriginAttributes;
+ nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>> mEmptyRedirectChain;
+ nsTArray<nsCOMPtr<nsIPrincipal>> mEmptyPrincipals;
+ nsTArray<uint64_t> mEmptyBrowsingContextIDs;
+ nsTArray<nsCString> mCorsUnsafeHeaders;
+ nsID mSandboxedNullPrincipalID;
+ Maybe<mozilla::dom::ClientInfo> mClientInfo;
+ Maybe<mozilla::dom::ClientInfo> mReservedClientInfo;
+ Maybe<mozilla::dom::ClientInfo> mInitialClientInfo;
+ Maybe<mozilla::dom::ServiceWorkerDescriptor> mController;
+ Maybe<RFPTarget> mOverriddenFingerprintingSettings;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_TRRLoadInfo_h
diff --git a/netwerk/base/ThrottleQueue.cpp b/netwerk/base/ThrottleQueue.cpp
new file mode 100644
index 0000000000..4313a6ecb3
--- /dev/null
+++ b/netwerk/base/ThrottleQueue.cpp
@@ -0,0 +1,410 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ThrottleQueue.h"
+#include "mozilla/net/InputChannelThrottleQueueParent.h"
+#include "nsISeekableStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIOService.h"
+#include "nsSocketTransportService2.h"
+#include "nsStreamUtils.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+class ThrottleInputStream final : public nsIAsyncInputStream,
+ public nsISeekableStream {
+ public:
+ ThrottleInputStream(nsIInputStream* aStream, ThrottleQueue* aQueue);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ void AllowInput();
+
+ private:
+ ~ThrottleInputStream();
+
+ nsCOMPtr<nsIInputStream> mStream;
+ RefPtr<ThrottleQueue> mQueue;
+ nsresult mClosedStatus;
+
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+NS_IMPL_ISUPPORTS(ThrottleInputStream, nsIAsyncInputStream, nsIInputStream,
+ nsITellableStream, nsISeekableStream)
+
+ThrottleInputStream::ThrottleInputStream(nsIInputStream* aStream,
+ ThrottleQueue* aQueue)
+ : mStream(aStream), mQueue(aQueue), mClosedStatus(NS_OK) {
+ MOZ_ASSERT(aQueue != nullptr);
+}
+
+ThrottleInputStream::~ThrottleInputStream() { Close(); }
+
+NS_IMETHODIMP
+ThrottleInputStream::Close() {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ if (mQueue) {
+ mQueue->DequeueStream(this);
+ mQueue = nullptr;
+ mClosedStatus = NS_BASE_STREAM_CLOSED;
+ }
+ return mStream->Close();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Available(uint64_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ return mStream->Available(aResult);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::StreamStatus() {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ return mStream->StreamStatus();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ uint32_t realCount;
+ nsresult rv = mQueue->Available(aCount, &realCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (realCount == 0) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mStream->Read(aBuf, realCount, aResult);
+ if (NS_SUCCEEDED(rv) && *aResult > 0) {
+ mQueue->RecordRead(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ uint32_t realCount;
+ nsresult rv = mQueue->Available(aCount, &realCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ MOZ_ASSERT(realCount <= aCount);
+
+ if (realCount == 0) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mStream->ReadSegments(aWriter, aClosure, realCount, aResult);
+ if (NS_SUCCEEDED(rv) && *aResult > 0) {
+ mQueue->RecordRead(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Seek(int32_t aWhence, int64_t aOffset) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->Seek(aWhence, aOffset);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Tell(int64_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsITellableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->Tell(aResult);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::SetEOF() {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->SetEOF();
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::CloseWithStatus(nsresult aStatus) {
+ if (NS_FAILED(mClosedStatus)) {
+ // Already closed, ignore.
+ return NS_OK;
+ }
+ if (NS_SUCCEEDED(aStatus)) {
+ aStatus = NS_BASE_STREAM_CLOSED;
+ }
+
+ mClosedStatus = Close();
+ if (NS_SUCCEEDED(mClosedStatus)) {
+ mClosedStatus = aStatus;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ if (aFlags != 0) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mCallback = aCallback;
+ mEventTarget = aEventTarget;
+ if (mCallback) {
+ mQueue->QueueStream(this);
+ } else {
+ mQueue->DequeueStream(this);
+ }
+ return NS_OK;
+}
+
+void ThrottleInputStream::AllowInput() {
+ MOZ_ASSERT(mCallback);
+ nsCOMPtr<nsIInputStreamCallback> callbackEvent = NS_NewInputStreamReadyEvent(
+ "ThrottleInputStream::AllowInput", mCallback, mEventTarget);
+ mCallback = nullptr;
+ mEventTarget = nullptr;
+ callbackEvent->OnInputStreamReady(this);
+}
+
+//-----------------------------------------------------------------------------
+
+// static
+already_AddRefed<nsIInputChannelThrottleQueue> ThrottleQueue::Create() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsIInputChannelThrottleQueue> tq;
+ if (nsIOService::UseSocketProcess()) {
+ tq = new InputChannelThrottleQueueParent();
+ } else {
+ tq = new ThrottleQueue();
+ }
+
+ return tq.forget();
+}
+
+NS_IMPL_ISUPPORTS(ThrottleQueue, nsIInputChannelThrottleQueue, nsITimerCallback,
+ nsINamed)
+
+ThrottleQueue::ThrottleQueue()
+
+{
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ }
+ if (NS_SUCCEEDED(rv)) mTimer = NS_NewTimer(sts);
+}
+
+ThrottleQueue::~ThrottleQueue() {
+ if (mTimer && mTimerArmed) {
+ mTimer->Cancel();
+ }
+ mTimer = nullptr;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::RecordRead(uint32_t aBytesRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ ThrottleEntry entry;
+ entry.mTime = TimeStamp::Now();
+ entry.mBytesRead = aBytesRead;
+ mReadEvents.AppendElement(entry);
+ mBytesProcessed += aBytesRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Available(uint32_t aRemaining, uint32_t* aAvailable) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ TimeStamp now = TimeStamp::Now();
+ TimeStamp oneSecondAgo = now - TimeDuration::FromSeconds(1);
+ size_t i;
+
+ // Remove all stale events.
+ for (i = 0; i < mReadEvents.Length(); ++i) {
+ if (mReadEvents[i].mTime >= oneSecondAgo) {
+ break;
+ }
+ }
+ mReadEvents.RemoveElementsAt(0, i);
+
+ uint32_t totalBytes = 0;
+ for (i = 0; i < mReadEvents.Length(); ++i) {
+ totalBytes += mReadEvents[i].mBytesRead;
+ }
+
+ uint32_t spread = mMaxBytesPerSecond - mMeanBytesPerSecond;
+ double prob = static_cast<double>(rand()) / RAND_MAX;
+ uint32_t thisSliceBytes =
+ mMeanBytesPerSecond - spread + static_cast<uint32_t>(2 * spread * prob);
+
+ if (totalBytes >= thisSliceBytes) {
+ *aAvailable = 0;
+ } else {
+ *aAvailable = std::min(thisSliceBytes, aRemaining);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Init(uint32_t aMeanBytesPerSecond, uint32_t aMaxBytesPerSecond) {
+ // Can be called on any thread.
+ if (aMeanBytesPerSecond == 0 || aMaxBytesPerSecond == 0 ||
+ aMaxBytesPerSecond < aMeanBytesPerSecond) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mMeanBytesPerSecond = aMeanBytesPerSecond;
+ mMaxBytesPerSecond = aMaxBytesPerSecond;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::BytesProcessed(uint64_t* aResult) {
+ *aResult = mBytesProcessed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::WrapStream(nsIInputStream* aInputStream,
+ nsIAsyncInputStream** aResult) {
+ nsCOMPtr<nsIAsyncInputStream> result =
+ new ThrottleInputStream(aInputStream, this);
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // A notified reader may need to push itself back on the queue.
+ // Swap out the list of readers so that this works properly.
+ nsTArray<RefPtr<ThrottleInputStream>> events = std::move(mAsyncEvents);
+
+ // Optimistically notify all the waiting readers, and then let them
+ // requeue if there isn't enough bandwidth.
+ for (size_t i = 0; i < events.Length(); ++i) {
+ events[i]->AllowInput();
+ }
+
+ mTimerArmed = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::GetName(nsACString& aName) {
+ aName.AssignLiteral("net::ThrottleQueue");
+ return NS_OK;
+}
+
+void ThrottleQueue::QueueStream(ThrottleInputStream* aStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mAsyncEvents.IndexOf(aStream) ==
+ nsTArray<RefPtr<mozilla::net::ThrottleInputStream>>::NoIndex) {
+ mAsyncEvents.AppendElement(aStream);
+
+ if (!mTimerArmed) {
+ uint32_t ms = 1000;
+ if (mReadEvents.Length() > 0) {
+ TimeStamp t = mReadEvents[0].mTime + TimeDuration::FromSeconds(1);
+ TimeStamp now = TimeStamp::Now();
+
+ if (t > now) {
+ ms = static_cast<uint32_t>((t - now).ToMilliseconds());
+ } else {
+ ms = 1;
+ }
+ }
+
+ if (NS_SUCCEEDED(
+ mTimer->InitWithCallback(this, ms, nsITimer::TYPE_ONE_SHOT))) {
+ mTimerArmed = true;
+ }
+ }
+ }
+}
+
+void ThrottleQueue::DequeueStream(ThrottleInputStream* aStream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ mAsyncEvents.RemoveElement(aStream);
+}
+
+NS_IMETHODIMP
+ThrottleQueue::GetMeanBytesPerSecond(uint32_t* aMeanBytesPerSecond) {
+ NS_ENSURE_ARG(aMeanBytesPerSecond);
+
+ *aMeanBytesPerSecond = mMeanBytesPerSecond;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::GetMaxBytesPerSecond(uint32_t* aMaxBytesPerSecond) {
+ NS_ENSURE_ARG(aMaxBytesPerSecond);
+
+ *aMaxBytesPerSecond = mMaxBytesPerSecond;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ThrottleQueue.h b/netwerk/base/ThrottleQueue.h
new file mode 100644
index 0000000000..4d06af8e64
--- /dev/null
+++ b/netwerk/base/ThrottleQueue.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ThrottleQueue_h
+#define mozilla_net_ThrottleQueue_h
+
+#include "mozilla/TimeStamp.h"
+#include "nsINamed.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+class ThrottleInputStream;
+
+/**
+ * An implementation of nsIInputChannelThrottleQueue that can be used
+ * to throttle uploads. This class is not thread-safe.
+ * Initialization and calls to WrapStream may be done on any thread;
+ * but otherwise, after creation, it can only be used on the socket
+ * thread. It currently throttles with a one second granularity, so
+ * may be a bit choppy.
+ */
+
+class ThrottleQueue : public nsIInputChannelThrottleQueue,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ static already_AddRefed<nsIInputChannelThrottleQueue> Create();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ void QueueStream(ThrottleInputStream* aStream);
+ void DequeueStream(ThrottleInputStream* aStream);
+
+ protected:
+ ThrottleQueue();
+ virtual ~ThrottleQueue();
+
+ struct ThrottleEntry {
+ TimeStamp mTime;
+ uint32_t mBytesRead = 0;
+ };
+
+ nsTArray<ThrottleEntry> mReadEvents;
+ uint32_t mMeanBytesPerSecond{0};
+ uint32_t mMaxBytesPerSecond{0};
+ uint64_t mBytesProcessed{0};
+
+ nsTArray<RefPtr<ThrottleInputStream>> mAsyncEvents;
+ nsCOMPtr<nsITimer> mTimer;
+ bool mTimerArmed{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ThrottleQueue_h
diff --git a/netwerk/base/Tickler.cpp b/netwerk/base/Tickler.cpp
new file mode 100644
index 0000000000..3030b24653
--- /dev/null
+++ b/netwerk/base/Tickler.cpp
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Tickler.h"
+
+#ifdef MOZ_USE_WIFI_TICKLER
+# include "nsComponentManagerUtils.h"
+# include "nsINamed.h"
+# include "nsServiceManagerUtils.h"
+# include "nsThreadUtils.h"
+# include "prnetdb.h"
+
+# include "mozilla/java/GeckoAppShellWrappers.h"
+# include "mozilla/jni/Utils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(Tickler, nsISupportsWeakReference, Tickler)
+
+Tickler::Tickler()
+ : mLock("Tickler::mLock"),
+ mActive(false),
+ mCanceled(false),
+ mEnabled(false),
+ mDelay(16),
+ mDuration(TimeDuration::FromMilliseconds(400)),
+ mFD(nullptr) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+Tickler::~Tickler() {
+ // non main thread uses of the tickler should hold weak
+ // references to it if they must hold a reference at all
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mThread) {
+ mThread->AsyncShutdown();
+ mThread = nullptr;
+ }
+
+ if (mTimer) mTimer->Cancel();
+ if (mFD) PR_Close(mFD);
+}
+
+nsresult Tickler::Init() {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mTimer);
+ MOZ_ASSERT(!mActive);
+ MOZ_ASSERT(!mThread);
+ MOZ_ASSERT(!mFD);
+
+ if (jni::IsAvailable()) {
+ java::GeckoAppShell::EnableNetworkNotifications();
+ }
+
+ mFD = PR_OpenUDPSocket(PR_AF_INET);
+ if (!mFD) return NS_ERROR_FAILURE;
+
+ // make sure new socket has a ttl of 1
+ // failure is not fatal.
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_IpTimeToLive;
+ opt.value.ip_ttl = 1;
+ PR_SetSocketOption(mFD, &opt);
+
+ nsresult rv = NS_NewNamedThread("wifi tickler", getter_AddRefs(mThread));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITimer> tmpTimer = NS_NewTimer(mThread);
+ if (!tmpTimer) return NS_ERROR_OUT_OF_MEMORY;
+
+ mTimer.swap(tmpTimer);
+
+ mAddr.inet.family = PR_AF_INET;
+ mAddr.inet.port = PR_htons(4886);
+ mAddr.inet.ip = 0;
+
+ return NS_OK;
+}
+
+void Tickler::Tickle() {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mThread);
+ mLastTickle = TimeStamp::Now();
+ if (!mActive) MaybeStartTickler();
+}
+
+void Tickler::PostCheckTickler() {
+ mLock.AssertCurrentThreadOwns();
+ mThread->Dispatch(NewRunnableMethod("net::Tickler::CheckTickler", this,
+ &Tickler::CheckTickler),
+ NS_DISPATCH_NORMAL);
+ return;
+}
+
+void Tickler::MaybeStartTicklerUnlocked() {
+ MutexAutoLock lock(mLock);
+ MaybeStartTickler();
+}
+
+void Tickler::MaybeStartTickler() {
+ mLock.AssertCurrentThreadOwns();
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NewRunnableMethod("net::Tickler::MaybeStartTicklerUnlocked", this,
+ &Tickler::MaybeStartTicklerUnlocked));
+ return;
+ }
+
+ if (!mPrefs) mPrefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (mPrefs) {
+ int32_t val;
+ bool boolVal;
+
+ if (NS_SUCCEEDED(
+ mPrefs->GetBoolPref("network.tickle-wifi.enabled", &boolVal)))
+ mEnabled = boolVal;
+
+ if (NS_SUCCEEDED(
+ mPrefs->GetIntPref("network.tickle-wifi.duration", &val))) {
+ if (val < 1) val = 1;
+ if (val > 100000) val = 100000;
+ mDuration = TimeDuration::FromMilliseconds(val);
+ }
+
+ if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.delay", &val))) {
+ if (val < 1) val = 1;
+ if (val > 1000) val = 1000;
+ mDelay = static_cast<uint32_t>(val);
+ }
+ }
+
+ PostCheckTickler();
+}
+
+void Tickler::CheckTickler() {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+
+ bool shouldRun =
+ (!mCanceled) && ((TimeStamp::Now() - mLastTickle) <= mDuration);
+
+ if ((shouldRun && mActive) || (!shouldRun && !mActive))
+ return; // no change in state
+
+ if (mActive)
+ StopTickler();
+ else
+ StartTickler();
+}
+
+void Tickler::Cancel() {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(NS_IsMainThread());
+ mCanceled = true;
+ if (mThread) PostCheckTickler();
+}
+
+void Tickler::StopTickler() {
+ mLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+ MOZ_ASSERT(mTimer);
+ MOZ_ASSERT(mActive);
+
+ mTimer->Cancel();
+ mActive = false;
+}
+
+class TicklerTimer final : public nsITimerCallback, public nsINamed {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ explicit TicklerTimer(Tickler* aTickler) {
+ mTickler = do_GetWeakReference(aTickler);
+ }
+
+ // nsINamed
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("TicklerTimer");
+ return NS_OK;
+ }
+
+ private:
+ ~TicklerTimer() {}
+
+ nsWeakPtr mTickler;
+};
+
+void Tickler::StartTickler() {
+ mLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mThread == NS_GetCurrentThread());
+ MOZ_ASSERT(!mActive);
+ MOZ_ASSERT(mTimer);
+
+ if (NS_SUCCEEDED(mTimer->InitWithCallback(new TicklerTimer(this),
+ mEnabled ? mDelay : 1000,
+ nsITimer::TYPE_REPEATING_SLACK)))
+ mActive = true;
+}
+
+// argument should be in network byte order
+void Tickler::SetIPV4Address(uint32_t address) { mAddr.inet.ip = address; }
+
+// argument should be in network byte order
+void Tickler::SetIPV4Port(uint16_t port) { mAddr.inet.port = port; }
+
+NS_IMPL_ISUPPORTS(TicklerTimer, nsITimerCallback, nsINamed)
+
+NS_IMETHODIMP TicklerTimer::Notify(nsITimer* timer) {
+ RefPtr<Tickler> tickler = do_QueryReferent(mTickler);
+ if (!tickler) return NS_ERROR_FAILURE;
+ MutexAutoLock lock(tickler->mLock);
+
+ if (!tickler->mFD) {
+ tickler->StopTickler();
+ return NS_ERROR_FAILURE;
+ }
+
+ if (tickler->mCanceled ||
+ ((TimeStamp::Now() - tickler->mLastTickle) > tickler->mDuration)) {
+ tickler->StopTickler();
+ return NS_OK;
+ }
+
+ if (!tickler->mEnabled) return NS_OK;
+
+ PR_SendTo(tickler->mFD, "", 0, 0, &tickler->mAddr, 0);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#else // not defined MOZ_USE_WIFI_TICKLER
+
+namespace mozilla {
+namespace net {
+NS_IMPL_ISUPPORTS0(Tickler)
+} // namespace net
+} // namespace mozilla
+
+#endif // defined MOZ_USE_WIFI_TICKLER
diff --git a/netwerk/base/Tickler.h b/netwerk/base/Tickler.h
new file mode 100644
index 0000000000..264ecefebb
--- /dev/null
+++ b/netwerk/base/Tickler.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Tickler_h
+#define mozilla_net_Tickler_h
+
+// The tickler sends a regular 0 byte UDP heartbeat out to a
+// particular address for a short time after it has been touched. This
+// is used on some mobile wifi chipsets to mitigate Power Save Polling
+// (PSP) Mode when we are anticipating a response packet
+// soon. Typically PSP adds 100ms of latency to a read event because
+// the packet delivery is not triggered until the 802.11 beacon is
+// delivered to the host (100ms is the standard Access Point
+// configuration for the beacon interval.) Requesting a frequent
+// transmission and getting a CTS frame from the AP at least that
+// frequently allows for low latency receives when we have reason to
+// expect them (e.g a SYN-ACK).
+//
+// The tickler is used to allow RTT based phases of web transport to
+// complete quickly when on wifi - ARP, DNS, TCP handshake, SSL
+// handshake, HTTP headers, and the TCP slow start phase. The
+// transaction is given up to 400 miliseconds by default to get
+// through those phases before the tickler is disabled.
+//
+// The tickler only applies to wifi on mobile right now. Hopefully it
+// can also be restricted to particular handset models in the future.
+
+#if defined(ANDROID) && !defined(MOZ_PROXY_BYPASS_PROTECTION)
+# define MOZ_USE_WIFI_TICKLER
+#endif
+
+#include "mozilla/Attributes.h"
+#include "nsISupports.h"
+#include <stdint.h>
+
+#ifdef MOZ_USE_WIFI_TICKLER
+# include "mozilla/Mutex.h"
+# include "mozilla/TimeStamp.h"
+# include "nsISupports.h"
+# include "nsIThread.h"
+# include "nsITimer.h"
+# include "nsWeakReference.h"
+# include "prio.h"
+
+class nsIPrefBranch;
+#endif
+
+namespace mozilla {
+namespace net {
+
+#ifdef MOZ_USE_WIFI_TICKLER
+
+// 8f769ed6-207c-4af9-9f7e-9e832da3754e
+# define NS_TICKLER_IID \
+ { \
+ 0x8f769ed6, 0x207c, 0x4af9, { \
+ 0x9f, 0x7e, 0x9e, 0x83, 0x2d, 0xa3, 0x75, 0x4e \
+ } \
+ }
+
+class Tickler final : public nsSupportsWeakReference {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TICKLER_IID)
+
+ // These methods are main thread only
+ Tickler();
+ void Cancel();
+ nsresult Init();
+ void SetIPV4Address(uint32_t address);
+ void SetIPV4Port(uint16_t port);
+
+ // Tickle the tickler to (re-)start the activity.
+ // May call from any thread
+ void Tickle();
+
+ private:
+ ~Tickler();
+
+ friend class TicklerTimer;
+ Mutex mLock MOZ_UNANNOTATED;
+ nsCOMPtr<nsIThread> mThread;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIPrefBranch> mPrefs;
+
+ bool mActive;
+ bool mCanceled;
+ bool mEnabled;
+ uint32_t mDelay;
+ TimeDuration mDuration;
+ PRFileDesc* mFD;
+
+ TimeStamp mLastTickle;
+ PRNetAddr mAddr;
+
+ // These functions may be called from any thread
+ void PostCheckTickler();
+ void MaybeStartTickler();
+ void MaybeStartTicklerUnlocked();
+
+ // Tickler thread only
+ void CheckTickler();
+ void StartTickler();
+ void StopTickler();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Tickler, NS_TICKLER_IID)
+
+#else // not defined MOZ_USE_WIFI_TICKLER
+
+class Tickler final : public nsISupports {
+ ~Tickler() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ Tickler() = default;
+ nsresult Init() { return NS_ERROR_NOT_IMPLEMENTED; }
+ void Cancel() {}
+ void SetIPV4Address(uint32_t){};
+ void SetIPV4Port(uint16_t) {}
+ void Tickle() {}
+};
+
+#endif // defined MOZ_USE_WIFI_TICKLER
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Tickler_h
diff --git a/netwerk/base/ascii_pac_utils.js b/netwerk/base/ascii_pac_utils.js
new file mode 100644
index 0000000000..cedb1a0f21
--- /dev/null
+++ b/netwerk/base/ascii_pac_utils.js
@@ -0,0 +1,256 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global dnsResolve */
+
+function dnsDomainIs(host, domain) {
+ return (
+ host.length >= domain.length &&
+ host.substring(host.length - domain.length) == domain
+ );
+}
+
+function dnsDomainLevels(host) {
+ return host.split(".").length - 1;
+}
+
+function isValidIpAddress(ipchars) {
+ var matches = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(ipchars);
+ if (matches == null) {
+ return false;
+ } else if (
+ matches[1] > 255 ||
+ matches[2] > 255 ||
+ matches[3] > 255 ||
+ matches[4] > 255
+ ) {
+ return false;
+ }
+ return true;
+}
+
+function convert_addr(ipchars) {
+ var bytes = ipchars.split(".");
+ var result =
+ ((bytes[0] & 0xff) << 24) |
+ ((bytes[1] & 0xff) << 16) |
+ ((bytes[2] & 0xff) << 8) |
+ (bytes[3] & 0xff);
+ return result;
+}
+
+function isInNet(ipaddr, pattern, maskstr) {
+ if (!isValidIpAddress(pattern) || !isValidIpAddress(maskstr)) {
+ return false;
+ }
+ if (!isValidIpAddress(ipaddr)) {
+ ipaddr = dnsResolve(ipaddr);
+ if (ipaddr == null) {
+ return false;
+ }
+ }
+ var host = convert_addr(ipaddr);
+ var pat = convert_addr(pattern);
+ var mask = convert_addr(maskstr);
+ return (host & mask) == (pat & mask);
+}
+
+function isPlainHostName(host) {
+ return host.search("(\\.)|:") == -1;
+}
+
+function isResolvable(host) {
+ var ip = dnsResolve(host);
+ return ip != null;
+}
+
+function localHostOrDomainIs(host, hostdom) {
+ return host == hostdom || hostdom.lastIndexOf(host + ".", 0) == 0;
+}
+
+function shExpMatch(url, pattern) {
+ pattern = pattern.replace(/\./g, "\\.");
+ pattern = pattern.replace(/\*/g, ".*");
+ pattern = pattern.replace(/\?/g, ".");
+ var newRe = new RegExp("^" + pattern + "$");
+ return newRe.test(url);
+}
+
+var wdays = { SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6 };
+var months = {
+ JAN: 0,
+ FEB: 1,
+ MAR: 2,
+ APR: 3,
+ MAY: 4,
+ JUN: 5,
+ JUL: 6,
+ AUG: 7,
+ SEP: 8,
+ OCT: 9,
+ NOV: 10,
+ DEC: 11,
+};
+
+function weekdayRange() {
+ function getDay(weekday) {
+ if (weekday in wdays) {
+ return wdays[weekday];
+ }
+ return -1;
+ }
+ var date = new Date();
+ var argc = arguments.length;
+ var wday;
+ if (argc < 1) {
+ return false;
+ }
+ if (arguments[argc - 1] == "GMT") {
+ argc--;
+ wday = date.getUTCDay();
+ } else {
+ wday = date.getDay();
+ }
+ var wd1 = getDay(arguments[0]);
+ var wd2 = argc == 2 ? getDay(arguments[1]) : wd1;
+ if (wd1 == -1 || wd2 == -1) {
+ return false;
+ }
+
+ if (wd1 <= wd2) {
+ return wd1 <= wday && wday <= wd2;
+ }
+
+ return wd2 >= wday || wday >= wd1;
+}
+
+function dateRange() {
+ function getMonth(name) {
+ if (name in months) {
+ return months[name];
+ }
+ return -1;
+ }
+ var date = new Date();
+ var argc = arguments.length;
+ if (argc < 1) {
+ return false;
+ }
+ var isGMT = arguments[argc - 1] == "GMT";
+
+ if (isGMT) {
+ argc--;
+ }
+ // function will work even without explict handling of this case
+ if (argc == 1) {
+ let tmp = parseInt(arguments[0]);
+ if (isNaN(tmp)) {
+ return (
+ (isGMT ? date.getUTCMonth() : date.getMonth()) == getMonth(arguments[0])
+ );
+ } else if (tmp < 32) {
+ return (isGMT ? date.getUTCDate() : date.getDate()) == tmp;
+ }
+ return (isGMT ? date.getUTCFullYear() : date.getFullYear()) == tmp;
+ }
+ var year = date.getFullYear();
+ var date1, date2;
+ date1 = new Date(year, 0, 1, 0, 0, 0);
+ date2 = new Date(year, 11, 31, 23, 59, 59);
+ var adjustMonth = false;
+ for (let i = 0; i < argc >> 1; i++) {
+ let tmp = parseInt(arguments[i]);
+ if (isNaN(tmp)) {
+ let mon = getMonth(arguments[i]);
+ date1.setMonth(mon);
+ } else if (tmp < 32) {
+ adjustMonth = argc <= 2;
+ date1.setDate(tmp);
+ } else {
+ date1.setFullYear(tmp);
+ }
+ }
+ for (let i = argc >> 1; i < argc; i++) {
+ let tmp = parseInt(arguments[i]);
+ if (isNaN(tmp)) {
+ let mon = getMonth(arguments[i]);
+ date2.setMonth(mon);
+ } else if (tmp < 32) {
+ date2.setDate(tmp);
+ } else {
+ date2.setFullYear(tmp);
+ }
+ }
+ if (adjustMonth) {
+ date1.setMonth(date.getMonth());
+ date2.setMonth(date.getMonth());
+ }
+ if (isGMT) {
+ let tmp = date;
+ tmp.setFullYear(date.getUTCFullYear());
+ tmp.setMonth(date.getUTCMonth());
+ tmp.setDate(date.getUTCDate());
+ tmp.setHours(date.getUTCHours());
+ tmp.setMinutes(date.getUTCMinutes());
+ tmp.setSeconds(date.getUTCSeconds());
+ date = tmp;
+ }
+ return date1 <= date2
+ ? date1 <= date && date <= date2
+ : date2 >= date || date >= date1;
+}
+
+function timeRange() {
+ var argc = arguments.length;
+ var date = new Date();
+ var isGMT = false;
+ if (argc < 1) {
+ return false;
+ }
+ if (arguments[argc - 1] == "GMT") {
+ isGMT = true;
+ argc--;
+ }
+
+ var hour = isGMT ? date.getUTCHours() : date.getHours();
+ var date1, date2;
+ date1 = new Date();
+ date2 = new Date();
+
+ if (argc == 1) {
+ return hour == arguments[0];
+ } else if (argc == 2) {
+ return arguments[0] <= hour && hour <= arguments[1];
+ }
+ switch (argc) {
+ case 6:
+ date1.setSeconds(arguments[2]);
+ date2.setSeconds(arguments[5]);
+ // falls through
+ case 4:
+ var middle = argc >> 1;
+ date1.setHours(arguments[0]);
+ date1.setMinutes(arguments[1]);
+ date2.setHours(arguments[middle]);
+ date2.setMinutes(arguments[middle + 1]);
+ if (middle == 2) {
+ date2.setSeconds(59);
+ }
+ break;
+ default:
+ throw new Error("timeRange: bad number of arguments");
+ }
+
+ if (isGMT) {
+ date.setFullYear(date.getUTCFullYear());
+ date.setMonth(date.getUTCMonth());
+ date.setDate(date.getUTCDate());
+ date.setHours(date.getUTCHours());
+ date.setMinutes(date.getUTCMinutes());
+ date.setSeconds(date.getUTCSeconds());
+ }
+ return date1 <= date2
+ ? date1 <= date && date <= date2
+ : date2 >= date || date >= date1;
+}
diff --git a/netwerk/base/http-sfv/Cargo.toml b/netwerk/base/http-sfv/Cargo.toml
new file mode 100644
index 0000000000..4625cdf974
--- /dev/null
+++ b/netwerk/base/http-sfv/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "http_sfv"
+version = "0.1.0"
+authors = ["barabass <yalyna.ts@gmail.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+sfv = "0.9.1"
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
diff --git a/netwerk/base/http-sfv/SFVService.cpp b/netwerk/base/http-sfv/SFVService.cpp
new file mode 100644
index 0000000000..9759993e7f
--- /dev/null
+++ b/netwerk/base/http-sfv/SFVService.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCOMPtr.h"
+#include "SFVService.h"
+
+// This anonymous namespace prevents outside C++ code from improperly accessing
+// these implementation details.
+namespace {
+extern "C" {
+// Implemented in Rust.
+void new_sfv_service(nsISFVService** result);
+}
+
+static mozilla::StaticRefPtr<nsISFVService> sService;
+} // namespace
+
+namespace mozilla::net {
+
+already_AddRefed<nsISFVService> GetSFVService() {
+ nsCOMPtr<nsISFVService> service;
+
+ if (sService) {
+ service = sService;
+ } else {
+ new_sfv_service(getter_AddRefs(service));
+ sService = service;
+ mozilla::ClearOnShutdown(&sService);
+ }
+
+ return service.forget();
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/base/http-sfv/SFVService.h b/netwerk/base/http-sfv/SFVService.h
new file mode 100644
index 0000000000..4016951609
--- /dev/null
+++ b/netwerk/base/http-sfv/SFVService.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStructuredFieldValues.h"
+namespace mozilla {
+namespace net {
+
+already_AddRefed<nsISFVService> GetSFVService();
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/http-sfv/moz.build b/netwerk/base/http-sfv/moz.build
new file mode 100644
index 0000000000..ed857c91a8
--- /dev/null
+++ b/netwerk/base/http-sfv/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "nsIStructuredFieldValues.idl",
+]
+
+XPIDL_MODULE = "http-sfv"
+
+EXPORTS.mozilla.net += ["SFVService.h"]
+
+SOURCES += ["SFVService.cpp"]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/base/http-sfv/nsIStructuredFieldValues.idl b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl
new file mode 100644
index 0000000000..3f02b33953
--- /dev/null
+++ b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl
@@ -0,0 +1,290 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Conceptually, there are three types of structured field (header) values:
+ *
+ * Item - can be an Integer, Decimal, String, Token, Byte Sequence, or Boolean.
+ * It can have associated Parameters.
+ * List - array of zero or more members, each of which can be an Item or an InnerList,
+ * both of which can be Parameterized.
+ * Dictionary - ordered map of name-value pairs, where the names are short textual strings
+ * and the values are Items or arrays of Items (represented with InnerList),
+ * both of which can be Parameterized. There can be zero or more members,
+ * and their names are unique in the scope of the Dictionary they occur within.
+ *
+ *
+ * There's also a few primitive types used to construct structured field values:
+ * - BareItem used as Item's value or as a parameter value in Parameters.
+ * - Parameters are an ordered map of key-value pairs that are associated with an Item or InnerList.
+ * The keys are unique within the scope the Parameters they occur within, and the values are BareItem.
+ * - InnerList is an array of zero or more Items. Can have Parameters.
+ * - ListEntry represents either Item or InnerList as a member of List or as member-value in Dictionary.
+ */
+
+
+
+/**
+ * nsISFVBareItem is a building block for Item header value (nsISFVItem) and Parameters (nsISFVParams).
+ * It can be of type BOOL, STRING, DECIMAL, INTEGER, TOKEN, BYTE_SEQUENCE.
+ * Each type is represented by its own interface which is used to create
+ * a bare item of that type.
+ */
+[scriptable, builtinclass, uuid(7072853f-215b-4a8a-92e5-9732bccc377b)]
+interface nsISFVBareItem : nsISupports {
+ const long BOOL = 1;
+ const long STRING = 2;
+ const long DECIMAL = 3;
+ const long INTEGER = 4;
+ const long TOKEN = 5;
+ const long BYTE_SEQUENCE = 6;
+
+ /**
+ * Returns value associated with type of bare item.
+ * Used to identify type of bare item without querying for interface
+ * (like nsISFVString, etc).
+ */
+ readonly attribute long type;
+};
+
+[scriptable, builtinclass, uuid(843eea44-990a-422c-bbf2-2aa4ba9ee4d2)]
+interface nsISFVInteger : nsISFVBareItem {
+ attribute long long value;
+};
+
+[scriptable, builtinclass, uuid(df6a0787-7caa-4fef-b145-08c1104c2fde)]
+interface nsISFVString : nsISFVBareItem {
+ attribute ACString value;
+};
+
+[scriptable, builtinclass, uuid(d263c6d7-4123-4c39-a121-ccf874a19012)]
+interface nsISFVBool : nsISFVBareItem {
+ attribute boolean value;
+};
+
+[scriptable, builtinclass, uuid(1098da8b-b4df-4526-b985-53dbd4160ad2)]
+interface nsISFVDecimal : nsISFVBareItem {
+ attribute double value;
+};
+
+[scriptable, builtinclass, uuid(8ad33d52-b9b2-4a17-8aa8-991250fc1214)]
+interface nsISFVToken : nsISFVBareItem {
+ attribute ACString value;
+};
+
+[scriptable, builtinclass, uuid(887eaef0-19fe-42bc-9a42-9ff773aa8fea)]
+interface nsISFVByteSeq : nsISFVBareItem {
+ attribute ACString value;
+};
+
+
+/**
+ * nsISFVParams represents parameters, key-value pairs of ACString and nsISFVBareItem,
+ * which parametrize Item type header or InnerList type withing List type header.
+ */
+[scriptable, builtinclass, uuid(b1a397d7-3333-43e7-993a-fbe8ab90ee96)]
+interface nsISFVParams : nsISupports {
+ /**
+ * Return value (nsISFVBareItem) stored for key, if it is present
+ *
+ * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters.
+ */
+ nsISFVBareItem get(in ACString key);
+
+ /**
+ * Insert a new key-value pair.
+ * If an equivalent key already exists: the key remains and retains in its place in the order,
+ * its corresponding value is updated with the new value.
+ *
+ * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVBareItem interface.
+ */
+ void set(in ACString key, in nsISFVBareItem item);
+
+ /**
+ * Remove the key-value pair equivalent to key.
+ *
+ * @throws NS_ERROR_UNEXPECTED upon attempt to delete key that does not exist in parameters.
+ */
+ void delete(in ACString key);
+
+ /**
+ * Returns array of keys.
+ */
+ Array<ACString> keys();
+};
+
+/**
+ * nsISFVParametrizable is implemented for types that
+ * can be parametrized with nsISFVParams
+ */
+[scriptable, builtinclass, uuid(6c0399f8-01de-4b25-b339-68e35e8d2e49)]
+interface nsISFVParametrizable : nsISupports {
+ readonly attribute nsISFVParams params;
+};
+
+/**
+ * nsISFVItemOrInnerList represents member in nsISFVList
+ * or member-value in nsISFVDictionary.
+ * nsISFVItemOrInnerList is implemented for
+ * nsISFVItem or nsISFVInnerList, both of which are used
+ * to create nsISFVList and nsISFVDictionary.
+ */
+[scriptable, builtinclass, uuid(99ac1b56-b5b3-44e7-ad96-db7444aae4b2)]
+interface nsISFVItemOrInnerList : nsISFVParametrizable {
+};
+
+/**
+ * nsISFVSerialize indicates that object can be serialized into ACString.
+ */
+[scriptable, builtinclass, uuid(28b9215d-c131-413c-9482-0004a371a5ec)]
+interface nsISFVSerialize : nsISupports {
+ ACString serialize();
+};
+
+/**
+ * nsISFVItem represents Item structured header value.
+ */
+[scriptable, builtinclass, uuid(abe8826b-6af7-4e54-bd2c-46ab231700ce)]
+interface nsISFVItem : nsISFVItemOrInnerList {
+ readonly attribute nsISFVBareItem value;
+ ACString serialize();
+};
+
+/**
+ * nsISFVInnerList can be used as a member of nsISFVList
+ * or a member-value of nsISFVDictionary.
+ */
+[scriptable, builtinclass, uuid(b2e52be2-8488-41b2-9ee2-3c48d92d095c)]
+interface nsISFVInnerList : nsISFVItemOrInnerList {
+ attribute Array<nsISFVItem> items;
+};
+
+/**
+ * nsISFVList represents List structured header value.
+ */
+[scriptable, builtinclass, uuid(02bb92a6-d1de-449c-b54f-d137f30c613d)]
+interface nsISFVList : nsISFVSerialize {
+ /**
+ * Returns array of members.
+ * QueryInterface can be used on a member to get more specific type.
+ */
+ attribute Array<nsISFVItemOrInnerList> members;
+
+ /**
+ * In case when header value is split across lines, it's possible
+ * this method parses supplied line and merges it with members of existing object.
+ */
+ void parseMore(in ACString header);
+};
+
+/**
+ * nsISFVDictionary represents nsISFVDictionary structured header value.
+ */
+[scriptable, builtinclass, uuid(6642a7fe-7026-4eba-b730-05e230ee3437)]
+interface nsISFVDictionary : nsISFVSerialize {
+
+ /**
+ * Return value (nsISFVItemOrInnerList) stored for key, if it is present.
+ * QueryInterface can be used on a value to get more specific type.
+ *
+ * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters.
+ */
+ nsISFVItemOrInnerList get(in ACString key);
+
+ /**
+ * Insert a new key-value pair.
+ * If an equivalent key already exists: the key remains and retains in its place in the order,
+ * its corresponding value is updated with the new value.
+ *
+ * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVItemOrInnerList interface.
+ */
+ void set(in ACString key, in nsISFVItemOrInnerList member_value);
+
+ /**
+ * Remove the key-value pair equivalent to key.
+ *
+ * @throws NS_ERROR_UNEXPECTED upon attempt to delete key that does not exist in parameters.
+ */
+ void delete(in ACString key);
+
+ /**
+ * Returns array of keys.
+ */
+ Array<ACString> keys();
+
+ /**
+ * In case when header value is split across lines, it's possible
+ * this method parses supplied line and merges it with members of existing object.
+ */
+ void parseMore(in ACString header);
+};
+
+
+/**
+ * nsISFVService provides a set of functions for working with HTTP header value as an object.
+ * It exposes functions for creating object from string containing header value,
+ * as well as individual components for manual structured header object creation.
+ */
+[scriptable, builtinclass, uuid(049f4be1-2f22-4438-a8da-518552ed390c)]
+interface nsISFVService: nsISupports
+{
+ /**
+ * Parses provided string into Dictionary header value (nsISFVDictionary).
+ *
+ * @throws NS_ERROR_FAILURE if parsing fails.
+ */
+ nsISFVDictionary parseDictionary(in ACString header);
+
+ /**
+ * Parses provided string into List header value (nsISFVList).
+ *
+ * @throws NS_ERROR_FAILURE if parsing fails.
+ */
+ nsISFVList parseList(in ACString header);
+
+ /**
+ * Parses provided string into Item header value (nsISFVItem).
+ *
+ * @throws NS_ERROR_FAILURE if parsing fails.
+ */
+ nsISFVItem parseItem(in ACString header);
+
+ /**
+ * The following functions create bare item of specific type.
+ */
+ nsISFVInteger newInteger(in long long value);
+ nsISFVBool newBool(in bool value);
+ nsISFVDecimal newDecimal(in double value);
+ nsISFVString newString(in ACString value);
+ nsISFVByteSeq newByteSequence(in ACString value);
+ nsISFVToken newToken(in ACString value);
+
+ /**
+ * Creates nsISFVParams with no parameters. In other words, it's an empty map byt default.
+ */
+ nsISFVParams newParameters();
+
+ /**
+ * Creates nsISFVInnerList from nsISFVItem array and nsISFVParams.
+ */
+ nsISFVInnerList newInnerList(in Array<nsISFVItem> items, in nsISFVParams params);
+
+ /**
+ * Creates nsISFVItem, which represents Item header value, from nsISFVBareItem and associated nsISFVParams.
+ */
+ nsISFVItem newItem(in nsISFVBareItem value, in nsISFVParams params);
+
+ /**
+ * Creates nsISFVList, which represents List header value, from array of nsISFVItemOrInnerList.
+ * nsISFVItemOrInnerList represens either Item (nsISFVItem) or Inner List (nsISFVInnerList).
+ */
+ nsISFVList newList(in Array<nsISFVItemOrInnerList> members);
+
+ /**
+ * Creates nsISFVDictionary representing Dictionary header value. It is empty by default.
+ */
+ nsISFVDictionary newDictionary();
+};
diff --git a/netwerk/base/http-sfv/src/lib.rs b/netwerk/base/http-sfv/src/lib.rs
new file mode 100644
index 0000000000..fe669f5f3f
--- /dev/null
+++ b/netwerk/base/http-sfv/src/lib.rs
@@ -0,0 +1,873 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_NULL_POINTER, NS_ERROR_UNEXPECTED, NS_OK};
+use nsstring::{nsACString, nsCString};
+use sfv::Parser;
+use sfv::SerializeValue;
+use sfv::{
+ BareItem, Decimal, Dictionary, FromPrimitive, InnerList, Item, List, ListEntry, Parameters,
+ ParseMore,
+};
+use std::cell::RefCell;
+use std::ops::Deref;
+use thin_vec::ThinVec;
+use xpcom::interfaces::{
+ nsISFVBareItem, nsISFVBool, nsISFVByteSeq, nsISFVDecimal, nsISFVDictionary, nsISFVInnerList,
+ nsISFVInteger, nsISFVItem, nsISFVItemOrInnerList, nsISFVList, nsISFVParams, nsISFVService,
+ nsISFVString, nsISFVToken,
+};
+use xpcom::{xpcom, xpcom_method, RefPtr, XpCom};
+
+#[no_mangle]
+pub unsafe extern "C" fn new_sfv_service(result: *mut *const nsISFVService) {
+ let service: RefPtr<SFVService> = SFVService::new();
+ RefPtr::new(service.coerce::<nsISFVService>()).forget(&mut *result);
+}
+
+#[xpcom(implement(nsISFVService), atomic)]
+struct SFVService {}
+
+impl SFVService {
+ fn new() -> RefPtr<SFVService> {
+ SFVService::allocate(InitSFVService {})
+ }
+
+ xpcom_method!(parse_dictionary => ParseDictionary(header: *const nsACString) -> *const nsISFVDictionary);
+ fn parse_dictionary(&self, header: &nsACString) -> Result<RefPtr<nsISFVDictionary>, nsresult> {
+ let parsed_dict = Parser::parse_dictionary(&header).map_err(|_| NS_ERROR_FAILURE)?;
+ let sfv_dict = SFVDictionary::new();
+ sfv_dict.value.replace(parsed_dict);
+ Ok(RefPtr::new(sfv_dict.coerce::<nsISFVDictionary>()))
+ }
+
+ xpcom_method!(parse_list => ParseList(field_value: *const nsACString) -> *const nsISFVList);
+ fn parse_list(&self, header: &nsACString) -> Result<RefPtr<nsISFVList>, nsresult> {
+ let parsed_list = Parser::parse_list(&header).map_err(|_| NS_ERROR_FAILURE)?;
+
+ let mut nsi_members = Vec::new();
+ for item_or_inner_list in parsed_list.iter() {
+ nsi_members.push(interface_from_list_entry(item_or_inner_list)?)
+ }
+ let sfv_list = SFVList::allocate(InitSFVList {
+ members: RefCell::new(nsi_members),
+ });
+ Ok(RefPtr::new(sfv_list.coerce::<nsISFVList>()))
+ }
+
+ xpcom_method!(parse_item => ParseItem(header: *const nsACString) -> *const nsISFVItem);
+ fn parse_item(&self, header: &nsACString) -> Result<RefPtr<nsISFVItem>, nsresult> {
+ let parsed_item = Parser::parse_item(&header).map_err(|_| NS_ERROR_FAILURE)?;
+ interface_from_item(&parsed_item)
+ }
+
+ xpcom_method!(new_integer => NewInteger(value: i64) -> *const nsISFVInteger);
+ fn new_integer(&self, value: i64) -> Result<RefPtr<nsISFVInteger>, nsresult> {
+ Ok(RefPtr::new(
+ SFVInteger::new(value).coerce::<nsISFVInteger>(),
+ ))
+ }
+
+ xpcom_method!(new_decimal => NewDecimal(value: f64) -> *const nsISFVDecimal);
+ fn new_decimal(&self, value: f64) -> Result<RefPtr<nsISFVDecimal>, nsresult> {
+ Ok(RefPtr::new(
+ SFVDecimal::new(value).coerce::<nsISFVDecimal>(),
+ ))
+ }
+
+ xpcom_method!(new_bool => NewBool(value: bool) -> *const nsISFVBool);
+ fn new_bool(&self, value: bool) -> Result<RefPtr<nsISFVBool>, nsresult> {
+ Ok(RefPtr::new(SFVBool::new(value).coerce::<nsISFVBool>()))
+ }
+
+ xpcom_method!(new_string => NewString(value: *const nsACString) -> *const nsISFVString);
+ fn new_string(&self, value: &nsACString) -> Result<RefPtr<nsISFVString>, nsresult> {
+ Ok(RefPtr::new(SFVString::new(value).coerce::<nsISFVString>()))
+ }
+
+ xpcom_method!(new_token => NewToken(value: *const nsACString) -> *const nsISFVToken);
+ fn new_token(&self, value: &nsACString) -> Result<RefPtr<nsISFVToken>, nsresult> {
+ Ok(RefPtr::new(SFVToken::new(value).coerce::<nsISFVToken>()))
+ }
+
+ xpcom_method!(new_byte_sequence => NewByteSequence(value: *const nsACString) -> *const nsISFVByteSeq);
+ fn new_byte_sequence(&self, value: &nsACString) -> Result<RefPtr<nsISFVByteSeq>, nsresult> {
+ Ok(RefPtr::new(
+ SFVByteSeq::new(value).coerce::<nsISFVByteSeq>(),
+ ))
+ }
+
+ xpcom_method!(new_parameters => NewParameters() -> *const nsISFVParams);
+ fn new_parameters(&self) -> Result<RefPtr<nsISFVParams>, nsresult> {
+ Ok(RefPtr::new(SFVParams::new().coerce::<nsISFVParams>()))
+ }
+
+ xpcom_method!(new_item => NewItem(value: *const nsISFVBareItem, params: *const nsISFVParams) -> *const nsISFVItem);
+ fn new_item(
+ &self,
+ value: &nsISFVBareItem,
+ params: &nsISFVParams,
+ ) -> Result<RefPtr<nsISFVItem>, nsresult> {
+ Ok(RefPtr::new(
+ SFVItem::new(value, params).coerce::<nsISFVItem>(),
+ ))
+ }
+
+ xpcom_method!(new_inner_list => NewInnerList(items: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>, params: *const nsISFVParams) -> *const nsISFVInnerList);
+ fn new_inner_list(
+ &self,
+ items: &thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>,
+ params: &nsISFVParams,
+ ) -> Result<RefPtr<nsISFVInnerList>, nsresult> {
+ let items = items
+ .iter()
+ .cloned()
+ .map(|item| item.ok_or(NS_ERROR_NULL_POINTER))
+ .collect::<Result<Vec<_>, nsresult>>()?;
+ Ok(RefPtr::new(
+ SFVInnerList::new(items, params).coerce::<nsISFVInnerList>(),
+ ))
+ }
+
+ xpcom_method!(new_list => NewList(members: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>) -> *const nsISFVList);
+ fn new_list(
+ &self,
+ members: &thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>,
+ ) -> Result<RefPtr<nsISFVList>, nsresult> {
+ let members = members
+ .iter()
+ .cloned()
+ .map(|item| item.ok_or(NS_ERROR_NULL_POINTER))
+ .collect::<Result<Vec<_>, nsresult>>()?;
+ Ok(RefPtr::new(SFVList::new(members).coerce::<nsISFVList>()))
+ }
+
+ xpcom_method!(new_dictionary => NewDictionary() -> *const nsISFVDictionary);
+ fn new_dictionary(&self) -> Result<RefPtr<nsISFVDictionary>, nsresult> {
+ Ok(RefPtr::new(
+ SFVDictionary::new().coerce::<nsISFVDictionary>(),
+ ))
+ }
+}
+
+#[xpcom(implement(nsISFVInteger, nsISFVBareItem), nonatomic)]
+struct SFVInteger {
+ value: RefCell<i64>,
+}
+
+impl SFVInteger {
+ fn new(value: i64) -> RefPtr<SFVInteger> {
+ SFVInteger::allocate(InitSFVInteger {
+ value: RefCell::new(value),
+ })
+ }
+
+ xpcom_method!(get_value => GetValue() -> i64);
+ fn get_value(&self) -> Result<i64, nsresult> {
+ Ok(*self.value.borrow())
+ }
+
+ xpcom_method!(set_value => SetValue(value: i64));
+ fn set_value(&self, value: i64) -> Result<(), nsresult> {
+ self.value.replace(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::INTEGER)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVBool, nsISFVBareItem), nonatomic)]
+struct SFVBool {
+ value: RefCell<bool>,
+}
+
+impl SFVBool {
+ fn new(value: bool) -> RefPtr<SFVBool> {
+ SFVBool::allocate(InitSFVBool {
+ value: RefCell::new(value),
+ })
+ }
+
+ xpcom_method!(get_value => GetValue() -> bool);
+ fn get_value(&self) -> Result<bool, nsresult> {
+ Ok(*self.value.borrow())
+ }
+
+ xpcom_method!(set_value => SetValue(value: bool));
+ fn set_value(&self, value: bool) -> Result<(), nsresult> {
+ self.value.replace(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::BOOL)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVString, nsISFVBareItem), nonatomic)]
+struct SFVString {
+ value: RefCell<nsCString>,
+}
+
+impl SFVString {
+ fn new(value: &nsACString) -> RefPtr<SFVString> {
+ SFVString::allocate(InitSFVString {
+ value: RefCell::new(nsCString::from(value)),
+ })
+ }
+
+ xpcom_method!(
+ get_value => GetValue(
+ ) -> nsACString
+ );
+
+ fn get_value(&self) -> Result<nsCString, nsresult> {
+ Ok(self.value.borrow().clone())
+ }
+
+ xpcom_method!(
+ set_value => SetValue(value: *const nsACString)
+ );
+
+ fn set_value(&self, value: &nsACString) -> Result<(), nsresult> {
+ self.value.borrow_mut().assign(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::STRING)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVToken, nsISFVBareItem), nonatomic)]
+struct SFVToken {
+ value: RefCell<nsCString>,
+}
+
+impl SFVToken {
+ fn new(value: &nsACString) -> RefPtr<SFVToken> {
+ SFVToken::allocate(InitSFVToken {
+ value: RefCell::new(nsCString::from(value)),
+ })
+ }
+
+ xpcom_method!(
+ get_value => GetValue(
+ ) -> nsACString
+ );
+
+ fn get_value(&self) -> Result<nsCString, nsresult> {
+ Ok(self.value.borrow().clone())
+ }
+
+ xpcom_method!(
+ set_value => SetValue(value: *const nsACString)
+ );
+
+ fn set_value(&self, value: &nsACString) -> Result<(), nsresult> {
+ self.value.borrow_mut().assign(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::TOKEN)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVByteSeq, nsISFVBareItem), nonatomic)]
+struct SFVByteSeq {
+ value: RefCell<nsCString>,
+}
+
+impl SFVByteSeq {
+ fn new(value: &nsACString) -> RefPtr<SFVByteSeq> {
+ SFVByteSeq::allocate(InitSFVByteSeq {
+ value: RefCell::new(nsCString::from(value)),
+ })
+ }
+
+ xpcom_method!(
+ get_value => GetValue(
+ ) -> nsACString
+ );
+
+ fn get_value(&self) -> Result<nsCString, nsresult> {
+ Ok(self.value.borrow().clone())
+ }
+
+ xpcom_method!(
+ set_value => SetValue(value: *const nsACString)
+ );
+
+ fn set_value(&self, value: &nsACString) -> Result<(), nsresult> {
+ self.value.borrow_mut().assign(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::BYTE_SEQUENCE)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVDecimal, nsISFVBareItem), nonatomic)]
+struct SFVDecimal {
+ value: RefCell<f64>,
+}
+
+impl SFVDecimal {
+ fn new(value: f64) -> RefPtr<SFVDecimal> {
+ SFVDecimal::allocate(InitSFVDecimal {
+ value: RefCell::new(value),
+ })
+ }
+
+ xpcom_method!(
+ get_value => GetValue(
+ ) -> f64
+ );
+
+ fn get_value(&self) -> Result<f64, nsresult> {
+ Ok(*self.value.borrow())
+ }
+
+ xpcom_method!(
+ set_value => SetValue(value: f64)
+ );
+
+ fn set_value(&self, value: f64) -> Result<(), nsresult> {
+ self.value.replace(value);
+ Ok(())
+ }
+
+ xpcom_method!(get_type => GetType() -> i32);
+ fn get_type(&self) -> Result<i32, nsresult> {
+ Ok(nsISFVBareItem::DECIMAL)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVParams), nonatomic)]
+struct SFVParams {
+ params: RefCell<Parameters>,
+}
+
+impl SFVParams {
+ fn new() -> RefPtr<SFVParams> {
+ SFVParams::allocate(InitSFVParams {
+ params: RefCell::new(Parameters::new()),
+ })
+ }
+
+ xpcom_method!(
+ get => Get(key: *const nsACString) -> *const nsISFVBareItem
+ );
+
+ fn get(&self, key: &nsACString) -> Result<RefPtr<nsISFVBareItem>, nsresult> {
+ let key = key.to_utf8();
+ let params = self.params.borrow();
+ let param_val = params.get(key.as_ref());
+
+ match param_val {
+ Some(val) => interface_from_bare_item(val),
+ None => return Err(NS_ERROR_UNEXPECTED),
+ }
+ }
+
+ xpcom_method!(
+ set => Set(key: *const nsACString, item: *const nsISFVBareItem)
+ );
+
+ fn set(&self, key: &nsACString, item: &nsISFVBareItem) -> Result<(), nsresult> {
+ let key = key.to_utf8().into_owned();
+ let bare_item = bare_item_from_interface(item)?;
+ self.params.borrow_mut().insert(key, bare_item);
+ Ok(())
+ }
+
+ xpcom_method!(
+ delete => Delete(key: *const nsACString)
+ );
+ fn delete(&self, key: &nsACString) -> Result<(), nsresult> {
+ let key = key.to_utf8();
+ let mut params = self.params.borrow_mut();
+
+ if !params.contains_key(key.as_ref()) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ // Keeps only entries that don't match key
+ params.retain(|k, _| k != key.as_ref());
+ Ok(())
+ }
+
+ xpcom_method!(
+ keys => Keys() -> thin_vec::ThinVec<nsCString>
+ );
+ fn keys(&self) -> Result<thin_vec::ThinVec<nsCString>, nsresult> {
+ let keys = self.params.borrow();
+ let keys = keys
+ .keys()
+ .map(nsCString::from)
+ .collect::<ThinVec<nsCString>>();
+ Ok(keys)
+ }
+
+ fn from_interface(obj: &nsISFVParams) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVItem, nsISFVItemOrInnerList), nonatomic)]
+struct SFVItem {
+ value: RefPtr<nsISFVBareItem>,
+ params: RefPtr<nsISFVParams>,
+}
+
+impl SFVItem {
+ fn new(value: &nsISFVBareItem, params: &nsISFVParams) -> RefPtr<SFVItem> {
+ SFVItem::allocate(InitSFVItem {
+ value: RefPtr::new(value),
+ params: RefPtr::new(params),
+ })
+ }
+
+ xpcom_method!(
+ get_value => GetValue(
+ ) -> *const nsISFVBareItem
+ );
+
+ fn get_value(&self) -> Result<RefPtr<nsISFVBareItem>, nsresult> {
+ Ok(self.value.clone())
+ }
+
+ xpcom_method!(
+ get_params => GetParams(
+ ) -> *const nsISFVParams
+ );
+ fn get_params(&self) -> Result<RefPtr<nsISFVParams>, nsresult> {
+ Ok(self.params.clone())
+ }
+
+ xpcom_method!(
+ serialize => Serialize() -> nsACString
+ );
+ fn serialize(&self) -> Result<nsCString, nsresult> {
+ let bare_item = bare_item_from_interface(self.value.deref())?;
+ let params = params_from_interface(self.params.deref())?;
+ let serialized = Item::with_params(bare_item, params)
+ .serialize_value()
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(nsCString::from(serialized))
+ }
+
+ fn from_interface(obj: &nsISFVItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVInnerList, nsISFVItemOrInnerList), nonatomic)]
+struct SFVInnerList {
+ items: RefCell<Vec<RefPtr<nsISFVItem>>>,
+ params: RefPtr<nsISFVParams>,
+}
+
+impl SFVInnerList {
+ fn new(items: Vec<RefPtr<nsISFVItem>>, params: &nsISFVParams) -> RefPtr<SFVInnerList> {
+ SFVInnerList::allocate(InitSFVInnerList {
+ items: RefCell::new(items),
+ params: RefPtr::new(params),
+ })
+ }
+
+ xpcom_method!(
+ get_items => GetItems() -> thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>
+ );
+
+ fn get_items(&self) -> Result<thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>, nsresult> {
+ let items = self.items.borrow().iter().cloned().map(Some).collect();
+ Ok(items)
+ }
+
+ #[allow(non_snake_case)]
+ unsafe fn SetItems(
+ &self,
+ value: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItem>>>,
+ ) -> nsresult {
+ if value.is_null() {
+ return NS_ERROR_NULL_POINTER;
+ }
+ match (*value)
+ .iter()
+ .map(|v| v.clone().ok_or(NS_ERROR_NULL_POINTER))
+ .collect::<Result<Vec<_>, nsresult>>()
+ {
+ Ok(value) => *self.items.borrow_mut() = value,
+ Err(rv) => return rv,
+ }
+ NS_OK
+ }
+
+ xpcom_method!(
+ get_params => GetParams(
+ ) -> *const nsISFVParams
+ );
+ fn get_params(&self) -> Result<RefPtr<nsISFVParams>, nsresult> {
+ Ok(self.params.clone())
+ }
+
+ fn from_interface(obj: &nsISFVInnerList) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[xpcom(implement(nsISFVList, nsISFVSerialize), nonatomic)]
+struct SFVList {
+ members: RefCell<Vec<RefPtr<nsISFVItemOrInnerList>>>,
+}
+
+impl SFVList {
+ fn new(members: Vec<RefPtr<nsISFVItemOrInnerList>>) -> RefPtr<SFVList> {
+ SFVList::allocate(InitSFVList {
+ members: RefCell::new(members),
+ })
+ }
+
+ xpcom_method!(
+ get_members => GetMembers() -> thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>
+ );
+
+ fn get_members(
+ &self,
+ ) -> Result<thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>, nsresult> {
+ Ok(self.members.borrow().iter().cloned().map(Some).collect())
+ }
+
+ #[allow(non_snake_case)]
+ unsafe fn SetMembers(
+ &self,
+ value: *const thin_vec::ThinVec<Option<RefPtr<nsISFVItemOrInnerList>>>,
+ ) -> nsresult {
+ if value.is_null() {
+ return NS_ERROR_NULL_POINTER;
+ }
+ match (*value)
+ .iter()
+ .map(|v| v.clone().ok_or(NS_ERROR_NULL_POINTER))
+ .collect::<Result<Vec<_>, nsresult>>()
+ {
+ Ok(value) => *self.members.borrow_mut() = value,
+ Err(rv) => return rv,
+ }
+ NS_OK
+ }
+
+ xpcom_method!(
+ parse_more => ParseMore(header: *const nsACString)
+ );
+ fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> {
+ // create List from SFVList to call parse_more on it
+ let mut list = List::new();
+ let members = self.members.borrow().clone();
+ for interface_entry in members.iter() {
+ let item_or_inner_list = list_entry_from_interface(interface_entry)?;
+ list.push(item_or_inner_list);
+ }
+
+ let _ = list.parse_more(&header).map_err(|_| NS_ERROR_FAILURE)?;
+
+ // replace SFVList's members with new_members
+ let mut new_members = Vec::new();
+ for item_or_inner_list in list.iter() {
+ new_members.push(interface_from_list_entry(item_or_inner_list)?)
+ }
+ self.members.replace(new_members);
+ Ok(())
+ }
+
+ xpcom_method!(
+ serialize => Serialize() -> nsACString
+ );
+ fn serialize(&self) -> Result<nsCString, nsresult> {
+ let mut list = List::new();
+ let members = self.members.borrow().clone();
+ for interface_entry in members.iter() {
+ let item_or_inner_list = list_entry_from_interface(interface_entry)?;
+ list.push(item_or_inner_list);
+ }
+
+ let serialized = list.serialize_value().map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(nsCString::from(serialized))
+ }
+}
+
+#[xpcom(implement(nsISFVDictionary, nsISFVSerialize), nonatomic)]
+struct SFVDictionary {
+ value: RefCell<Dictionary>,
+}
+
+impl SFVDictionary {
+ fn new() -> RefPtr<SFVDictionary> {
+ SFVDictionary::allocate(InitSFVDictionary {
+ value: RefCell::new(Dictionary::new()),
+ })
+ }
+
+ xpcom_method!(
+ get => Get(key: *const nsACString) -> *const nsISFVItemOrInnerList
+ );
+
+ fn get(&self, key: &nsACString) -> Result<RefPtr<nsISFVItemOrInnerList>, nsresult> {
+ let key = key.to_utf8();
+ let value = self.value.borrow();
+ let member_value = value.get(key.as_ref());
+
+ match member_value {
+ Some(member) => interface_from_list_entry(member),
+ None => return Err(NS_ERROR_UNEXPECTED),
+ }
+ }
+
+ xpcom_method!(
+ set => Set(key: *const nsACString, item: *const nsISFVItemOrInnerList)
+ );
+
+ fn set(&self, key: &nsACString, member_value: &nsISFVItemOrInnerList) -> Result<(), nsresult> {
+ let key = key.to_utf8().into_owned();
+ let value = list_entry_from_interface(member_value)?;
+ self.value.borrow_mut().insert(key, value);
+ Ok(())
+ }
+
+ xpcom_method!(
+ delete => Delete(key: *const nsACString)
+ );
+
+ fn delete(&self, key: &nsACString) -> Result<(), nsresult> {
+ let key = key.to_utf8();
+ let mut params = self.value.borrow_mut();
+
+ if !params.contains_key(key.as_ref()) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ // Keeps only entries that don't match key
+ params.retain(|k, _| k != key.as_ref());
+ Ok(())
+ }
+
+ xpcom_method!(
+ keys => Keys() -> thin_vec::ThinVec<nsCString>
+ );
+ fn keys(&self) -> Result<thin_vec::ThinVec<nsCString>, nsresult> {
+ let members = self.value.borrow();
+ let keys = members
+ .keys()
+ .map(nsCString::from)
+ .collect::<ThinVec<nsCString>>();
+ Ok(keys)
+ }
+
+ xpcom_method!(
+ parse_more => ParseMore(header: *const nsACString)
+ );
+ fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> {
+ let _ = self
+ .value
+ .borrow_mut()
+ .parse_more(&header)
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(())
+ }
+
+ xpcom_method!(
+ serialize => Serialize() -> nsACString
+ );
+ fn serialize(&self) -> Result<nsCString, nsresult> {
+ let serialized = self
+ .value
+ .borrow()
+ .serialize_value()
+ .map_err(|_| NS_ERROR_FAILURE)?;
+ Ok(nsCString::from(serialized))
+ }
+}
+
+fn bare_item_from_interface(obj: &nsISFVBareItem) -> Result<BareItem, nsresult> {
+ let obj = obj
+ .query_interface::<nsISFVBareItem>()
+ .ok_or(NS_ERROR_UNEXPECTED)?;
+ let mut obj_type: i32 = -1;
+ unsafe {
+ obj.deref().GetType(&mut obj_type);
+ }
+
+ match obj_type {
+ nsISFVBareItem::BOOL => {
+ let item_value = SFVBool::from_bare_item_interface(obj.deref()).get_value()?;
+ Ok(BareItem::Boolean(item_value))
+ }
+ nsISFVBareItem::STRING => {
+ let string_itm = SFVString::from_bare_item_interface(obj.deref()).get_value()?;
+ let item_value = (*string_itm.to_utf8()).to_string();
+ Ok(BareItem::String(item_value))
+ }
+ nsISFVBareItem::TOKEN => {
+ let token_itm = SFVToken::from_bare_item_interface(obj.deref()).get_value()?;
+ let item_value = (*token_itm.to_utf8()).to_string();
+ Ok(BareItem::Token(item_value))
+ }
+ nsISFVBareItem::INTEGER => {
+ let item_value = SFVInteger::from_bare_item_interface(obj.deref()).get_value()?;
+ Ok(BareItem::Integer(item_value))
+ }
+ nsISFVBareItem::DECIMAL => {
+ let item_value = SFVDecimal::from_bare_item_interface(obj.deref()).get_value()?;
+ let decimal: Decimal = Decimal::from_f64(item_value).ok_or(NS_ERROR_UNEXPECTED)?;
+ Ok(BareItem::Decimal(decimal))
+ }
+ nsISFVBareItem::BYTE_SEQUENCE => {
+ let token_itm = SFVByteSeq::from_bare_item_interface(obj.deref()).get_value()?;
+ let item_value: String = (*token_itm.to_utf8()).to_string();
+ Ok(BareItem::ByteSeq(item_value.into_bytes()))
+ }
+ _ => return Err(NS_ERROR_UNEXPECTED),
+ }
+}
+
+fn params_from_interface(obj: &nsISFVParams) -> Result<Parameters, nsresult> {
+ let params = SFVParams::from_interface(obj).params.borrow();
+ Ok(params.clone())
+}
+
+fn item_from_interface(obj: &nsISFVItem) -> Result<Item, nsresult> {
+ let sfv_item = SFVItem::from_interface(obj);
+ let bare_item = bare_item_from_interface(sfv_item.value.deref())?;
+ let parameters = params_from_interface(sfv_item.params.deref())?;
+ Ok(Item::with_params(bare_item, parameters))
+}
+
+fn inner_list_from_interface(obj: &nsISFVInnerList) -> Result<InnerList, nsresult> {
+ let sfv_inner_list = SFVInnerList::from_interface(obj);
+
+ let mut inner_list_items: Vec<Item> = vec![];
+ for item in sfv_inner_list.items.borrow().iter() {
+ let item = item_from_interface(item)?;
+ inner_list_items.push(item);
+ }
+ let inner_list_params = params_from_interface(sfv_inner_list.params.deref())?;
+ Ok(InnerList::with_params(inner_list_items, inner_list_params))
+}
+
+fn list_entry_from_interface(obj: &nsISFVItemOrInnerList) -> Result<ListEntry, nsresult> {
+ if let Some(nsi_item) = obj.query_interface::<nsISFVItem>() {
+ let item = item_from_interface(nsi_item.deref())?;
+ Ok(ListEntry::Item(item))
+ } else if let Some(nsi_inner_list) = obj.query_interface::<nsISFVInnerList>() {
+ let inner_list = inner_list_from_interface(nsi_inner_list.deref())?;
+ Ok(ListEntry::InnerList(inner_list))
+ } else {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+}
+
+fn interface_from_bare_item(bare_item: &BareItem) -> Result<RefPtr<nsISFVBareItem>, nsresult> {
+ let bare_item = match bare_item {
+ BareItem::Boolean(val) => RefPtr::new(SFVBool::new(*val).coerce::<nsISFVBareItem>()),
+ BareItem::String(val) => {
+ RefPtr::new(SFVString::new(&nsCString::from(val)).coerce::<nsISFVBareItem>())
+ }
+ BareItem::Token(val) => {
+ RefPtr::new(SFVToken::new(&nsCString::from(val)).coerce::<nsISFVBareItem>())
+ }
+ BareItem::ByteSeq(val) => RefPtr::new(
+ SFVByteSeq::new(&nsCString::from(String::from_utf8(val.to_vec()).unwrap()))
+ .coerce::<nsISFVBareItem>(),
+ ),
+ BareItem::Decimal(val) => {
+ let val = val
+ .to_string()
+ .parse::<f64>()
+ .map_err(|_| NS_ERROR_UNEXPECTED)?;
+ RefPtr::new(SFVDecimal::new(val).coerce::<nsISFVBareItem>())
+ }
+ BareItem::Integer(val) => RefPtr::new(SFVInteger::new(*val).coerce::<nsISFVBareItem>()),
+ };
+
+ Ok(bare_item)
+}
+
+fn interface_from_item(item: &Item) -> Result<RefPtr<nsISFVItem>, nsresult> {
+ let nsi_bare_item = interface_from_bare_item(&item.bare_item)?;
+ let nsi_params = interface_from_params(&item.params)?;
+ Ok(RefPtr::new(
+ SFVItem::new(&nsi_bare_item, &nsi_params).coerce::<nsISFVItem>(),
+ ))
+}
+
+fn interface_from_params(params: &Parameters) -> Result<RefPtr<nsISFVParams>, nsresult> {
+ let sfv_params = SFVParams::new();
+ for (key, value) in params.iter() {
+ sfv_params
+ .params
+ .borrow_mut()
+ .insert(key.clone(), value.clone());
+ }
+ Ok(RefPtr::new(sfv_params.coerce::<nsISFVParams>()))
+}
+
+fn interface_from_list_entry(
+ member: &ListEntry,
+) -> Result<RefPtr<nsISFVItemOrInnerList>, nsresult> {
+ match member {
+ ListEntry::Item(item) => {
+ let nsi_bare_item = interface_from_bare_item(&item.bare_item)?;
+ let nsi_params = interface_from_params(&item.params)?;
+ Ok(RefPtr::new(
+ SFVItem::new(&nsi_bare_item, &nsi_params).coerce::<nsISFVItemOrInnerList>(),
+ ))
+ }
+ ListEntry::InnerList(inner_list) => {
+ let mut nsi_inner_list = Vec::new();
+ for item in inner_list.items.iter() {
+ let nsi_item = interface_from_item(item)?;
+ nsi_inner_list.push(nsi_item);
+ }
+
+ let nsi_params = interface_from_params(&inner_list.params)?;
+ Ok(RefPtr::new(
+ SFVInnerList::new(nsi_inner_list, &nsi_params).coerce::<nsISFVItemOrInnerList>(),
+ ))
+ }
+ }
+}
diff --git a/netwerk/base/makecppstring.py b/netwerk/base/makecppstring.py
new file mode 100644
index 0000000000..330fcfb04c
--- /dev/null
+++ b/netwerk/base/makecppstring.py
@@ -0,0 +1,17 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import sys
+
+
+def main(output, filename):
+ file = open(filename, "r")
+ output.write('R"(') # insert literal start
+ for line in file:
+ output.write(line)
+ output.write(')"') # insert literal end
+
+
+if __name__ == "__main__":
+ main(sys.stdout, sys.argv[1])
diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build
new file mode 100644
index 0000000000..e069a5f296
--- /dev/null
+++ b/netwerk/base/moz.build
@@ -0,0 +1,319 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "mozIThirdPartyUtil.idl",
+ "nsIArrayBufferInputStream.idl",
+ "nsIAsyncStreamCopier.idl",
+ "nsIAsyncStreamCopier2.idl",
+ "nsIAsyncVerifyRedirectCallback.idl",
+ "nsIAuthInformation.idl",
+ "nsIAuthModule.idl",
+ "nsIAuthPrompt.idl",
+ "nsIAuthPrompt2.idl",
+ "nsIAuthPromptAdapterFactory.idl",
+ "nsIAuthPromptCallback.idl",
+ "nsIAuthPromptProvider.idl",
+ "nsIBackgroundFileSaver.idl",
+ "nsIBufferedStreams.idl",
+ "nsIByteRangeRequest.idl",
+ "nsICacheInfoChannel.idl",
+ "nsICachingChannel.idl",
+ "nsICancelable.idl",
+ "nsICaptivePortalService.idl",
+ "nsIChannel.idl",
+ "nsIChannelEventSink.idl",
+ "nsIChildChannel.idl",
+ "nsIClassifiedChannel.idl",
+ "nsIClassOfService.idl",
+ "nsIContentSniffer.idl",
+ "nsIDashboard.idl",
+ "nsIDashboardEventNotifier.idl",
+ "nsIDHCPClient.idl",
+ "nsIDownloader.idl",
+ "nsIEncodedChannel.idl",
+ "nsIExternalProtocolHandler.idl",
+ "nsIFileStreams.idl",
+ "nsIFileURL.idl",
+ "nsIForcePendingChannel.idl",
+ "nsIFormPOSTActionChannel.idl",
+ "nsIHttpAuthenticatorCallback.idl",
+ "nsIHttpPushListener.idl",
+ "nsIIncrementalDownload.idl",
+ "nsIIncrementalStreamLoader.idl",
+ "nsIInputStreamChannel.idl",
+ "nsIInputStreamPump.idl",
+ "nsIInterceptionInfo.idl",
+ "nsIIOService.idl",
+ "nsILoadContextInfo.idl",
+ "nsILoadGroup.idl",
+ "nsILoadGroupChild.idl",
+ "nsILoadInfo.idl",
+ "nsIMIMEInputStream.idl",
+ "nsIMultiPartChannel.idl",
+ "nsINestedURI.idl",
+ "nsINetAddr.idl",
+ "nsINetUtil.idl",
+ "nsINetworkConnectivityService.idl",
+ "nsINetworkInfoService.idl",
+ "nsINetworkInterceptController.idl",
+ "nsINetworkLinkService.idl",
+ "nsINetworkPredictor.idl",
+ "nsINetworkPredictorVerifier.idl",
+ "nsINullChannel.idl",
+ "nsIParentChannel.idl",
+ "nsIParentRedirectingChannel.idl",
+ "nsIPermission.idl",
+ "nsIPermissionManager.idl",
+ "nsIPrivateBrowsingChannel.idl",
+ "nsIProgressEventSink.idl",
+ "nsIPrompt.idl",
+ "nsIProtocolHandler.idl",
+ "nsIProtocolProxyCallback.idl",
+ "nsIProtocolProxyFilter.idl",
+ "nsIProtocolProxyService.idl",
+ "nsIProtocolProxyService2.idl",
+ "nsIProxiedChannel.idl",
+ "nsIProxiedProtocolHandler.idl",
+ "nsIProxyInfo.idl",
+ "nsIRandomGenerator.idl",
+ "nsIRedirectChannelRegistrar.idl",
+ "nsIRedirectHistoryEntry.idl",
+ "nsIRedirectResultListener.idl",
+ "nsIRequest.idl",
+ "nsIRequestContext.idl",
+ "nsIRequestObserver.idl",
+ "nsIRequestObserverProxy.idl",
+ "nsIResumableChannel.idl",
+ "nsISecCheckWrapChannel.idl",
+ "nsISecureBrowserUI.idl",
+ "nsISensitiveInfoHiddenURI.idl",
+ "nsISerializationHelper.idl",
+ "nsIServerSocket.idl",
+ "nsISimpleStreamListener.idl",
+ "nsISimpleURIMutator.idl",
+ "nsISocketFilter.idl",
+ "nsISocketTransport.idl",
+ "nsISocketTransportService.idl",
+ "nsISpeculativeConnect.idl",
+ "nsIStandardURL.idl",
+ "nsIStreamListener.idl",
+ "nsIStreamListenerTee.idl",
+ "nsIStreamLoader.idl",
+ "nsIStreamTransportService.idl",
+ "nsISyncStreamListener.idl",
+ "nsISystemProxySettings.idl",
+ "nsIThreadRetargetableRequest.idl",
+ "nsIThreadRetargetableStreamListener.idl",
+ "nsIThrottledInputChannel.idl",
+ "nsITimedChannel.idl",
+ "nsITLSServerSocket.idl",
+ "nsITraceableChannel.idl",
+ "nsITransport.idl",
+ "nsIUDPSocket.idl",
+ "nsIUploadChannel.idl",
+ "nsIUploadChannel2.idl",
+ "nsIURI.idl",
+ "nsIURIMutator.idl",
+ "nsIURIWithSpecialOrigin.idl",
+ "nsIURL.idl",
+ "nsIURLParser.idl",
+ "nsPISocketTransportService.idl",
+]
+
+XPIDL_MODULE = "necko"
+
+EXPORTS += [
+ "netCore.h",
+ "nsASocketHandler.h",
+ "nsAsyncRedirectVerifyHelper.h",
+ "nsBaseChannel.h",
+ "nsFileStreams.h",
+ "nsInputStreamPump.h",
+ "nsMIMEInputStream.h",
+ "nsNetUtil.h",
+ "nsReadLine.h",
+ "nsSerializationHelper.h",
+ "nsSimpleNestedURI.h",
+ "nsSimpleURI.h",
+ "nsStandardURL.h",
+ "nsStreamListenerWrapper.h",
+ "nsURIHashKey.h",
+ "nsURLHelper.h",
+ "nsURLParsers.h",
+ "SimpleChannel.h",
+]
+
+EXPORTS.mozilla += [
+ "LoadContextInfo.h",
+ "LoadInfo.h",
+ "LoadTainting.h",
+ "nsRedirectHistoryEntry.h",
+]
+
+EXPORTS.mozilla.net += [
+ "CacheInfoIPCTypes.h",
+ "CaptivePortalService.h",
+ "Dashboard.h",
+ "DashboardTypes.h",
+ "DefaultURI.h",
+ "InterceptionInfo.h",
+ "IOActivityMonitor.h",
+ "MemoryDownloader.h",
+ "NetworkConnectivityService.h",
+ "Predictor.h",
+ "PrivateBrowsingChannel.h",
+ "ProtocolHandlerInfo.h",
+ "RedirectChannelRegistrar.h",
+ "RequestContextService.h",
+ "SimpleChannelParent.h",
+ "SSLTokensCache.h",
+ "ThrottleQueue.h",
+]
+
+UNIFIED_SOURCES += [
+ "ArrayBufferInputStream.cpp",
+ "BackgroundFileSaver.cpp",
+ "CaptivePortalService.cpp",
+ "Dashboard.cpp",
+ "DefaultURI.cpp",
+ "EventTokenBucket.cpp",
+ "InterceptionInfo.cpp",
+ "IOActivityMonitor.cpp",
+ "LoadContextInfo.cpp",
+ "LoadInfo.cpp",
+ "MemoryDownloader.cpp",
+ "NetworkConnectivityService.cpp",
+ "NetworkDataCountLayer.cpp",
+ "nsAsyncRedirectVerifyHelper.cpp",
+ "nsAsyncStreamCopier.cpp",
+ "nsAuthInformationHolder.cpp",
+ "nsBase64Encoder.cpp",
+ "nsBaseChannel.cpp",
+ "nsBaseContentStream.cpp",
+ "nsBufferedStreams.cpp",
+ "nsDirectoryIndexStream.cpp",
+ "nsDNSPrefetch.cpp",
+ "nsDownloader.cpp",
+ "nsFileStreams.cpp",
+ "nsIncrementalDownload.cpp",
+ "nsIncrementalStreamLoader.cpp",
+ "nsInputStreamChannel.cpp",
+ "nsInputStreamPump.cpp",
+ "nsIOService.cpp",
+ "nsIURIMutatorUtils.cpp",
+ "nsLoadGroup.cpp",
+ "nsMIMEInputStream.cpp",
+ "nsNetAddr.cpp",
+ "nsNetUtil.cpp",
+ "nsPACMan.cpp",
+ "nsPreloadedStream.cpp",
+ "nsProtocolProxyService.cpp",
+ "nsProxyInfo.cpp",
+ "nsRedirectHistoryEntry.cpp",
+ "nsRequestObserverProxy.cpp",
+ "nsSerializationHelper.cpp",
+ "nsServerSocket.cpp",
+ "nsSimpleNestedURI.cpp",
+ "nsSimpleStreamListener.cpp",
+ "nsSimpleURI.cpp",
+ "nsSocketTransport2.cpp",
+ "nsSocketTransportService2.cpp",
+ "nsStandardURL.cpp",
+ "nsStreamListenerTee.cpp",
+ "nsStreamListenerWrapper.cpp",
+ "nsStreamLoader.cpp",
+ "nsStreamTransportService.cpp",
+ "nsSyncStreamListener.cpp",
+ "nsTransportUtils.cpp",
+ "nsUDPSocket.cpp",
+ "PollableEvent.cpp",
+ "Predictor.cpp",
+ "ProtocolHandlerInfo.cpp",
+ "ProxyAutoConfig.cpp",
+ "RedirectChannelRegistrar.cpp",
+ "RequestContextService.cpp",
+ "SimpleBuffer.cpp",
+ "SimpleChannel.cpp",
+ "SimpleChannelParent.cpp",
+ "SSLTokensCache.cpp",
+ "ThrottleQueue.cpp",
+ "Tickler.cpp",
+ "TLSServerSocket.cpp",
+ "TRRLoadInfo.cpp",
+]
+
+if CONFIG["FUZZING"]:
+ SOURCES += [
+ "FuzzyLayer.cpp",
+ "FuzzySecurityInfo.cpp",
+ "FuzzySocketControl.cpp",
+ ]
+
+if CONFIG["FUZZING_INTERFACES"] and CONFIG["LIBFUZZER"]:
+ include("/tools/fuzzing/libfuzzer-flags.mozbuild")
+ SOURCES += [
+ "nsMediaFragmentURIParser.cpp",
+ "nsURLHelper.cpp",
+ "nsURLParsers.cpp",
+ ]
+ SOURCES["nsMediaFragmentURIParser.cpp"].flags += libfuzzer_flags
+ SOURCES["nsURLHelper.cpp"].flags += libfuzzer_flags
+ SOURCES["nsURLParsers.cpp"].flags += libfuzzer_flags
+else:
+ UNIFIED_SOURCES += [
+ "nsMediaFragmentURIParser.cpp",
+ "nsURLHelper.cpp",
+ "nsURLParsers.cpp",
+ ]
+
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ SOURCES += [
+ "nsURLHelperWin.cpp",
+ "ShutdownLayer.cpp",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += [
+ "nsURLHelperOSX.cpp",
+ ]
+else:
+ SOURCES += [
+ "nsURLHelperUnix.cpp",
+ ]
+
+EXTRA_JS_MODULES += [
+ "NetUtil.sys.mjs",
+]
+
+DIRS += ["mozurl", "rust-helper", "http-sfv"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "!/xpcom/components",
+ "/docshell/base",
+ "/dom/base",
+ "/js/xpconnect/src",
+ "/netwerk/dns",
+ "/netwerk/protocol/http",
+ "/netwerk/protocol/webtransport",
+ "/netwerk/socket",
+ "/netwerk/url-classifier",
+ "/security/manager/ssl",
+ "/xpcom/components",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ LOCAL_INCLUDES += [
+ "/xpcom/base",
+ ]
+
+GeneratedFile(
+ "ascii_pac_utils.inc", script="makecppstring.py", inputs=["ascii_pac_utils.js"]
+)
diff --git a/netwerk/base/mozIThirdPartyUtil.idl b/netwerk/base/mozIThirdPartyUtil.idl
new file mode 100644
index 0000000000..0c029942d8
--- /dev/null
+++ b/netwerk/base/mozIThirdPartyUtil.idl
@@ -0,0 +1,231 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface mozIDOMWindowProxy;
+interface nsIChannel;
+interface nsIPrincipal;
+interface nsILoadInfo;
+
+%{C++
+
+#include "mozilla/EnumSet.h"
+
+enum class ThirdPartyAnalysis {
+ IsForeign,
+ IsThirdPartyTrackingResource,
+ IsThirdPartySocialTrackingResource,
+ IsStorageAccessPermissionGranted,
+};
+
+using ThirdPartyAnalysisResult = mozilla::EnumSet<ThirdPartyAnalysis>;
+
+typedef bool (*RequireThirdPartyCheck)(nsILoadInfo*);
+
+%}
+
+native ThirdPartyAnalysisResult(ThirdPartyAnalysisResult);
+native RequireThirdPartyCheck(RequireThirdPartyCheck);
+
+/**
+ * Utility functions for determining whether a given URI, channel, or window
+ * hierarchy is third party with respect to a known URI.
+ */
+[scriptable, builtinclass, uuid(fd82700e-ffb4-4932-b7d6-08f0b5697dda)]
+interface mozIThirdPartyUtil : nsISupports
+{
+ /**
+ * isThirdPartyURI
+ *
+ * Determine whether two URIs are third party with respect to each other.
+ * This is determined by computing the base domain for both URIs. If they can
+ * be determined, and the base domains match, the request is defined as first
+ * party. If it cannot be determined because one or both URIs do not have a
+ * base domain (for instance, in the case of IP addresses, host aliases such
+ * as 'localhost', or a file:// URI), an exact string comparison on host is
+ * performed.
+ *
+ * For example, the URI "http://mail.google.com/" is not third party with
+ * respect to "http://images.google.com/", but "http://mail.yahoo.com/" and
+ * "http://192.168.1.1/" are.
+ *
+ * @return true if aFirstURI is third party with respect to aSecondURI.
+ *
+ * @throws if either URI is null, has a malformed host, or has an empty host
+ * and is not a file:// URI.
+ */
+ boolean isThirdPartyURI(in nsIURI aFirstURI, in nsIURI aSecondURI);
+
+ /**
+ * isThirdPartyWindow
+ *
+ * Determine whether the given window hierarchy is third party. This is done
+ * as follows:
+ *
+ * 1) Obtain the URI of the principal associated with 'aWindow'. Call this the
+ * 'bottom URI'.
+ * 2) If 'aURI' is provided, determine if it is third party with respect to
+ * the bottom URI. If so, return.
+ * 3) Find the same-type parent window, if there is one, and its URI.
+ * Determine whether it is third party with respect to the bottom URI. If
+ * so, return.
+ *
+ * Therefore, each level in the window hierarchy is tested. (This means that
+ * nested iframes with different base domains, even though the bottommost and
+ * topmost URIs might be equal, will be considered third party.)
+ *
+ * @param aWindow
+ * The bottommost window in the hierarchy.
+ * @param aURI
+ * A URI to test against. If null, the URI of the principal
+ * associated with 'aWindow' will be used.
+ *
+ * For example, if 'aURI' is "http://mail.google.com/", 'aWindow' has a URI
+ * of "http://google.com/", and its parent is the topmost content window with
+ * a URI of "http://mozilla.com", the result will be true.
+ *
+ * @return true if 'aURI' is third party with respect to any of the URIs
+ * associated with aWindow and its same-type parents.
+ *
+ * @throws if aWindow is null; the same-type parent of any window in the
+ * hierarchy cannot be determined; or the URI associated with any
+ * window in the hierarchy is null, has a malformed host, or has an
+ * empty host and is not a file:// URI.
+ *
+ * @see isThirdPartyURI
+ */
+ boolean isThirdPartyWindow(in mozIDOMWindowProxy aWindow, [optional] in nsIURI aURI);
+
+ /**
+ * isThirdPartyChannel
+ *
+ * Determine whether the given channel and its content window hierarchy is
+ * third party. This is done as follows:
+ *
+ * 1) If 'aChannel' is an nsIHttpChannel and has the
+ * 'forceAllowThirdPartyCookie' property set, then:
+ * a) If 'aURI' is null, return false.
+ * b) Otherwise, find the URI of the channel, determine whether it is
+ * foreign with respect to 'aURI', and return.
+ * 2) Find the URI of the channel and determine whether it is third party with
+ * respect to the URI of the channel. If so, return.
+ * 3) Obtain the bottommost nsIDOMWindow, and its same-type parent if it
+ * exists, from the channel's notification callbacks. Then:
+ * a) If the parent is the same as the bottommost window, and the channel
+ * has the LOAD_DOCUMENT_URI flag set, return false. This represents the
+ * case where a toplevel load is occurring and the window's URI has not
+ * yet been updated. (We have already checked that 'aURI' is not foreign
+ * with respect to the channel URI.)
+ * b) Otherwise, return the result of isThirdPartyWindow with arguments
+ * of the channel's bottommost window and the channel URI, respectively.
+ *
+ * Therefore, both the channel's URI and each level in the window hierarchy
+ * associated with the channel is tested.
+ *
+ * @param aChannel
+ * The channel associated with the load.
+ * @param aURI
+ * A URI to test against. If null, the URI of the channel will be used.
+ *
+ * For example, if 'aURI' is "http://mail.google.com/", 'aChannel' has a URI
+ * of "http://google.com/", and its parent is the topmost content window with
+ * a URI of "http://mozilla.com", the result will be true.
+ *
+ * @return true if aURI is third party with respect to the channel URI or any
+ * of the URIs associated with the same-type window hierarchy of the
+ * channel.
+ *
+ * @throws if 'aChannel' is null; the channel has no notification callbacks or
+ * an associated window; or isThirdPartyWindow throws.
+ *
+ * @see isThirdPartyWindow
+ */
+ boolean isThirdPartyChannel(in nsIChannel aChannel, [optional] in nsIURI aURI);
+
+ /**
+ * getBaseDomain
+ *
+ * Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
+ * "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
+ * dot may be present. If aHostURI is an IP address, an alias such as
+ * 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
+ * be the exact host. The result of this function should only be used in exact
+ * string comparisons, since substring comparisons will not be valid for the
+ * special cases elided above.
+ *
+ * @param aHostURI
+ * The URI to analyze.
+ *
+ * @return the base domain.
+ */
+ AUTF8String getBaseDomain(in nsIURI aHostURI);
+
+ /**
+ * NOTE: Long term, this method won't be needed once bug 922464 is fixed which
+ * will make it possible to parse all URI's off the main thread.
+ *
+ * getBaseDomainFromSchemeHost
+ *
+ * Get the base domain for aScheme and aHost. Otherwise identical to
+ * getBaseDomain().
+ *
+ * @param aScheme
+ * The scheme to analyze.
+ *
+ * @param aAsciiHost
+ * The host to analyze.
+ *
+ * @return the base domain.
+ */
+ AUTF8String getBaseDomainFromSchemeHost(in AUTF8String aScheme,
+ in AUTF8String aAsciiHost);
+
+ /**
+ * getURIFromWindow
+ *
+ * Returns the URI associated with the script object principal for the
+ * window.
+ */
+ nsIURI getURIFromWindow(in mozIDOMWindowProxy aWindow);
+
+ /**
+ * getPrincipalFromWindow
+ *
+ * Returns the script object principal for the window.
+ */
+ nsIPrincipal getPrincipalFromWindow(in mozIDOMWindowProxy aWindow);
+
+ /**
+ * getTopWindowForChannel
+ *
+ * Returns the top-level window associated with the given channel.
+ */
+ [noscript]
+ mozIDOMWindowProxy getTopWindowForChannel(in nsIChannel aChannel, [optional] in nsIURI aURIBeingLoaded);
+
+ /*
+ * Performs a full analysis of a channel.
+ *
+ * aChannel the input channel
+ * aNotify whether to send content blocking notifications if access control checks fail
+ * aURI optional URI to check for (the channel URI will be used instead if not provided)
+ * aRequireThirdPartyCheck a functor used to determine whether the load info requires third-party checks
+ */
+ [noscript, notxpcom]
+ ThirdPartyAnalysisResult analyzeChannel(in nsIChannel aChannel,
+ in boolean aNotify,
+ [optional] in nsIURI aURI,
+ [optional] in RequireThirdPartyCheck aRequireThirdPartyCheck,
+ out uint32_t aRejectedReason);
+};
+
+%{ C++
+/**
+ * The mozIThirdPartyUtil implementation is an XPCOM service registered
+ * under the ContractID:
+ */
+#define THIRDPARTYUTIL_CONTRACTID "@mozilla.org/thirdpartyutil;1"
+%}
diff --git a/netwerk/base/mozurl/.gitignore b/netwerk/base/mozurl/.gitignore
new file mode 100644
index 0000000000..4fffb2f89c
--- /dev/null
+++ b/netwerk/base/mozurl/.gitignore
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/netwerk/base/mozurl/Cargo.toml b/netwerk/base/mozurl/Cargo.toml
new file mode 100644
index 0000000000..1166bd2bfa
--- /dev/null
+++ b/netwerk/base/mozurl/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "mozurl"
+version = "0.0.1"
+authors = ["Nika Layzell <nika@thelayzells.com>"]
+license = "MPL-2.0"
+
+[dependencies]
+url = "2.4"
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+uuid = { version = "1.0", features = ["v4"] }
diff --git a/netwerk/base/mozurl/MozURL.cpp b/netwerk/base/mozurl/MozURL.cpp
new file mode 100644
index 0000000000..47a679164a
--- /dev/null
+++ b/netwerk/base/mozurl/MozURL.cpp
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/StaticPrefs_security.h"
+
+extern "C" {
+
+bool Gecko_StrictFileOriginPolicy() {
+ return mozilla::StaticPrefs::security_fileuri_strict_origin_policy();
+}
+}
diff --git a/netwerk/base/mozurl/MozURL.h b/netwerk/base/mozurl/MozURL.h
new file mode 100644
index 0000000000..60e8c189ae
--- /dev/null
+++ b/netwerk/base/mozurl/MozURL.h
@@ -0,0 +1,233 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozURL_h__
+#define mozURL_h__
+
+#include "mozilla/net/MozURL_ffi.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+
+namespace mozilla {
+namespace net {
+
+// This class provides a thread-safe, immutable URL parser.
+// As long as there is RefPtr to the object, you may use it on any thread.
+// The constructor is private. One can instantiate the object by
+// calling the Init() method as such:
+//
+// RefPtr<MozURL> url;
+// nsAutoCString href("http://example.com/path?query#ref");
+// nsresult rv = MozURL::Init(getter_AddRefs(url), href);
+// if (NS_SUCCEEDED(rv)) { /* use url */ }
+//
+// When changing the URL is needed, you need to call the Mutate() method.
+// This gives you a Mutator object, on which you can perform setter operations.
+// Calling Finalize() on the Mutator will result in a new MozURL and a status
+// code. If any of the setter operations failed, it will be reflected in the
+// status code, and a null MozURL.
+//
+// Note: In the case of a domain name containing non-ascii characters,
+// GetSpec and GetHostname will return the IDNA(punycode) version of the host.
+// Also note that for now, MozURL only supports the UTF-8 charset.
+//
+// Implementor Note: This type is only a holder for methods in C++, and does not
+// reflect the actual layout of the type.
+class MozURL final {
+ public:
+ static nsresult Init(MozURL** aURL, const nsACString& aSpec,
+ const MozURL* aBaseURL = nullptr) {
+ return mozurl_new(aURL, &aSpec, aBaseURL);
+ }
+
+ nsDependentCSubstring Spec() const { return mozurl_spec(this); }
+ nsDependentCSubstring Scheme() const { return mozurl_scheme(this); }
+ nsDependentCSubstring Username() const { return mozurl_username(this); }
+ nsDependentCSubstring Password() const { return mozurl_password(this); }
+ // Will return the hostname of URL. If the hostname is an IPv6 address,
+ // it will be enclosed in square brackets, such as `[::1]`
+ nsDependentCSubstring Host() const { return mozurl_host(this); }
+ // Will return the port number, if specified, or -1
+ int32_t Port() const { return mozurl_port(this); }
+ int32_t RealPort() const { return mozurl_real_port(this); }
+ // If the URL's port number is equal to the default port, will only return the
+ // hostname, otherwise it will return a string of the form `{host}:{port}`
+ // See: https://url.spec.whatwg.org/#default-port
+ nsDependentCSubstring HostPort() const { return mozurl_host_port(this); }
+ nsDependentCSubstring FilePath() const { return mozurl_filepath(this); }
+ nsDependentCSubstring Path() const { return mozurl_path(this); }
+ nsDependentCSubstring Query() const { return mozurl_query(this); }
+ bool HasQuery() const { return mozurl_has_query(this); }
+ nsDependentCSubstring Ref() const { return mozurl_fragment(this); }
+ bool HasFragment() const { return mozurl_has_fragment(this); }
+ nsDependentCSubstring Directory() const { return mozurl_directory(this); }
+ nsDependentCSubstring PrePath() const { return mozurl_prepath(this); }
+ nsDependentCSubstring SpecNoRef() const { return mozurl_spec_no_ref(this); }
+
+ // This matches the definition of origins and base domains in nsIPrincipal for
+ // almost all URIs (some rare file:// URIs don't match and it would be hard to
+ // fix them). It definitely matches nsIPrincipal for URIs used in quota
+ // manager and there are checks in quota manager and its clients that prevent
+ // different definitions (see QuotaManager::IsPrincipalInfoValid).
+ // See also TestMozURL.cpp which enumerates a huge pile of URIs and checks
+ // that origin and base domain definitions are in sync.
+ void Origin(nsACString& aOrigin) const { mozurl_origin(this, &aOrigin); }
+ nsresult BaseDomain(nsACString& aBaseDomain) const {
+ return mozurl_base_domain(this, &aBaseDomain);
+ }
+ bool CannotBeABase() { return mozurl_cannot_be_a_base(this); }
+
+ nsresult GetCommonBase(const MozURL* aOther, MozURL** aCommon) const {
+ return mozurl_common_base(this, aOther, aCommon);
+ }
+ nsresult GetRelative(const MozURL* aOther, nsACString* aRelative) const {
+ return mozurl_relative(this, aOther, aRelative);
+ }
+
+ size_t SizeOf() { return mozurl_sizeof(this); }
+
+ class Mutator {
+ public:
+ // Calling this method will result in the creation of a new MozURL that
+ // adopts the mutator's mURL.
+ // If any of the setters failed with an error code, that error code will be
+ // returned here. It will also return an error code if Finalize is called
+ // more than once on the Mutator.
+ nsresult Finalize(MozURL** aURL) {
+ nsresult rv = GetStatus();
+ if (NS_SUCCEEDED(rv)) {
+ mURL.forget(aURL);
+ } else {
+ *aURL = nullptr;
+ }
+ return rv;
+ }
+
+ // These setter methods will return a reference to `this` so that you may
+ // chain setter operations as such:
+ //
+ // RefPtr<MozURL> url2;
+ // nsresult rv = url->Mutate().SetHostname("newhost"_ns)
+ // .SetFilePath("new/file/path"_ns)
+ // .Finalize(getter_AddRefs(url2));
+ // if (NS_SUCCEEDED(rv)) { /* use url2 */ }
+ Mutator& SetScheme(const nsACString& aScheme) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_scheme(mURL, &aScheme);
+ }
+ return *this;
+ }
+ Mutator& SetUsername(const nsACString& aUser) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_username(mURL, &aUser);
+ }
+ return *this;
+ }
+ Mutator& SetPassword(const nsACString& aPassword) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_password(mURL, &aPassword);
+ }
+ return *this;
+ }
+ Mutator& SetHostname(const nsACString& aHost) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_hostname(mURL, &aHost);
+ }
+ return *this;
+ }
+ Mutator& SetHostPort(const nsACString& aHostPort) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_host_port(mURL, &aHostPort);
+ }
+ return *this;
+ }
+ Mutator& SetFilePath(const nsACString& aPath) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_pathname(mURL, &aPath);
+ }
+ return *this;
+ }
+ Mutator& SetQuery(const nsACString& aQuery) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_query(mURL, &aQuery);
+ }
+ return *this;
+ }
+ Mutator& SetRef(const nsACString& aRef) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_fragment(mURL, &aRef);
+ }
+ return *this;
+ }
+ Mutator& SetPort(int32_t aPort) {
+ if (NS_SUCCEEDED(GetStatus())) {
+ mStatus = mozurl_set_port_no(mURL, aPort);
+ }
+ return *this;
+ }
+
+ // This method returns the status code of the setter operations.
+ // If any of the setters failed, it will return the code of the first error
+ // that occured. If none of the setters failed, it will return NS_OK.
+ // This method is useful to avoid doing expensive operations when the result
+ // would not be used because an error occurred. For example:
+ //
+ // RefPtr<MozURL> url2;
+ // MozURL::Mutator mut = url->Mutate();
+ // mut.SetScheme("!@#$"); // this would fail
+ // if (NS_SUCCEDED(mut.GetStatus())) {
+ // nsAutoCString host(ExpensiveComputing());
+ // rv = mut.SetHostname(host).Finalize(getter_AddRefs(url2));
+ // }
+ // if (NS_SUCCEEDED(rv)) { /* use url2 */ }
+ nsresult GetStatus() { return mURL ? mStatus : NS_ERROR_NOT_AVAILABLE; }
+
+ static Result<Mutator, nsresult> FromSpec(
+ const nsACString& aSpec, const MozURL* aBaseURL = nullptr) {
+ Mutator m = Mutator(aSpec, aBaseURL);
+ if (m.mURL) {
+ MOZ_ASSERT(NS_SUCCEEDED(m.mStatus));
+ return m;
+ }
+
+ MOZ_ASSERT(NS_FAILED(m.mStatus));
+ return Err(m.mStatus);
+ }
+
+ private:
+ explicit Mutator(MozURL* aUrl) : mStatus(NS_OK) {
+ mozurl_clone(aUrl, getter_AddRefs(mURL));
+ }
+
+ // This is only used to create a mutator from a string without cloning it
+ // so we avoid a pointless copy in FromSpec. It is important that we
+ // check the value of mURL afterwards.
+ explicit Mutator(const nsACString& aSpec,
+ const MozURL* aBaseURL = nullptr) {
+ mStatus = mozurl_new(getter_AddRefs(mURL), &aSpec, aBaseURL);
+ }
+ RefPtr<MozURL> mURL;
+ nsresult mStatus;
+ friend class MozURL;
+ };
+
+ Mutator Mutate() { return Mutator(this); }
+
+ // AddRef and Release are non-virtual on this type, and always call into rust.
+ void AddRef() { mozurl_addref(this); }
+ void Release() { mozurl_release(this); }
+
+ private:
+ // Make it a compile time error for C++ code to ever create, destruct, or copy
+ // MozURL objects. All of these operations will be performed by rust.
+ MozURL(); /* never defined */
+ ~MozURL(); /* never defined */
+ MozURL(const MozURL&) = delete;
+ MozURL& operator=(const MozURL&) = delete;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozURL_h__
diff --git a/netwerk/base/mozurl/cbindgen.toml b/netwerk/base/mozurl/cbindgen.toml
new file mode 100644
index 0000000000..bb5fdbd08f
--- /dev/null
+++ b/netwerk/base/mozurl/cbindgen.toml
@@ -0,0 +1,61 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The MozURL type is implemented in Rust code, and uses extern "C" FFI calls to
+ * operate on the internal data structure. This file contains C declarations of
+ * these files.
+
+ * WARNING: DO NOT CALL ANY OF THESE FUNCTIONS. USE |MozURL| INSTEAD! */
+ """
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */
+
+namespace mozilla {
+namespace net {
+class MozURL;
+} // namespace net
+} // namespace mozilla
+
+extern "C" {
+
+// FFI-compatible string slice struct used internally by MozURL.
+// Coerces to nsDependentCSubstring.
+struct MozURLSpecSlice {
+ char* mData;
+ uint32_t mLen;
+
+ operator nsDependentCSubstring() {
+ return nsDependentCSubstring(mData, mLen);
+ }
+};
+
+nsresult mozurl_new(mozilla::net::MozURL** aResult, const nsACString* aSpec,
+ /* optional */ const mozilla::net::MozURL* aBase);
+
+void mozurl_clone(const mozilla::net::MozURL* aThis,
+ mozilla::net::MozURL** aResult);
+
+nsresult mozurl_common_base(const mozilla::net::MozURL* aUrl1,
+ const mozilla::net::MozURL* aUrl2,
+ mozilla::net::MozURL** aResult);
+}
+
+"""
+
+include_guard = "mozilla_net_MozURL_ffi_h"
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = []
+includes = ["nsError.h", "nsString.h"]
+
+[export]
+exclude = ["Gecko_StrictFileOriginPolicy", "MozURL", "SpecSlice", "mozurl_new", "mozurl_clone", "mozurl_common_base"]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+
+[export.rename]
+"SpecSlice" = "MozURLSpecSlice"
+"MozURL" = "mozilla::net::MozURL"
diff --git a/netwerk/base/mozurl/moz.build b/netwerk/base/mozurl/moz.build
new file mode 100644
index 0000000000..0a4e522b5a
--- /dev/null
+++ b/netwerk/base/mozurl/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ "MozURL.h",
+]
+
+SOURCES += [
+ "MozURL.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ CbindgenHeader("MozURL_ffi.h", inputs=["/netwerk/base/mozurl"])
+
+ EXPORTS.mozilla.net += [
+ "!MozURL_ffi.h",
+ ]
diff --git a/netwerk/base/mozurl/src/lib.rs b/netwerk/base/mozurl/src/lib.rs
new file mode 100644
index 0000000000..21be5f7e8b
--- /dev/null
+++ b/netwerk/base/mozurl/src/lib.rs
@@ -0,0 +1,574 @@
+/* -*- Mode: rust; rust-indent-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate url;
+use url::quirks;
+use url::{ParseOptions, Position, Url};
+
+extern crate nsstring;
+use nsstring::{nsACString, nsCString};
+
+extern crate nserror;
+use nserror::*;
+
+extern crate xpcom;
+use xpcom::interfaces::mozIThirdPartyUtil;
+use xpcom::{AtomicRefcnt, RefCounted, RefPtr};
+
+extern crate uuid;
+use uuid::Uuid;
+
+use std::fmt::Write;
+use std::marker::PhantomData;
+use std::mem;
+use std::ops;
+use std::ptr;
+use std::str;
+
+extern "C" {
+ fn Gecko_StrictFileOriginPolicy() -> bool;
+}
+
+/// Helper macro. If the expression $e is Ok(t) evaluates to t, otherwise,
+/// returns NS_ERROR_MALFORMED_URI.
+macro_rules! try_or_malformed {
+ ($e:expr) => {
+ match $e {
+ Ok(v) => v,
+ Err(_) => return NS_ERROR_MALFORMED_URI,
+ }
+ };
+}
+
+fn parser<'a>() -> ParseOptions<'a> {
+ Url::options()
+}
+
+fn default_port(scheme: &str) -> Option<u16> {
+ match scheme {
+ "ftp" => Some(21),
+ "gopher" => Some(70),
+ "http" => Some(80),
+ "https" => Some(443),
+ "ws" => Some(80),
+ "wss" => Some(443),
+ "rtsp" => Some(443),
+ "android" => Some(443),
+ _ => None,
+ }
+}
+
+/// A slice into the backing string. This type is only valid as long as the
+/// MozURL which it was pulled from is valid. In C++, this type implicitly
+/// converts to a nsDependentCString, and is an implementation detail.
+///
+/// This type exists because, unlike &str, this type is safe to return over FFI.
+#[repr(C)]
+pub struct SpecSlice<'a> {
+ data: *const u8,
+ len: u32,
+ _marker: PhantomData<&'a [u8]>,
+}
+
+impl<'a> From<&'a str> for SpecSlice<'a> {
+ fn from(s: &'a str) -> SpecSlice<'a> {
+ assert!(s.len() < u32::max_value() as usize);
+ SpecSlice {
+ data: s.as_ptr(),
+ len: s.len() as u32,
+ _marker: PhantomData,
+ }
+ }
+}
+
+/// The MozURL reference-counted threadsafe URL type. This type intentionally
+/// implements no XPCOM interfaces, and all method calls are non-virtual.
+#[repr(C)]
+pub struct MozURL {
+ pub url: Url,
+ refcnt: AtomicRefcnt,
+}
+
+impl MozURL {
+ pub fn from_url(url: Url) -> RefPtr<MozURL> {
+ // Actually allocate the URL on the heap. This is the only place we actually
+ // create a MozURL, other than in clone().
+ unsafe {
+ RefPtr::from_raw(Box::into_raw(Box::new(MozURL {
+ url: url,
+ refcnt: AtomicRefcnt::new(),
+ })))
+ .unwrap()
+ }
+ }
+}
+
+impl ops::Deref for MozURL {
+ type Target = Url;
+ fn deref(&self) -> &Url {
+ &self.url
+ }
+}
+impl ops::DerefMut for MozURL {
+ fn deref_mut(&mut self) -> &mut Url {
+ &mut self.url
+ }
+}
+
+// Memory Management for MozURL
+#[no_mangle]
+pub unsafe extern "C" fn mozurl_addref(url: &MozURL) {
+ url.refcnt.inc();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn mozurl_release(url: &MozURL) {
+ let rc = url.refcnt.dec();
+ if rc == 0 {
+ mem::drop(Box::from_raw(url as *const MozURL as *mut MozURL));
+ }
+}
+
+// xpcom::RefPtr support
+unsafe impl RefCounted for MozURL {
+ unsafe fn addref(&self) {
+ mozurl_addref(self);
+ }
+ unsafe fn release(&self) {
+ mozurl_release(self);
+ }
+}
+
+// Allocate a new MozURL object with a RefCnt of 1, and store a pointer to it
+// into url.
+#[no_mangle]
+pub extern "C" fn mozurl_new(
+ result: &mut *const MozURL,
+ spec: &nsACString,
+ base: Option<&MozURL>,
+) -> nsresult {
+ *result = ptr::null_mut();
+
+ let spec = try_or_malformed!(str::from_utf8(spec));
+ let url = if let Some(base) = base {
+ try_or_malformed!(base.url.join(spec))
+ } else {
+ try_or_malformed!(parser().parse(spec))
+ };
+
+ MozURL::from_url(url).forget(result);
+ NS_OK
+}
+
+/// Allocate a new MozURL object which is a clone of the original, and store a
+/// pointer to it into newurl.
+#[no_mangle]
+pub extern "C" fn mozurl_clone(url: &MozURL, newurl: &mut *const MozURL) {
+ MozURL::from_url(url.url.clone()).forget(newurl);
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_spec(url: &MozURL) -> SpecSlice {
+ url.as_ref().into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_scheme(url: &MozURL) -> SpecSlice {
+ url.scheme().into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_username(url: &MozURL) -> SpecSlice {
+ if url.cannot_be_a_base() {
+ "".into()
+ } else {
+ url.username().into()
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_password(url: &MozURL) -> SpecSlice {
+ url.password().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_host(url: &MozURL) -> SpecSlice {
+ url.host_str().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_port(url: &MozURL) -> i32 {
+ // NOTE: Gecko uses -1 to represent the default port.
+ url.port().map(|p| p as i32).unwrap_or(-1)
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_real_port(url: &MozURL) -> i32 {
+ url.port()
+ .or_else(|| default_port(url.scheme()))
+ .map(|p| p as i32)
+ .unwrap_or(-1)
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_host_port(url: &MozURL) -> SpecSlice {
+ (&url[Position::BeforeHost..Position::BeforePath]).into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_filepath(url: &MozURL) -> SpecSlice {
+ url.path().into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_path(url: &MozURL) -> SpecSlice {
+ (&url[Position::BeforePath..]).into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_query(url: &MozURL) -> SpecSlice {
+ url.query().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_fragment(url: &MozURL) -> SpecSlice {
+ url.fragment().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_spec_no_ref(url: &MozURL) -> SpecSlice {
+ (&url[..Position::AfterQuery]).into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_has_fragment(url: &MozURL) -> bool {
+ url.fragment().is_some()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_has_query(url: &MozURL) -> bool {
+ url.query().is_some()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_directory(url: &MozURL) -> SpecSlice {
+ if let Some(position) = url.path().rfind('/') {
+ url.path()[..position + 1].into()
+ } else {
+ url.path().into()
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_prepath(url: &MozURL) -> SpecSlice {
+ (&url[..Position::BeforePath]).into()
+}
+
+fn get_origin(url: &MozURL) -> Option<String> {
+ match url.scheme() {
+ "blob" | "ftp" | "http" | "https" | "ws" | "wss" => {
+ Some(url.origin().ascii_serialization())
+ }
+ "indexeddb" | "moz-extension" | "resource" => {
+ let host = url.host_str().unwrap_or("");
+
+ let port = url.port().or_else(|| default_port(url.scheme()));
+
+ if port == default_port(url.scheme()) {
+ Some(format!("{}://{}", url.scheme(), host))
+ } else {
+ Some(format!("{}://{}:{}", url.scheme(), host, port.unwrap()))
+ }
+ }
+ "file" => {
+ if unsafe { Gecko_StrictFileOriginPolicy() } {
+ Some(url[..Position::AfterPath].to_owned())
+ } else {
+ Some("file://UNIVERSAL_FILE_URI_ORIGIN".to_owned())
+ }
+ }
+ "about" | "moz-safe-about" => Some(url[..Position::AfterPath].to_owned()),
+ _ => None,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_origin(url: &MozURL, origin: &mut nsACString) {
+ let origin_str = if !url.as_ref().starts_with("about:blank") {
+ get_origin(url)
+ } else {
+ None
+ };
+
+ let origin_str = origin_str.unwrap_or_else(|| {
+ // nsIPrincipal stores the uuid, so the same uuid is returned everytime.
+ // We can't do that for MozURL because it can be used across threads.
+ // Storing uuid would mutate the object which would cause races between
+ // threads.
+ format!("moz-nullprincipal:{{{}}}", Uuid::new_v4())
+ });
+
+ // NOTE: Try to re-use the allocation we got from rust-url, and transfer
+ // ownership of the buffer to C++.
+ let mut o = nsCString::from(origin_str);
+ origin.take_from(&mut o);
+}
+
+fn get_base_domain(url: &MozURL) -> Result<Option<String>, nsresult> {
+ match url.scheme() {
+ "ftp" | "http" | "https" | "moz-extension" | "resource" => {
+ let third_party_util: RefPtr<mozIThirdPartyUtil> =
+ xpcom::components::ThirdPartyUtil::service()
+ .map_err(|_| NS_ERROR_ILLEGAL_DURING_SHUTDOWN)?;
+
+ let scheme = nsCString::from(url.scheme());
+
+ let mut host_str = url.host_str().unwrap_or("");
+
+ if host_str.starts_with('[') && host_str.ends_with(']') {
+ host_str = &host_str[1..host_str.len() - 1];
+ }
+
+ let host = nsCString::from(host_str);
+
+ unsafe {
+ let mut string = nsCString::new();
+ third_party_util
+ .GetBaseDomainFromSchemeHost(&*scheme, &*host, &mut *string)
+ .to_result()?;
+
+ // We know that GetBaseDomainFromSchemeHost returns AUTF8String, so just
+ // use unwrap().
+ Ok(Some(String::from_utf8(string.to_vec()).unwrap()))
+ }
+ }
+ "ws" | "wss" => Ok(Some(url.as_ref().to_owned())),
+ "file" => {
+ if unsafe { Gecko_StrictFileOriginPolicy() } {
+ Ok(Some(url.path().to_owned()))
+ } else {
+ Ok(Some("UNIVERSAL_FILE_URI_ORIGIN".to_owned()))
+ }
+ }
+ "about" | "moz-safe-about" | "indexeddb" => Ok(Some(url.as_ref().to_owned())),
+ _ => Ok(None),
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_base_domain(url: &MozURL, base_domain: &mut nsACString) -> nsresult {
+ let base_domain_str = if !url.as_ref().starts_with("about:blank") {
+ match get_base_domain(url) {
+ Ok(domain) => domain,
+ Err(rv) => return rv,
+ }
+ } else {
+ None
+ };
+
+ let base_domain_str = base_domain_str.unwrap_or_else(|| {
+ // See the comment in mozurl_origin about why we return a new uuid for
+ // "moz-nullprincipals".
+ format!("{{{}}}", Uuid::new_v4())
+ });
+
+ let mut bd = nsCString::from(base_domain_str);
+ base_domain.take_from(&mut bd);
+
+ NS_OK
+}
+
+// Helper macro for debug asserting that we're the only reference to MozURL.
+macro_rules! debug_assert_mut {
+ ($e:expr) => {
+ debug_assert_eq!($e.refcnt.get(), 1, "Cannot mutate an aliased MozURL!");
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_cannot_be_a_base(url: &mut MozURL) -> bool {
+ debug_assert_mut!(url);
+ url.cannot_be_a_base()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_scheme(url: &mut MozURL, scheme: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let scheme = try_or_malformed!(str::from_utf8(scheme));
+ try_or_malformed!(quirks::set_protocol(url, scheme));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_username(url: &mut MozURL, username: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let username = try_or_malformed!(str::from_utf8(username));
+ try_or_malformed!(quirks::set_username(url, username));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_password(url: &mut MozURL, password: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let password = try_or_malformed!(str::from_utf8(password));
+ try_or_malformed!(quirks::set_password(url, password));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_host_port(url: &mut MozURL, hostport: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let hostport = try_or_malformed!(str::from_utf8(hostport));
+ try_or_malformed!(quirks::set_host(url, hostport));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_hostname(url: &mut MozURL, host: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let host = try_or_malformed!(str::from_utf8(host));
+ try_or_malformed!(quirks::set_hostname(url, host));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_port_no(url: &mut MozURL, new_port: i32) -> nsresult {
+ debug_assert_mut!(url);
+ if url.cannot_be_a_base() {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ let port = match new_port {
+ new if new < 0 || u16::max_value() as i32 <= new => None,
+ new if Some(new as u16) == default_port(url.scheme()) => None,
+ new => Some(new as u16),
+ };
+ try_or_malformed!(url.set_port(port));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_pathname(url: &mut MozURL, path: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let path = try_or_malformed!(str::from_utf8(path));
+ quirks::set_pathname(url, path);
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_query(url: &mut MozURL, query: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let query = try_or_malformed!(str::from_utf8(query));
+ quirks::set_search(url, query);
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_fragment(url: &mut MozURL, fragment: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let fragment = try_or_malformed!(str::from_utf8(fragment));
+ quirks::set_hash(url, fragment);
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_sizeof(url: &MozURL) -> usize {
+ debug_assert_mut!(url);
+ mem::size_of::<MozURL>() + url.as_str().len()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_common_base(
+ url1: &MozURL,
+ url2: &MozURL,
+ result: &mut *const MozURL,
+) -> nsresult {
+ *result = ptr::null();
+ if url1.url == url2.url {
+ RefPtr::new(url1).forget(result);
+ return NS_OK;
+ }
+
+ if url1.scheme() != url2.scheme()
+ || url1.host() != url2.host()
+ || url1.username() != url2.username()
+ || url1.password() != url2.password()
+ || url1.port() != url2.port()
+ {
+ return NS_OK;
+ }
+
+ match (url1.path_segments(), url2.path_segments()) {
+ (Some(path1), Some(path2)) => {
+ // Take the shared prefix of path segments
+ let mut url = url1.url.clone();
+ if let Ok(mut segs) = url.path_segments_mut() {
+ segs.clear();
+ segs.extend(path1.zip(path2).take_while(|&(a, b)| a == b).map(|p| p.0));
+ } else {
+ return NS_OK;
+ }
+
+ MozURL::from_url(url).forget(result);
+ NS_OK
+ }
+ _ => NS_OK,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_relative(
+ url1: &MozURL,
+ url2: &MozURL,
+ result: &mut nsACString,
+) -> nsresult {
+ if url1.url == url2.url {
+ result.truncate();
+ return NS_OK;
+ }
+
+ if url1.scheme() != url2.scheme()
+ || url1.host() != url2.host()
+ || url1.username() != url2.username()
+ || url1.password() != url2.password()
+ || url1.port() != url2.port()
+ {
+ result.assign(url2.as_ref());
+ return NS_OK;
+ }
+
+ match (url1.path_segments(), url2.path_segments()) {
+ (Some(mut path1), Some(mut path2)) => {
+ // Exhaust the part of the iterators that match
+ while let (Some(ref p1), Some(ref p2)) = (path1.next(), path2.next()) {
+ if p1 != p2 {
+ break;
+ }
+ }
+
+ result.truncate();
+ for _ in path1 {
+ result.append("../");
+ }
+ for p2 in path2 {
+ result.append(p2);
+ result.append("/");
+ }
+ }
+ _ => {
+ result.assign(url2.as_ref());
+ }
+ }
+ NS_OK
+}
+
+/// This type is used by nsStandardURL
+#[no_mangle]
+pub extern "C" fn rusturl_parse_ipv6addr(input: &nsACString, addr: &mut nsACString) -> nsresult {
+ let ip6 = try_or_malformed!(str::from_utf8(input));
+ let host = try_or_malformed!(url::Host::parse(ip6));
+ let _ = write!(addr, "{}", host);
+ NS_OK
+}
diff --git a/netwerk/base/netCore.h b/netwerk/base/netCore.h
new file mode 100644
index 0000000000..258cc5cb58
--- /dev/null
+++ b/netwerk/base/netCore.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __netCore_h__
+#define __netCore_h__
+
+#include "nsError.h"
+
+// Where most necko status messages come from:
+#define NECKO_MSGS_URL "chrome://necko/locale/necko.properties"
+
+#endif // __netCore_h__
diff --git a/netwerk/base/nsASocketHandler.h b/netwerk/base/nsASocketHandler.h
new file mode 100644
index 0000000000..c68da7c359
--- /dev/null
+++ b/netwerk/base/nsASocketHandler.h
@@ -0,0 +1,102 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsASocketHandler_h__
+#define nsASocketHandler_h__
+
+#include "nsError.h"
+#include "nsINetAddr.h"
+#include "nsISupports.h"
+#include "prio.h"
+
+// socket handler used by nsISocketTransportService.
+// methods are only called on the socket thread.
+
+class nsASocketHandler : public nsISupports {
+ public:
+ nsASocketHandler() = default;
+
+ //
+ // this condition variable will be checked to determine if the socket
+ // handler should be detached. it must only be accessed on the socket
+ // thread.
+ //
+ nsresult mCondition{NS_OK};
+
+ //
+ // these flags can only be modified on the socket transport thread.
+ // the socket transport service will check these flags before calling
+ // PR_Poll.
+ //
+ uint16_t mPollFlags{0};
+
+ //
+ // this value specifies the maximum amount of time in seconds that may be
+ // spent waiting for activity on this socket. if this timeout is reached,
+ // then OnSocketReady will be called with outFlags = -1.
+ //
+ // the default value for this member is UINT16_MAX, which disables the
+ // timeout error checking. (i.e., a timeout value of UINT16_MAX is
+ // never reached.)
+ //
+ uint16_t mPollTimeout{UINT16_MAX};
+
+ bool mIsPrivate{false};
+
+ //
+ // called to service a socket
+ //
+ // params:
+ // socketRef - socket identifier
+ // fd - socket file descriptor
+ // outFlags - value of PR_PollDesc::out_flags after PR_Poll returns
+ // or -1 if a timeout occurred
+ //
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) = 0;
+
+ //
+ // called when a socket is no longer under the control of the socket
+ // transport service. the socket handler may close the socket at this
+ // point. after this call returns, the handler will no longer be owned
+ // by the socket transport service.
+ //
+ virtual void OnSocketDetached(PRFileDesc* fd) = 0;
+
+ //
+ // called to determine if the socket is for a local peer.
+ // when used for server sockets, indicates if it only accepts local
+ // connections.
+ //
+ virtual void IsLocal(bool* aIsLocal) = 0;
+
+ //
+ // called to determine if this socket should not be terminated when Gecko
+ // is turned offline. This is mostly useful for the debugging server
+ // socket.
+ //
+ virtual void KeepWhenOffline(bool* aKeepWhenOffline) {
+ *aKeepWhenOffline = false;
+ }
+
+ //
+ // called when global pref for keepalive has changed.
+ //
+ virtual void OnKeepaliveEnabledPrefChange(bool aEnabled) {}
+
+ //
+ // called to determine the NetAddr. addr can only be assumed to be initialized
+ // when NS_OK is returned
+ //
+ virtual nsresult GetRemoteAddr(mozilla::net::NetAddr* addr) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ //
+ // returns the number of bytes sent/transmitted over the socket
+ //
+ virtual uint64_t ByteCountSent() = 0;
+ virtual uint64_t ByteCountReceived() = 0;
+};
+
+#endif // !nsASocketHandler_h__
diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
new file mode 100644
index 0000000000..7a10d4d3f0
--- /dev/null
+++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+
+#include "nsIOService.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsILoadInfo.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gRedirectLog("nsRedirect");
+#undef LOG
+#define LOG(args) MOZ_LOG(gRedirectLog, LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper, nsIAsyncVerifyRedirectCallback,
+ nsIRunnable, nsINamed)
+
+class nsAsyncVerifyRedirectCallbackEvent : public Runnable {
+ public:
+ nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback* cb,
+ nsresult result)
+ : Runnable("nsAsyncVerifyRedirectCallbackEvent"),
+ mCallback(cb),
+ mResult(result) {}
+
+ NS_IMETHOD Run() override {
+ LOG(
+ ("nsAsyncVerifyRedirectCallbackEvent::Run() "
+ "callback to %p with result %" PRIx32,
+ mCallback.get(), static_cast<uint32_t>(mResult)));
+ (void)mCallback->OnRedirectVerifyCallback(mResult);
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback;
+ nsresult mResult;
+};
+
+nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() {
+ NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0,
+ "Did not receive all required callbacks!");
+}
+
+nsresult nsAsyncRedirectVerifyHelper::Init(
+ nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsIEventTarget* mainThreadEventTarget, bool synchronize) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::Init() "
+ "oldChan=%p newChan=%p",
+ oldChan, newChan));
+ mOldChan = oldChan;
+ mNewChan = newChan;
+ mFlags = flags;
+ mCallbackEventTarget = NS_IsMainThread() && mainThreadEventTarget
+ ? mainThreadEventTarget
+ : GetCurrentSerialEventTarget();
+
+ if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE))) {
+ nsCOMPtr<nsILoadInfo> loadInfo = oldChan->LoadInfo();
+ if (loadInfo->GetDontFollowRedirects()) {
+ ExplicitCallback(NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+ }
+
+ if (synchronize) mWaitingForRedirectCallback = true;
+
+ nsCOMPtr<nsIRunnable> runnable = this;
+ nsresult rv;
+ rv = mainThreadEventTarget
+ ? mainThreadEventTarget->Dispatch(runnable.forget())
+ : GetMainThreadSerialEventTarget()->Dispatch(runnable.forget());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (synchronize) {
+ if (!SpinEventLoopUntil("nsAsyncRedirectVerifyHelper::Init"_ns,
+ [&]() { return !mWaitingForRedirectCallback; })) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() "
+ "result=%" PRIx32 " expectedCBs=%u mResult=%" PRIx32,
+ static_cast<uint32_t>(result), mExpectedCallbacks,
+ static_cast<uint32_t>(mResult)));
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ mExpectedCallbacks > 0,
+ "OnRedirectVerifyCallback called more times than expected");
+ if (mExpectedCallbacks <= 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ --mExpectedCallbacks;
+
+ // If response indicates failure we may call back immediately
+ if (NS_FAILED(result)) {
+ // We chose to store the first failure-value (as opposed to the last)
+ if (NS_SUCCEEDED(mResult)) mResult = result;
+
+ // If InitCallback() has been called, just invoke the callback and
+ // return. Otherwise it will be invoked from InitCallback()
+ if (mCallbackInitiated) {
+ ExplicitCallback(mResult);
+ return NS_OK;
+ }
+ }
+
+ // If the expected-counter is in balance and InitCallback() was called, all
+ // sinks have agreed that the redirect is ok and we can invoke our callback
+ if (mCallbackInitiated && mExpectedCallbacks == 0) {
+ ExplicitCallback(mResult);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(
+ nsIChannelEventSink* sink, nsIChannel* oldChannel, nsIChannel* newChannel,
+ uint32_t flags) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() "
+ "sink=%p expectedCBs=%u mResult=%" PRIx32,
+ sink, mExpectedCallbacks, static_cast<uint32_t>(mResult)));
+
+ ++mExpectedCallbacks;
+
+ if (IsOldChannelCanceled()) {
+ LOG(
+ (" old channel has been canceled, cancel the redirect by "
+ "emulating OnRedirectVerifyCallback..."));
+ (void)OnRedirectVerifyCallback(NS_BINDING_ABORTED);
+ return NS_BINDING_ABORTED;
+ }
+
+ nsresult rv =
+ sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
+
+ LOG((" result=%" PRIx32 " expectedCBs=%u", static_cast<uint32_t>(rv),
+ mExpectedCallbacks));
+
+ // If the sink returns failure from this call the redirect is vetoed. We
+ // emulate a callback from the sink in this case in order to perform all
+ // the necessary logic.
+ if (NS_FAILED(rv)) {
+ LOG((" emulating OnRedirectVerifyCallback..."));
+ (void)OnRedirectVerifyCallback(rv);
+ }
+
+ return rv; // Return the actual status since our caller may need it
+}
+
+void nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "result=%" PRIx32
+ " expectedCBs=%u mCallbackInitiated=%u mResult=%" PRIx32,
+ static_cast<uint32_t>(result), mExpectedCallbacks, mCallbackInitiated,
+ static_cast<uint32_t>(mResult)));
+
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> callback(
+ do_QueryInterface(mOldChan));
+
+ if (!callback || !mCallbackEventTarget) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "callback=%p mCallbackEventTarget=%p",
+ callback.get(), mCallbackEventTarget.get()));
+ return;
+ }
+
+ mCallbackInitiated = false; // reset to ensure only one callback
+ mWaitingForRedirectCallback = false;
+
+ // Now, dispatch the callback on the event-target which called Init()
+ nsCOMPtr<nsIRunnable> event =
+ new nsAsyncVerifyRedirectCallbackEvent(callback, result);
+ if (!event) {
+ NS_WARNING(
+ "nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "failed creating callback event!");
+ return;
+ }
+ nsresult rv = mCallbackEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "failed dispatching callback event!");
+ } else {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
+ "dispatched callback event=%p",
+ event.get()));
+ }
+}
+
+void nsAsyncRedirectVerifyHelper::InitCallback() {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::InitCallback() "
+ "expectedCBs=%d mResult=%" PRIx32,
+ mExpectedCallbacks, static_cast<uint32_t>(mResult)));
+
+ mCallbackInitiated = true;
+
+ // Invoke the callback if we are done
+ if (mExpectedCallbacks == 0) ExplicitCallback(mResult);
+}
+
+NS_IMETHODIMP
+nsAsyncRedirectVerifyHelper::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsAsyncRedirectVerifyHelper");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncRedirectVerifyHelper::Run() {
+ /* If the channel got canceled after it fired AsyncOnChannelRedirect
+ * and before we got here, mostly because docloader load has been canceled,
+ * we must completely ignore this notification and prevent any further
+ * notification.
+ */
+ if (IsOldChannelCanceled()) {
+ ExplicitCallback(NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+
+ // First, the global observer
+ NS_ASSERTION(gIOService, "Must have an IO service at this point");
+ LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService..."));
+ nsresult rv =
+ gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan, mFlags, this);
+ if (NS_FAILED(rv)) {
+ ExplicitCallback(rv);
+ return NS_OK;
+ }
+
+ // Now, the per-channel observers
+ nsCOMPtr<nsIChannelEventSink> sink;
+ NS_QueryNotificationCallbacks(mOldChan, sink);
+ if (sink) {
+ LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink..."));
+ rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags);
+ }
+
+ // All invocations to AsyncOnChannelRedirect has been done - call
+ // InitCallback() to flag this
+ InitCallback();
+ return NS_OK;
+}
+
+bool nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() {
+ if (!mOldChan) {
+ return false;
+ }
+ bool canceled;
+ nsresult rv = mOldChan->GetCanceled(&canceled);
+ return NS_SUCCEEDED(rv) && canceled;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.h b/netwerk/base/nsAsyncRedirectVerifyHelper.h
new file mode 100644
index 0000000000..3e1afa8c69
--- /dev/null
+++ b/netwerk/base/nsAsyncRedirectVerifyHelper.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAsyncRedirectVerifyHelper_h
+#define nsAsyncRedirectVerifyHelper_h
+
+#include "nsIRunnable.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsINamed.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+/**
+ * This class simplifies call of OnChannelRedirect of IOService and
+ * the sink bound with the channel being redirected while the result of
+ * redirect decision is returned through the callback.
+ */
+class nsAsyncRedirectVerifyHelper final
+ : public nsIRunnable,
+ public nsINamed,
+ public nsIAsyncVerifyRedirectCallback {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSINAMED
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+ public:
+ nsAsyncRedirectVerifyHelper() = default;
+
+ /*
+ * Calls AsyncOnChannelRedirect() on the given sink with the given
+ * channels and flags. Keeps track of number of async callbacks to expect.
+ */
+ nsresult DelegateOnChannelRedirect(nsIChannelEventSink* sink,
+ nsIChannel* oldChannel,
+ nsIChannel* newChannel, uint32_t flags);
+
+ /**
+ * Initialize and run the chain of AsyncOnChannelRedirect calls. OldChannel
+ * is QI'ed for nsIAsyncVerifyRedirectCallback. The result of the redirect
+ * decision is passed through this interface back to the oldChannel.
+ *
+ * @param oldChan
+ * channel being redirected, MUST implement
+ * nsIAsyncVerifyRedirectCallback
+ * @param newChan
+ * target of the redirect channel
+ * @param flags
+ * redirect flags
+ * @param mainThreadEventTarget
+ * a labeled event target for dispatching runnables
+ * @param synchronize
+ * set to TRUE if you want the Init method wait synchronously for
+ * all redirect callbacks
+ */
+ nsresult Init(nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsIEventTarget* mainThreadEventTarget,
+ bool synchronize = false);
+
+ protected:
+ nsCOMPtr<nsIChannel> mOldChan;
+ nsCOMPtr<nsIChannel> mNewChan;
+ uint32_t mFlags{0};
+ bool mWaitingForRedirectCallback{false};
+ nsCOMPtr<nsIEventTarget> mCallbackEventTarget;
+ bool mCallbackInitiated{false};
+ int32_t mExpectedCallbacks{0};
+ nsresult mResult{NS_OK}; // value passed to callback
+
+ void InitCallback();
+
+ /**
+ * Calls back to |oldChan| as described in Init()
+ */
+ void ExplicitCallback(nsresult result);
+
+ private:
+ ~nsAsyncRedirectVerifyHelper();
+
+ bool IsOldChannelCanceled();
+};
+
+/*
+ * Helper to make the call-stack handle some control-flow for us
+ */
+class nsAsyncRedirectAutoCallback {
+ public:
+ explicit nsAsyncRedirectAutoCallback(
+ nsIAsyncVerifyRedirectCallback* aCallback)
+ : mCallback(aCallback) {
+ mResult = NS_OK;
+ }
+ ~nsAsyncRedirectAutoCallback() {
+ if (mCallback) mCallback->OnRedirectVerifyCallback(mResult);
+ }
+ /*
+ * Call this is you want it to call back with a different result-code
+ */
+ void SetResult(nsresult aRes) { mResult = aRes; }
+ /*
+ * Call this is you want to avoid the callback
+ */
+ void DontCallback() { mCallback = nullptr; }
+
+ private:
+ nsIAsyncVerifyRedirectCallback* mCallback;
+ nsresult mResult;
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/base/nsAsyncStreamCopier.cpp b/netwerk/base/nsAsyncStreamCopier.cpp
new file mode 100644
index 0000000000..de8363d17d
--- /dev/null
+++ b/netwerk/base/nsAsyncStreamCopier.cpp
@@ -0,0 +1,405 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAsyncStreamCopier.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIOService.h"
+#include "nsIEventTarget.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIBufferedStreams.h"
+#include "nsIRequestObserver.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+#undef LOG
+//
+// MOZ_LOG=nsStreamCopier:5
+//
+static LazyLogModule gStreamCopierLog("nsStreamCopier");
+#define LOG(args) MOZ_LOG(gStreamCopierLog, mozilla::LogLevel::Debug, args)
+
+/**
+ * An event used to perform initialization off the main thread.
+ */
+class AsyncApplyBufferingPolicyEvent final : public Runnable {
+ public:
+ /**
+ * @param aCopier
+ * The nsAsyncStreamCopier requesting the information.
+ */
+ explicit AsyncApplyBufferingPolicyEvent(nsAsyncStreamCopier* aCopier)
+ : mozilla::Runnable("AsyncApplyBufferingPolicyEvent"),
+ mCopier(aCopier),
+ mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_IMETHOD Run() override {
+ nsresult rv = mCopier->ApplyBufferingPolicy();
+ if (NS_FAILED(rv)) {
+ mCopier->Cancel(rv);
+ return NS_OK;
+ }
+
+ rv = mTarget->Dispatch(
+ NewRunnableMethod("nsAsyncStreamCopier::AsyncCopyInternal", mCopier,
+ &nsAsyncStreamCopier::AsyncCopyInternal),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (NS_FAILED(rv)) {
+ mCopier->Cancel(rv);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsAsyncStreamCopier> mCopier;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+//-----------------------------------------------------------------------------
+
+nsAsyncStreamCopier::nsAsyncStreamCopier()
+ : mChunkSize(nsIOService::gDefaultSegmentSize) {
+ LOG(("Creating nsAsyncStreamCopier @%p\n", this));
+}
+
+nsAsyncStreamCopier::~nsAsyncStreamCopier() {
+ LOG(("Destroying nsAsyncStreamCopier @%p\n", this));
+}
+
+bool nsAsyncStreamCopier::IsComplete(nsresult* status) {
+ MutexAutoLock lock(mLock);
+ if (status) *status = mStatus;
+ return !mIsPending;
+}
+
+nsIRequest* nsAsyncStreamCopier::AsRequest() {
+ return static_cast<nsIRequest*>(static_cast<nsIAsyncStreamCopier*>(this));
+}
+
+void nsAsyncStreamCopier::Complete(nsresult status) {
+ LOG(("nsAsyncStreamCopier::Complete [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(status)));
+
+ nsCOMPtr<nsIRequestObserver> observer;
+ nsCOMPtr<nsISupports> ctx;
+ {
+ MutexAutoLock lock(mLock);
+ mCopierCtx = nullptr;
+
+ if (mIsPending) {
+ mIsPending = false;
+ mStatus = status;
+
+ // setup OnStopRequest callback and release references...
+ observer = mObserver;
+ mObserver = nullptr;
+ }
+ }
+
+ if (observer) {
+ LOG((" calling OnStopRequest [status=%" PRIx32 "]\n",
+ static_cast<uint32_t>(status)));
+ observer->OnStopRequest(AsRequest(), status);
+ }
+}
+
+void nsAsyncStreamCopier::OnAsyncCopyComplete(void* closure, nsresult status) {
+ // AddRef'd in AsyncCopy. Will be released at the end of the method.
+ RefPtr<nsAsyncStreamCopier> self = dont_AddRef((nsAsyncStreamCopier*)closure);
+ self->Complete(status);
+}
+
+//-----------------------------------------------------------------------------
+// nsISupports
+
+// We cannot use simply NS_IMPL_ISUPPORTSx as both
+// nsIAsyncStreamCopier and nsIAsyncStreamCopier2 implement nsIRequest
+
+NS_IMPL_ADDREF(nsAsyncStreamCopier)
+NS_IMPL_RELEASE(nsAsyncStreamCopier)
+NS_INTERFACE_TABLE_HEAD(nsAsyncStreamCopier)
+ NS_INTERFACE_TABLE_BEGIN
+ NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier)
+ NS_INTERFACE_TABLE_ENTRY(nsAsyncStreamCopier, nsIAsyncStreamCopier2)
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsIRequest,
+ nsIAsyncStreamCopier)
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsAsyncStreamCopier, nsISupports,
+ nsIAsyncStreamCopier)
+ NS_INTERFACE_TABLE_END
+NS_INTERFACE_TABLE_TAIL
+
+//-----------------------------------------------------------------------------
+// nsIRequest
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetName(nsACString& name) {
+ name.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::IsPending(bool* result) {
+ *result = !IsComplete();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetStatus(nsresult* status) {
+ IsComplete(status);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAsyncStreamCopier::SetCanceledReason(
+ const nsACString& aReason) {
+ return nsIAsyncStreamCopier::SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsAsyncStreamCopier::GetCanceledReason(nsACString& aReason) {
+ return nsIAsyncStreamCopier::GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsAsyncStreamCopier::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return nsIAsyncStreamCopier::CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Cancel(nsresult status) {
+ nsCOMPtr<nsISupports> copierCtx;
+ {
+ MutexAutoLock lock(mLock);
+ if (!mIsPending) {
+ return NS_OK;
+ }
+ copierCtx.swap(mCopierCtx);
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ NS_WARNING("cancel with non-failure status code");
+ status = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (copierCtx) NS_CancelAsyncCopy(copierCtx, status);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Suspend() {
+ MOZ_ASSERT_UNREACHABLE("nsAsyncStreamCopier::Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Resume() {
+ MOZ_ASSERT_UNREACHABLE("nsAsyncStreamCopier::Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ *aLoadFlags = LOAD_NORMAL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return nsIAsyncStreamCopier::GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return nsIAsyncStreamCopier::SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ *aLoadGroup = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
+
+// Can't be accessed by multiple threads yet
+nsresult nsAsyncStreamCopier::InitInternal(
+ nsIInputStream* source, nsIOutputStream* sink, nsIEventTarget* target,
+ uint32_t chunkSize, bool closeSource,
+ bool closeSink) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ NS_ASSERTION(!mSource && !mSink, "Init() called more than once");
+ if (chunkSize == 0) {
+ chunkSize = nsIOService::gDefaultSegmentSize;
+ }
+ mChunkSize = chunkSize;
+
+ mSource = source;
+ mSink = sink;
+ mCloseSource = closeSource;
+ mCloseSink = closeSink;
+
+ if (target) {
+ mTarget = target;
+ } else {
+ nsresult rv;
+ mTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncStreamCopier
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Init(nsIInputStream* source, nsIOutputStream* sink,
+ nsIEventTarget* target, bool sourceBuffered,
+ bool sinkBuffered, uint32_t chunkSize,
+ bool closeSource, bool closeSink) {
+ NS_ASSERTION(sourceBuffered || sinkBuffered,
+ "at least one stream must be buffered");
+ mMode = sourceBuffered ? NS_ASYNCCOPY_VIA_READSEGMENTS
+ : NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+
+ return InitInternal(source, sink, target, chunkSize, closeSource, closeSink);
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncStreamCopier2
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Init(nsIInputStream* source, nsIOutputStream* sink,
+ nsIEventTarget* target, uint32_t chunkSize,
+ bool closeSource, bool closeSink) {
+ mShouldSniffBuffering = true;
+
+ return InitInternal(source, sink, target, chunkSize, closeSource, closeSink);
+}
+
+/**
+ * Detect whether the input or the output stream is buffered,
+ * bufferize one of them if neither is buffered.
+ */
+nsresult nsAsyncStreamCopier::ApplyBufferingPolicy() {
+ // This function causes I/O, it must not be executed on the main
+ // thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (NS_OutputStreamIsBuffered(mSink)) {
+ // Sink is buffered, no need to perform additional buffering
+ mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+ return NS_OK;
+ }
+ if (NS_InputStreamIsBuffered(mSource)) {
+ // Source is buffered, no need to perform additional buffering
+ mMode = NS_ASYNCCOPY_VIA_READSEGMENTS;
+ return NS_OK;
+ }
+
+ // No buffering, let's buffer the sink
+ nsresult rv;
+ nsCOMPtr<nsIBufferedOutputStream> sink =
+ do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = sink->Init(mSink, mChunkSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+ mSink = sink;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Both nsIAsyncStreamCopier and nsIAsyncStreamCopier2
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver* observer, nsISupports* ctx) {
+ LOG(("nsAsyncStreamCopier::AsyncCopy [this=%p observer=%p]\n", this,
+ observer));
+
+ NS_ASSERTION(mSource && mSink, "not initialized");
+ nsresult rv;
+
+ if (observer) {
+ // build proxy for observer events
+ rv = NS_NewRequestObserverProxy(getter_AddRefs(mObserver), observer, ctx);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // from this point forward, AsyncCopy is going to return NS_OK. any errors
+ // will be reported via OnStopRequest.
+ {
+ MutexAutoLock lock(mLock);
+ mIsPending = true;
+ }
+
+ if (mObserver) {
+ rv = mObserver->OnStartRequest(AsRequest());
+ if (NS_FAILED(rv)) Cancel(rv);
+ }
+
+ if (!mShouldSniffBuffering) {
+ // No buffer sniffing required, let's proceed
+ AsyncCopyInternal();
+ return NS_OK;
+ }
+
+ if (NS_IsMainThread()) {
+ // Don't perform buffer sniffing on the main thread
+ nsCOMPtr<nsIRunnable> event = new AsyncApplyBufferingPolicyEvent(this);
+ rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ return NS_OK;
+ }
+
+ // We're not going to block the main thread, so let's sniff here
+ rv = ApplyBufferingPolicy();
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ AsyncCopyInternal();
+ return NS_OK;
+}
+
+// Launch async copy.
+// All errors are reported through the observer.
+void nsAsyncStreamCopier::AsyncCopyInternal() {
+ MOZ_ASSERT(mMode == NS_ASYNCCOPY_VIA_READSEGMENTS ||
+ mMode == NS_ASYNCCOPY_VIA_WRITESEGMENTS);
+
+ nsresult rv;
+ // We want to receive progress notifications; release happens in
+ // OnAsyncCopyComplete.
+ RefPtr<nsAsyncStreamCopier> self = this;
+ {
+ MutexAutoLock lock(mLock);
+ rv = NS_AsyncCopy(mSource, mSink, mTarget, mMode, mChunkSize,
+ OnAsyncCopyComplete, this, mCloseSource, mCloseSink,
+ getter_AddRefs(mCopierCtx));
+ }
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ return; // release self
+ }
+
+ Unused << self.forget(); // Will be released in OnAsyncCopyComplete
+}
diff --git a/netwerk/base/nsAsyncStreamCopier.h b/netwerk/base/nsAsyncStreamCopier.h
new file mode 100644
index 0000000000..120218c3c7
--- /dev/null
+++ b/netwerk/base/nsAsyncStreamCopier.h
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAsyncStreamCopier_h__
+#define nsAsyncStreamCopier_h__
+
+#include "nsIAsyncStreamCopier.h"
+#include "nsIAsyncStreamCopier2.h"
+#include "mozilla/Mutex.h"
+#include "nsStreamUtils.h"
+#include "nsCOMPtr.h"
+
+class nsIRequestObserver;
+
+//-----------------------------------------------------------------------------
+
+class nsAsyncStreamCopier final : public nsIAsyncStreamCopier,
+ nsIAsyncStreamCopier2 {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIASYNCSTREAMCOPIER
+
+ // nsIAsyncStreamCopier2
+ // We declare it by hand instead of NS_DECL_NSIASYNCSTREAMCOPIER2
+ // as nsIAsyncStreamCopier2 duplicates methods of nsIAsyncStreamCopier
+ NS_IMETHOD Init(nsIInputStream* aSource, nsIOutputStream* aSink,
+ nsIEventTarget* aTarget, uint32_t aChunkSize,
+ bool aCloseSource, bool aCloseSink) override;
+
+ nsAsyncStreamCopier();
+
+ //-------------------------------------------------------------------------
+ // these methods may be called on any thread
+
+ bool IsComplete(nsresult* status = nullptr);
+ void Complete(nsresult status);
+
+ private:
+ virtual ~nsAsyncStreamCopier();
+
+ nsresult InitInternal(nsIInputStream* source, nsIOutputStream* sink,
+ nsIEventTarget* target, uint32_t chunkSize,
+ bool closeSource, bool closeSink);
+
+ static void OnAsyncCopyComplete(void*, nsresult);
+
+ void AsyncCopyInternal();
+ nsresult ApplyBufferingPolicy();
+ nsIRequest* AsRequest();
+
+ nsCOMPtr<nsIInputStream> mSource;
+ nsCOMPtr<nsIOutputStream> mSink;
+
+ nsCOMPtr<nsIRequestObserver> mObserver;
+
+ nsCOMPtr<nsIEventTarget> mTarget;
+
+ nsCOMPtr<nsISupports> mCopierCtx MOZ_GUARDED_BY(mLock);
+
+ mozilla::Mutex mLock{"nsAsyncStreamCopier.mLock"};
+
+ nsAsyncCopyMode mMode{NS_ASYNCCOPY_VIA_READSEGMENTS};
+ uint32_t mChunkSize; // only modified in Init
+ nsresult mStatus MOZ_GUARDED_BY(mLock){NS_OK};
+ bool mIsPending MOZ_GUARDED_BY(mLock){false};
+ bool mCloseSource MOZ_GUARDED_BY(mLock){false};
+ bool mCloseSink MOZ_GUARDED_BY(mLock){false};
+ bool mShouldSniffBuffering{false}; // only modified in Init
+
+ friend class ProceedWithAsyncCopy;
+ friend class AsyncApplyBufferingPolicyEvent;
+};
+
+#endif // !nsAsyncStreamCopier_h__
diff --git a/netwerk/base/nsAuthInformationHolder.cpp b/netwerk/base/nsAuthInformationHolder.cpp
new file mode 100644
index 0000000000..11cf7626ce
--- /dev/null
+++ b/netwerk/base/nsAuthInformationHolder.cpp
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAuthInformationHolder.h"
+
+NS_IMPL_ISUPPORTS(nsAuthInformationHolder, nsIAuthInformation)
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetFlags(uint32_t* aFlags) {
+ *aFlags = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetRealm(nsAString& aRealm) {
+ aRealm = mRealm;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetAuthenticationScheme(nsACString& aScheme) {
+ aScheme = mAuthType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetUsername(nsAString& aUserName) {
+ aUserName = mUser;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetUsername(const nsAString& aUserName) {
+ if (!(mFlags & ONLY_PASSWORD)) mUser = aUserName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetPassword(nsAString& aPassword) {
+ aPassword = mPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetPassword(const nsAString& aPassword) {
+ mPassword = aPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::GetDomain(nsAString& aDomain) {
+ aDomain = mDomain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthInformationHolder::SetDomain(const nsAString& aDomain) {
+ if (mFlags & NEED_DOMAIN) mDomain = aDomain;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsAuthInformationHolder.h b/netwerk/base/nsAuthInformationHolder.h
new file mode 100644
index 0000000000..48054d9413
--- /dev/null
+++ b/netwerk/base/nsAuthInformationHolder.h
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSAUTHINFORMATIONHOLDER_H_
+#define NSAUTHINFORMATIONHOLDER_H_
+
+#include "nsIAuthInformation.h"
+#include "nsString.h"
+
+class nsAuthInformationHolder : public nsIAuthInformation {
+ protected:
+ virtual ~nsAuthInformationHolder() = default;
+
+ public:
+ // aAuthType must be ASCII
+ nsAuthInformationHolder(uint32_t aFlags, const nsString& aRealm,
+ const nsACString& aAuthType)
+ : mFlags(aFlags), mRealm(aRealm), mAuthType(aAuthType) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTHINFORMATION
+
+ const nsString& User() const { return mUser; }
+ const nsString& Password() const { return mPassword; }
+ const nsString& Domain() const { return mDomain; }
+
+ /**
+ * This method can be used to initialize the username when the
+ * ONLY_PASSWORD flag is set.
+ */
+ void SetUserInternal(const nsString& aUsername) { mUser = aUsername; }
+
+ private:
+ nsString mUser;
+ nsString mPassword;
+ nsString mDomain;
+
+ uint32_t mFlags;
+ nsString mRealm;
+ nsCString mAuthType;
+};
+
+#endif
diff --git a/netwerk/base/nsBase64Encoder.cpp b/netwerk/base/nsBase64Encoder.cpp
new file mode 100644
index 0000000000..621dd2c4ec
--- /dev/null
+++ b/netwerk/base/nsBase64Encoder.cpp
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBase64Encoder.h"
+
+#include "mozilla/Base64.h"
+
+NS_IMPL_ISUPPORTS(nsBase64Encoder, nsIOutputStream)
+
+NS_IMETHODIMP
+nsBase64Encoder::Close() { return NS_OK; }
+
+NS_IMETHODIMP
+nsBase64Encoder::Flush() { return NS_OK; }
+
+NS_IMETHODIMP
+nsBase64Encoder::StreamStatus() { return NS_OK; }
+
+NS_IMETHODIMP
+nsBase64Encoder::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ mData.Append(aBuf, aCount);
+ *_retval = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::WriteFrom(nsIInputStream* aStream, uint32_t aCount,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBase64Encoder::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+nsresult nsBase64Encoder::Finish(nsACString& result) {
+ nsresult rv = mozilla::Base64Encode(mData, result);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Free unneeded memory and allow reusing the object
+ mData.Truncate();
+ return NS_OK;
+}
diff --git a/netwerk/base/nsBase64Encoder.h b/netwerk/base/nsBase64Encoder.h
new file mode 100644
index 0000000000..b74d3c9b5a
--- /dev/null
+++ b/netwerk/base/nsBase64Encoder.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSBASE64ENCODER_H_
+#define NSBASE64ENCODER_H_
+
+#include "nsIOutputStream.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+/**
+ * A base64 encoder. Usage: Instantiate class, write to it using
+ * Write(), then call Finish() to get the base64-encoded data.
+ */
+class nsBase64Encoder final : public nsIOutputStream {
+ public:
+ nsBase64Encoder() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsresult Finish(nsACString& _result);
+
+ private:
+ ~nsBase64Encoder() = default;
+
+ /// The data written to this stream. nsCString can deal fine with
+ /// binary data.
+ nsCString mData;
+};
+
+#endif
diff --git a/netwerk/base/nsBaseChannel.cpp b/netwerk/base/nsBaseChannel.cpp
new file mode 100644
index 0000000000..df8aa23db4
--- /dev/null
+++ b/netwerk/base/nsBaseChannel.cpp
@@ -0,0 +1,1024 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 sts=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBaseChannel.h"
+#include "nsContentUtils.h"
+#include "nsURLHelper.h"
+#include "nsNetCID.h"
+#include "nsMimeTypes.h"
+#include "nsUnknownDecoder.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsMimeTypes.h"
+#include "nsICancelable.h"
+#include "nsIChannelEventSink.h"
+#include "nsIStreamConverterService.h"
+#include "nsChannelClassifier.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsProxyRelease.h"
+#include "nsXULAppAPI.h"
+#include "nsContentSecurityManager.h"
+#include "LoadInfo.h"
+#include "nsServiceManagerUtils.h"
+#include "nsRedirectHistoryEntry.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/BasePrincipal.h"
+
+using namespace mozilla;
+
+// This class is used to suspend a request across a function scope.
+class ScopedRequestSuspender {
+ public:
+ explicit ScopedRequestSuspender(nsIRequest* request) : mRequest(request) {
+ if (mRequest && NS_FAILED(mRequest->Suspend())) {
+ NS_WARNING("Couldn't suspend pump");
+ mRequest = nullptr;
+ }
+ }
+ ~ScopedRequestSuspender() {
+ if (mRequest) mRequest->Resume();
+ }
+
+ private:
+ nsIRequest* mRequest;
+};
+
+// Used to suspend data events from mRequest within a function scope. This is
+// usually needed when a function makes callbacks that could process events.
+#define SUSPEND_PUMP_FOR_SCOPE() \
+ ScopedRequestSuspender pump_suspender__(mRequest)
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel
+
+nsBaseChannel::nsBaseChannel() : NeckoTargetHolder(nullptr) {
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+}
+
+nsBaseChannel::~nsBaseChannel() {
+ NS_ReleaseOnMainThread("nsBaseChannel::mLoadInfo", mLoadInfo.forget());
+}
+
+nsresult nsBaseChannel::Redirect(nsIChannel* newChannel, uint32_t redirectFlags,
+ bool openNewChannel) {
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ // Transfer properties
+
+ newChannel->SetLoadGroup(mLoadGroup);
+ newChannel->SetNotificationCallbacks(mCallbacks);
+ newChannel->SetLoadFlags(mLoadFlags | LOAD_REPLACE);
+
+ // make a copy of the loadinfo, append to the redirectchain
+ // and set it on the new channel
+ nsSecurityFlags secFlags =
+ mLoadInfo->GetSecurityFlags() & ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ nsCOMPtr<nsILoadInfo> newLoadInfo =
+ static_cast<net::LoadInfo*>(mLoadInfo.get())
+ ->CloneWithNewSecFlags(secFlags);
+
+ bool isInternalRedirect =
+ (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+
+ newLoadInfo->AppendRedirectHistoryEntry(this, isInternalRedirect);
+
+ // Ensure the channel's loadInfo's result principal URI so that it's
+ // either non-null or updated to the redirect target URI.
+ // We must do this because in case the loadInfo's result principal URI
+ // is null, it would be taken from OriginalURI of the channel. But we
+ // overwrite it with the whole redirect chain first URI before opening
+ // the target channel, hence the information would be lost.
+ // If the protocol handler that created the channel wants to use
+ // the originalURI of the channel as the principal URI, it has left
+ // the result principal URI on the load info null.
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+
+ nsCOMPtr<nsILoadInfo> existingLoadInfo = newChannel->LoadInfo();
+ if (existingLoadInfo) {
+ existingLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+ }
+ if (!resultPrincipalURI) {
+ newChannel->GetOriginalURI(getter_AddRefs(resultPrincipalURI));
+ }
+
+ newLoadInfo->SetResultPrincipalURI(resultPrincipalURI);
+
+ newChannel->SetLoadInfo(newLoadInfo);
+
+ // Preserve the privacy bit if it has been overridden
+ if (mPrivateBrowsingOverriden) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
+ do_QueryInterface(newChannel);
+ if (newPBChannel) {
+ newPBChannel->SetPrivate(mPrivateBrowsing);
+ }
+ }
+
+ if (nsCOMPtr<nsIWritablePropertyBag> bag = ::do_QueryInterface(newChannel)) {
+ nsHashPropertyBag::CopyFrom(bag, static_cast<nsIPropertyBag2*>(this));
+ }
+
+ // Notify consumer, giving chance to cancel redirect.
+
+ auto redirectCallbackHelper = MakeRefPtr<net::nsAsyncRedirectVerifyHelper>();
+
+ bool checkRedirectSynchronously = !openNewChannel;
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+
+ mRedirectChannel = newChannel;
+ mRedirectFlags = redirectFlags;
+ mOpenRedirectChannel = openNewChannel;
+ nsresult rv = redirectCallbackHelper->Init(
+ this, newChannel, redirectFlags, target, checkRedirectSynchronously);
+ if (NS_FAILED(rv)) return rv;
+
+ if (checkRedirectSynchronously && NS_FAILED(mStatus)) return mStatus;
+
+ return NS_OK;
+}
+
+nsresult nsBaseChannel::ContinueRedirect() {
+ // Make sure to do this _after_ making all the OnChannelRedirect calls
+ mRedirectChannel->SetOriginalURI(OriginalURI());
+
+ // If we fail to open the new channel, then we want to leave this channel
+ // unaffected, so we defer tearing down our channel until we have succeeded
+ // with the redirect.
+
+ if (mOpenRedirectChannel) {
+ nsresult rv = NS_OK;
+ rv = mRedirectChannel->AsyncOpen(mListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mRedirectChannel = nullptr;
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+ ChannelDone();
+
+ return NS_OK;
+}
+
+bool nsBaseChannel::HasContentTypeHint() const {
+ NS_ASSERTION(!Pending(), "HasContentTypeHint called too late");
+ return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE);
+}
+
+nsresult nsBaseChannel::BeginPumpingData() {
+ nsresult rv;
+
+ rv = BeginAsyncRead(this, getter_AddRefs(mRequest),
+ getter_AddRefs(mCancelableAsyncRequest));
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(mRequest || mCancelableAsyncRequest,
+ "should have got a request or cancelable");
+ mPumpingData = true;
+ return NS_OK;
+ }
+ if (rv != NS_ERROR_NOT_IMPLEMENTED) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsCOMPtr<nsIChannel> channel;
+ rv = OpenContentStream(true, getter_AddRefs(stream), getter_AddRefs(channel));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(!stream || !channel, "Got both a channel and a stream?");
+
+ if (channel) {
+ nsCOMPtr<nsIRunnable> runnable = new RedirectRunnable(this, channel);
+ rv = Dispatch(runnable.forget());
+ if (NS_SUCCEEDED(rv)) mWaitingOnAsyncRedirect = true;
+ return rv;
+ }
+
+ // By assigning mPump, we flag this channel as pending (see Pending). It's
+ // important that the pending flag is set when we call into the stream (the
+ // call to AsyncRead results in the stream's AsyncWait method being called)
+ // and especially when we call into the loadgroup. Our caller takes care to
+ // release mPump if we return an error.
+
+ nsCOMPtr<nsISerialEventTarget> target = GetNeckoTarget();
+ rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, 0, 0, true,
+ target);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mPumpingData = true;
+ mRequest = mPump;
+ rv = mPump->AsyncRead(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<BlockingPromise> promise;
+ rv = ListenerBlockingPromise(getter_AddRefs(promise));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (promise) {
+ mPump->Suspend();
+
+ RefPtr<nsBaseChannel> self(this);
+
+ promise->Then(
+ target, __func__,
+ [self, this](nsresult rv) {
+ MOZ_ASSERT(mPump);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mPump->Resume();
+ },
+ [self, this](nsresult rv) {
+ MOZ_ASSERT(mPump);
+ MOZ_ASSERT(NS_FAILED(rv));
+ Cancel(rv);
+ mPump->Resume();
+ });
+ }
+
+ return NS_OK;
+}
+
+void nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel) {
+ NS_ASSERTION(!mPumpingData, "Shouldn't have gotten here");
+
+ nsresult rv = mStatus;
+ if (NS_SUCCEEDED(mStatus)) {
+ rv = Redirect(newChannel, nsIChannelEventSink::REDIRECT_TEMPORARY, true);
+ if (NS_SUCCEEDED(rv)) {
+ // OnRedirectVerifyCallback will be called asynchronously
+ return;
+ }
+ }
+
+ ContinueHandleAsyncRedirect(rv);
+}
+
+void nsBaseChannel::ContinueHandleAsyncRedirect(nsresult result) {
+ mWaitingOnAsyncRedirect = false;
+
+ if (NS_FAILED(result)) Cancel(result);
+
+ if (NS_FAILED(result) && mListener) {
+ // Notify our consumer ourselves
+ mListener->OnStartRequest(this);
+ mListener->OnStopRequest(this, mStatus);
+ ChannelDone();
+ }
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ CallbacksChanged();
+}
+
+void nsBaseChannel::ClassifyURI() {
+ // For channels created in the child process, delegate to the parent to
+ // classify URIs.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (NS_ShouldClassifyChannel(this)) {
+ auto classifier = MakeRefPtr<net::nsChannelClassifier>(this);
+ classifier->Start();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsISupports
+
+NS_IMPL_ADDREF(nsBaseChannel)
+NS_IMPL_RELEASE(nsBaseChannel)
+
+NS_INTERFACE_MAP_BEGIN(nsBaseChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
+NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIRequest
+
+NS_IMETHODIMP
+nsBaseChannel::GetName(nsACString& result) {
+ if (!mURI) {
+ result.Truncate();
+ return NS_OK;
+ }
+ return mURI->GetSpec(result);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::IsPending(bool* result) {
+ *result = Pending();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetStatus(nsresult* status) {
+ if (mRequest && NS_SUCCEEDED(mStatus)) {
+ mRequest->GetStatus(status);
+ } else {
+ *status = mStatus;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseChannel::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsBaseChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsBaseChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Cancel(nsresult status) {
+ // Ignore redundant cancelation
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = status;
+
+ if (mCancelableAsyncRequest) {
+ mCancelableAsyncRequest->Cancel(status);
+ }
+
+ if (mRequest) {
+ mRequest->Cancel(status);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Suspend() {
+ NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED);
+ return mRequest->Suspend();
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Resume() {
+ NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED);
+ return mRequest->Resume();
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ nsCOMPtr<nsILoadGroup> loadGroup(mLoadGroup);
+ loadGroup.forget(aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ CallbacksChanged();
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIChannel
+
+NS_IMETHODIMP
+nsBaseChannel::GetOriginalURI(nsIURI** aURI) {
+ RefPtr<nsIURI> uri = OriginalURI();
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetOriginalURI(nsIURI* aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetURI(nsIURI** aURI) {
+ nsCOMPtr<nsIURI> uri(mURI);
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetOwner(nsISupports** aOwner) {
+ nsCOMPtr<nsISupports> owner(mOwner);
+ owner.forget(aOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetOwner(nsISupports* aOwner) {
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ mLoadInfo = aLoadInfo;
+
+ // Need to update |mNeckoTarget| when load info has changed.
+ SetupNeckoTarget();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ nsCOMPtr<nsILoadInfo> loadInfo(mLoadInfo);
+ loadInfo.forget(aLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks(mCallbacks);
+ callbacks.forget(aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ if (!CanSetCallbacks(aCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aCallbacks;
+ CallbacksChanged();
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
+ *aSecurityInfo = do_AddRef(mSecurityInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentType(nsACString& aContentType) {
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentType(const nsACString& aContentType) {
+ // mContentCharset is unchanged if not parsed
+ bool dummy;
+ net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentCharset(nsACString& aContentCharset) {
+ aContentCharset = mContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentCharset(const nsACString& aContentCharset) {
+ mContentCharset = aContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ // preserve old behavior, fail unless explicitly set.
+ if (mContentDispositionHint == UINT32_MAX) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ mContentDispositionHint = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ if (!mContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aContentDispositionFilename = *mContentDispositionFilename;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ mContentDispositionFilename =
+ MakeUnique<nsString>(aContentDispositionFilename);
+
+ // For safety reasons ensure the filename doesn't contain null characters and
+ // replace them with underscores. We may later pass the extension to system
+ // MIME APIs that expect null terminated strings.
+ mContentDispositionFilename->ReplaceChar(char16_t(0), '_');
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetContentLength(int64_t* aContentLength) {
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::SetContentLength(int64_t aContentLength) {
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::Open(nsIInputStream** aStream) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
+
+ nsCOMPtr<nsIChannel> chan;
+ rv = OpenContentStream(false, aStream, getter_AddRefs(chan));
+ NS_ASSERTION(!chan || !*aStream, "Got both a channel and a stream?");
+ if (NS_SUCCEEDED(rv) && chan) {
+ rv = Redirect(chan, nsIChannelEventSink::REDIRECT_INTERNAL, false);
+ if (NS_FAILED(rv)) return rv;
+ rv = chan->Open(aStream);
+ } else if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ return NS_ImplementChannelOpen(this, aStream);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ mWasOpened = true;
+ ClassifyURI();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_FAILED(rv)) {
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ MOZ_ASSERT(
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()),
+ "security flags in loadInfo but doContentSecurityCheck() not called");
+
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+ NS_ENSURE_ARG(listener);
+
+ SetupNeckoTarget();
+
+ // Skip checking for chrome:// sub-resources.
+ nsAutoCString scheme;
+ mURI->GetScheme(scheme);
+ if (!scheme.EqualsLiteral("file")) {
+ NS_CompareLoadInfoAndLoadContext(this);
+ }
+
+ // Ensure that this is an allowed port before proceeding.
+ rv = NS_CheckPortSafety(mURI);
+ if (NS_FAILED(rv)) {
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(this);
+
+ // Store the listener and context early so that OpenContentStream and the
+ // stream's AsyncWait method (called by AsyncRead) can have access to them
+ // via the StreamListener methods. However, since
+ // this typically introduces a reference cycle between this and the listener,
+ // we need to be sure to break the reference if this method does not succeed.
+ mListener = listener;
+
+ // This method assigns mPump as a side-effect. We need to clear mPump if
+ // this method fails.
+ rv = BeginPumpingData();
+ if (NS_FAILED(rv)) {
+ mPump = nullptr;
+ mRequest = nullptr;
+ mPumpingData = false;
+ ChannelDone();
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ // At this point, we are going to return success no matter what.
+
+ mWasOpened = true;
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
+
+ ClassifyURI();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsITransportEventSink
+
+NS_IMETHODIMP
+nsBaseChannel::OnTransportStatus(nsITransport* transport, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ // In some cases, we may wish to suppress transport-layer status events.
+
+ if (!mPumpingData || NS_FAILED(mStatus)) {
+ return NS_OK;
+ }
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ // Lazily fetch mProgressSink
+ if (!mProgressSink) {
+ if (mQueriedProgressSink) {
+ return NS_OK;
+ }
+ GetCallback(mProgressSink);
+ mQueriedProgressSink = true;
+ if (!mProgressSink) {
+ return NS_OK;
+ }
+ }
+
+ if (!HasLoadFlag(LOAD_BACKGROUND)) {
+ nsAutoString statusArg;
+ if (GetStatusArg(status, statusArg)) {
+ mProgressSink->OnStatus(this, status, statusArg.get());
+ }
+ }
+
+ if (progress) {
+ mProgressSink->OnProgress(this, progress, progressMax);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsBaseChannel::GetInterface(const nsIID& iid, void** result) {
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, iid, result);
+ return *result ? NS_OK : NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIRequestObserver
+
+static void CallTypeSniffers(void* aClosure, const uint8_t* aData,
+ uint32_t aCount) {
+ nsIChannel* chan = static_cast<nsIChannel*>(aClosure);
+
+ nsAutoCString newType;
+ NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
+ if (!newType.IsEmpty()) {
+ chan->SetContentType(newType);
+ }
+}
+
+static void CallUnknownTypeSniffer(void* aClosure, const uint8_t* aData,
+ uint32_t aCount) {
+ nsIChannel* chan = static_cast<nsIChannel*>(aClosure);
+
+ RefPtr<nsUnknownDecoder> sniffer = new nsUnknownDecoder();
+
+ nsAutoCString detected;
+ nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected);
+ if (NS_SUCCEEDED(rv)) chan->SetContentType(detected);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnStartRequest(nsIRequest* request) {
+ MOZ_ASSERT_IF(mRequest, request == mRequest);
+ MOZ_ASSERT_IF(mCancelableAsyncRequest, !mRequest);
+
+ if (mPump) {
+ // If our content type is unknown, use the content type
+ // sniffer. If the sniffer is not available for some reason, then we just
+ // keep going as-is.
+ if (NS_SUCCEEDED(mStatus) &&
+ mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
+ mPump->PeekStream(CallUnknownTypeSniffer, static_cast<nsIChannel*>(this));
+ }
+
+ // Now, the general type sniffers. Skip this if we have none.
+ if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+ mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
+ }
+ }
+
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ if (mListener) { // null in case of redirect
+ return mListener->OnStartRequest(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnStopRequest(nsIRequest* request, nsresult status) {
+ // If both mStatus and status are failure codes, we keep mStatus as-is since
+ // that is consistent with our GetStatus and Cancel methods.
+ if (NS_SUCCEEDED(mStatus)) mStatus = status;
+
+ // Cause Pending to return false.
+ mPump = nullptr;
+ mRequest = nullptr;
+ mCancelableAsyncRequest = nullptr;
+ mPumpingData = false;
+
+ if (mListener) { // null in case of redirect
+ mListener->OnStopRequest(this, mStatus);
+ }
+ ChannelDone();
+
+ // No need to suspend pump in this scope since we will not be receiving
+ // any more events from it.
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ CallbacksChanged();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel::nsIStreamListener
+
+NS_IMETHODIMP
+nsBaseChannel::OnDataAvailable(nsIRequest* request, nsIInputStream* stream,
+ uint64_t offset, uint32_t count) {
+ SUSPEND_PUMP_FOR_SCOPE();
+
+ nsresult rv = mListener->OnDataAvailable(this, stream, offset, count);
+ if (mSynthProgressEvents && NS_SUCCEEDED(rv)) {
+ int64_t prog = offset + count;
+ if (NS_IsMainThread()) {
+ OnTransportStatus(nullptr, NS_NET_STATUS_READING, prog, mContentLength);
+ } else {
+ class OnTransportStatusAsyncEvent : public Runnable {
+ RefPtr<nsBaseChannel> mChannel;
+ int64_t mProgress;
+ int64_t mContentLength;
+
+ public:
+ OnTransportStatusAsyncEvent(nsBaseChannel* aChannel, int64_t aProgress,
+ int64_t aContentLength)
+ : Runnable("OnTransportStatusAsyncEvent"),
+ mChannel(aChannel),
+ mProgress(aProgress),
+ mContentLength(aContentLength) {}
+
+ NS_IMETHOD Run() override {
+ return mChannel->OnTransportStatus(nullptr, NS_NET_STATUS_READING,
+ mProgress, mContentLength);
+ }
+ };
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new OnTransportStatusAsyncEvent(this, prog, mContentLength);
+ Dispatch(runnable.forget());
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnRedirectVerifyCallback(nsresult result) {
+ if (NS_SUCCEEDED(result)) result = ContinueRedirect();
+
+ if (NS_FAILED(result) && !mWaitingOnAsyncRedirect) {
+ if (NS_SUCCEEDED(mStatus)) mStatus = result;
+ return NS_OK;
+ }
+
+ if (mWaitingOnAsyncRedirect) ContinueHandleAsyncRedirect(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseChannel::RetargetDeliveryTo(nsISerialEventTarget* aEventTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mRequest) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableRequest> req;
+ if (mAllowThreadRetargeting) {
+ req = do_QueryInterface(mRequest);
+ }
+
+ NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED);
+
+ return req->RetargetDeliveryTo(aEventTarget);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIThreadRetargetableRequest> req;
+ req = do_QueryInterface(mRequest);
+
+ NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED);
+ return req->GetDeliveryTarget(aEventTarget);
+}
+
+NS_IMETHODIMP
+nsBaseChannel::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mAllowThreadRetargeting) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mListener);
+ if (!listener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return listener->CheckListenerChain();
+}
+
+NS_IMETHODIMP
+nsBaseChannel::OnDataFinished(nsresult aStatus) {
+ if (!mListener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mAllowThreadRetargeting) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mListener);
+ if (listener) {
+ return listener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseChannel::GetCanceled(bool* aCanceled) {
+ *aCanceled = mCanceled;
+ return NS_OK;
+}
+
+void nsBaseChannel::SetupNeckoTarget() {
+ mNeckoTarget = GetMainThreadSerialEventTarget();
+}
+
+nsBaseChannel::ContentRange::ContentRange(const nsACString& aRangeHeader,
+ uint64_t aSize)
+ : mStart(0), mEnd(0), mSize(0) {
+ auto parsed = nsContentUtils::ParseSingleRangeRequest(aRangeHeader, true);
+ // https://fetch.spec.whatwg.org/#ref-for-simple-range-header-value%E2%91%A1
+ // If rangeValue is failure, then return a network error.
+ if (!parsed) {
+ return;
+ }
+
+ // Sanity check: ParseSingleRangeRequest should handle these two cases.
+ // If rangeEndValue and rangeStartValue are null, then return failure.
+ MOZ_ASSERT(parsed->Start().isSome() || parsed->End().isSome());
+ // If rangeStartValue and rangeEndValue are numbers, and rangeStartValue
+ // is greater than rangeEndValue, then return failure.
+ MOZ_ASSERT(parsed->Start().isNothing() || parsed->End().isNothing() ||
+ *parsed->Start() <= *parsed->End());
+
+ // https://fetch.spec.whatwg.org/#ref-for-simple-range-header-value%E2%91%A1
+ // If rangeStart is null:
+ if (parsed->Start().isNothing()) {
+ // Set rangeStart to fullLength − rangeEnd.
+ mStart = aSize - *parsed->End();
+
+ // Set rangeEnd to rangeStart + rangeEnd − 1.
+ mEnd = mStart + *parsed->End() - 1;
+
+ // Otherwise:
+ } else {
+ // If rangeStart is greater than or equal to fullLength, then return a
+ // network error.
+ if (*parsed->Start() >= aSize) {
+ return;
+ }
+ mStart = *parsed->Start();
+
+ // If rangeEnd is null or rangeEnd is greater than or equal to fullLength,
+ // then set rangeEnd to fullLength − 1.
+ if (parsed->End().isNothing() || *parsed->End() >= aSize) {
+ mEnd = aSize - 1;
+ } else {
+ mEnd = *parsed->End();
+ }
+ }
+ mSize = aSize;
+}
+
+void nsBaseChannel::ContentRange::AsHeader(nsACString& aOutString) const {
+ aOutString.Assign("bytes "_ns);
+ aOutString.AppendInt(mStart);
+ aOutString.AppendLiteral("-");
+ aOutString.AppendInt(mEnd);
+ aOutString.AppendLiteral("/");
+ aOutString.AppendInt(mSize);
+}
diff --git a/netwerk/base/nsBaseChannel.h b/netwerk/base/nsBaseChannel.h
new file mode 100644
index 0000000000..179a24bf45
--- /dev/null
+++ b/netwerk/base/nsBaseChannel.h
@@ -0,0 +1,348 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseChannel_h__
+#define nsBaseChannel_h__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/net/PrivateBrowsingChannel.h"
+#include "nsHashPropertyBag.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIProgressEventSink.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsITransport.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURI.h"
+#include "nsInputStreamPump.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+
+class nsIInputStream;
+class nsICancelable;
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel is designed to be subclassed. The subclass is responsible for
+// implementing the OpenContentStream method, which will be called by the
+// nsIChannel::AsyncOpen and nsIChannel::Open implementations.
+//
+// nsBaseChannel implements nsIInterfaceRequestor to provide a convenient way
+// for subclasses to query both the nsIChannel::notificationCallbacks and
+// nsILoadGroup::notificationCallbacks for supported interfaces.
+//
+// nsBaseChannel implements nsITransportEventSink to support progress & status
+// notifications generated by the transport layer.
+
+class nsBaseChannel
+ : public nsHashPropertyBag,
+ public nsIChannel,
+ public nsIThreadRetargetableRequest,
+ public nsIInterfaceRequestor,
+ public nsITransportEventSink,
+ public nsIAsyncVerifyRedirectCallback,
+ public mozilla::net::PrivateBrowsingChannel<nsBaseChannel>,
+ public mozilla::net::NeckoTargetHolder,
+ protected nsIThreadRetargetableStreamListener {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsBaseChannel();
+
+ protected:
+ // -----------------------------------------------
+ // Methods to be implemented by the derived class:
+
+ virtual ~nsBaseChannel();
+
+ using BlockingPromise = mozilla::MozPromise<nsresult, nsresult, true>;
+
+ private:
+ // Implemented by subclass to supply data stream. The parameter, async, is
+ // true when called from nsIChannel::AsyncOpen and false otherwise. When
+ // async is true, the resulting stream will be used with a nsIInputStreamPump
+ // instance. This means that if it is a non-blocking stream that supports
+ // nsIAsyncInputStream that it will be read entirely on the main application
+ // thread, and its AsyncWait method will be called whenever ReadSegments
+ // returns NS_BASE_STREAM_WOULD_BLOCK. Otherwise, if the stream is blocking,
+ // then it will be read on one of the background I/O threads, and it does not
+ // need to implement ReadSegments. If async is false, this method may return
+ // NS_ERROR_NOT_IMPLEMENTED to cause the basechannel to implement Open in
+ // terms of AsyncOpen (see NS_ImplementChannelOpen).
+ // A callee is allowed to return an nsIChannel instead of an nsIInputStream.
+ // That case will be treated as a redirect to the new channel. By default
+ // *channel will be set to null by the caller, so callees who don't want to
+ // return one an just not touch it.
+ virtual nsresult OpenContentStream(bool async, nsIInputStream** stream,
+ nsIChannel** channel) = 0;
+
+ // Implemented by subclass to begin pumping data for an async channel, in
+ // lieu of returning a stream. If implemented, OpenContentStream will never
+ // be called for async channels. If not implemented, AsyncOpen will fall
+ // back to OpenContentStream.
+ //
+ // On success, the callee must begin pumping data to the stream listener,
+ // and at some point call OnStartRequest followed by OnStopRequest.
+ //
+ // Additionally, when a successful nsresult is returned, then the subclass
+ // should be setting through its two out params either:
+ // - a request object, which may be used to suspend, resume, and cancel
+ // the underlying request.
+ // - or a cancelable object (e.g. when a request can't be returned right away
+ // due to some async work needed to retrieve it). which may be used to
+ // cancel the underlying request (e.g. because the channel has been
+ // canceled)
+ //
+ // Not returning a request or cancelable leads to potentially leaking the
+ // an underling stream pump (which would keep to be pumping data even after
+ // the channel has been canceled and nothing is going to handle the data
+ // available, e.g. see Bug 1706594).
+ virtual nsresult BeginAsyncRead(nsIStreamListener* listener,
+ nsIRequest** request,
+ nsICancelable** cancelableRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // This method may return a promise that will keep the input stream pump
+ // suspended until the promise is resolved or rejected. On resolution the
+ // pump is resumed. On rejection the channel is canceled with the resulting
+ // error and then the pump is also resumed to propagate the error to the
+ // channel listener. Use it to do any asynchronous/background tasks you need
+ // to finish prior calling OnStartRequest of the listener. This method is
+ // called right after OpenContentStream() with async == true, after the input
+ // stream pump has already been called asyncRead().
+ virtual nsresult ListenerBlockingPromise(BlockingPromise** aPromise) {
+ NS_ENSURE_ARG(aPromise);
+ *aPromise = nullptr;
+ return NS_OK;
+ }
+
+ // The basechannel calls this method from its OnTransportStatus method to
+ // determine whether to call nsIProgressEventSink::OnStatus in addition to
+ // nsIProgressEventSink::OnProgress. This method may be overriden by the
+ // subclass to enable nsIProgressEventSink::OnStatus events. If this method
+ // returns true, then the statusArg out param specifies the "statusArg" value
+ // to pass to the OnStatus method. By default, OnStatus messages are
+ // suppressed. The status parameter passed to this method is the status value
+ // from the OnTransportStatus method.
+ virtual bool GetStatusArg(nsresult status, nsString& statusArg) {
+ return false;
+ }
+
+ // Called when the callbacks available to this channel may have changed.
+ virtual void OnCallbacksChanged() {}
+
+ // Called when our channel is done, to allow subclasses to drop resources.
+ virtual void OnChannelDone() {}
+
+ public:
+ // ----------------------------------------------
+ // Methods provided for use by the derived class:
+
+ // Redirect to another channel. This method takes care of notifying
+ // observers of this redirect as well as of opening the new channel, if asked
+ // to do so. It also cancels |this| with the status code
+ // NS_BINDING_REDIRECTED. A failure return from this method means that the
+ // redirect could not be performed (no channel was opened; this channel
+ // wasn't canceled.) The redirectFlags parameter consists of the flag values
+ // defined on nsIChannelEventSink.
+ nsresult Redirect(nsIChannel* newChannel, uint32_t redirectFlags,
+ bool openNewChannel);
+
+ // Tests whether a type hint was set. Subclasses can use this to decide
+ // whether to call SetContentType.
+ // NOTE: This is only reliable if the subclass didn't itself call
+ // SetContentType, and should also not be called after OpenContentStream.
+ bool HasContentTypeHint() const;
+
+ // The URI member should be initialized before the channel is used, and then
+ // it should never be changed again until the channel is destroyed.
+ nsIURI* URI() { return mURI; }
+ void SetURI(nsIURI* uri) {
+ NS_ASSERTION(uri, "must specify a non-null URI");
+ NS_ASSERTION(!mURI, "must not modify URI");
+ NS_ASSERTION(!mOriginalURI, "how did that get set so early?");
+ mURI = uri;
+ mOriginalURI = uri;
+ }
+ nsIURI* OriginalURI() { return mOriginalURI; }
+
+ // The security info is a property of the transport-layer, which should be
+ // assigned by the subclass.
+ nsITransportSecurityInfo* SecurityInfo() { return mSecurityInfo; }
+ void SetSecurityInfo(nsITransportSecurityInfo* info) { mSecurityInfo = info; }
+
+ // Test the load flags
+ bool HasLoadFlag(uint32_t flag) { return (mLoadFlags & flag) != 0; }
+
+ // This is a short-cut to calling nsIRequest::IsPending()
+ virtual bool Pending() const {
+ return mPumpingData || mWaitingOnAsyncRedirect;
+ }
+
+ // Blob requests may specify a range header. We must parse, validate, and
+ // store that info in a place where BlobURLInputStream::StoreBlobImplStream
+ // can access it. This class helps to encapsulate that logic.
+ class ContentRange {
+ private:
+ uint64_t mStart;
+ uint64_t mEnd;
+ uint64_t mSize;
+
+ public:
+ uint64_t Start() const { return mStart; }
+ uint64_t End() const { return mEnd; }
+ uint64_t Size() const { return mSize; }
+ bool IsValid() const { return mStart < mSize; }
+ ContentRange() : mStart(0), mEnd(0), mSize(0) {}
+ ContentRange(uint64_t aStart, uint64_t aEnd, uint64_t aSize)
+ : mStart(aStart), mEnd(aEnd), mSize(aSize) {}
+ ContentRange(const nsACString& aRangeHeader, uint64_t aSize);
+ void AsHeader(nsACString& aOutString) const;
+ };
+
+ const mozilla::Maybe<ContentRange>& GetContentRange() const {
+ return mContentRange;
+ }
+
+ void SetContentRange(uint64_t aStart, uint64_t aEnd, uint64_t aSize) {
+ mContentRange.emplace(ContentRange(aStart, aEnd, aSize));
+ }
+
+ bool SetContentRange(const nsACString& aRangeHeader, uint64_t aSize) {
+ auto range = ContentRange(aRangeHeader, aSize);
+ if (!range.IsValid()) {
+ return false;
+ }
+ mContentRange.emplace(range);
+ return true;
+ }
+
+ // Helper function for querying the channel's notification callbacks.
+ template <class T>
+ void GetCallback(nsCOMPtr<T>& result) {
+ GetInterface(NS_GET_TEMPLATE_IID(T), getter_AddRefs(result));
+ }
+
+ // If a subclass does not want to feed transport-layer progress events to the
+ // base channel via nsITransportEventSink, then it may set this flag to cause
+ // the base channel to synthesize progress events when it receives data from
+ // the content stream. By default, progress events are not synthesized.
+ void EnableSynthesizedProgressEvents(bool enable) {
+ mSynthProgressEvents = enable;
+ }
+
+ // Some subclasses may wish to manually insert a stream listener between this
+ // and the channel's listener. The following methods make that possible.
+ void SetStreamListener(nsIStreamListener* listener) { mListener = listener; }
+ nsIStreamListener* StreamListener() { return mListener; }
+
+ protected:
+ void DisallowThreadRetargeting() { mAllowThreadRetargeting = false; }
+
+ virtual void SetupNeckoTarget();
+
+ private:
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // Called to setup mPump and call AsyncRead on it.
+ nsresult BeginPumpingData();
+
+ // Called when the callbacks available to this channel may have changed.
+ void CallbacksChanged() {
+ mProgressSink = nullptr;
+ mQueriedProgressSink = false;
+ OnCallbacksChanged();
+ }
+
+ // Called when our channel is done. This should drop no-longer-needed
+ // pointers.
+ void ChannelDone() {
+ mListener = nullptr;
+ OnChannelDone();
+ }
+
+ // Handle an async redirect callback. This will only be called if we
+ // returned success from AsyncOpen while posting a redirect runnable.
+ void HandleAsyncRedirect(nsIChannel* newChannel);
+ void ContinueHandleAsyncRedirect(nsresult result);
+ nsresult ContinueRedirect();
+
+ // start URI classifier if requested
+ void ClassifyURI();
+
+ class RedirectRunnable : public mozilla::Runnable {
+ public:
+ RedirectRunnable(nsBaseChannel* chan, nsIChannel* newChannel)
+ : mozilla::Runnable("nsBaseChannel::RedirectRunnable"),
+ mChannel(chan),
+ mNewChannel(newChannel) {
+ MOZ_ASSERT(newChannel, "Must have channel to redirect to");
+ }
+
+ NS_IMETHOD Run() override {
+ mChannel->HandleAsyncRedirect(mNewChannel);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsBaseChannel> mChannel;
+ nsCOMPtr<nsIChannel> mNewChannel;
+ };
+ friend class RedirectRunnable;
+
+ RefPtr<nsInputStreamPump> mPump;
+ RefPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsICancelable> mCancelableAsyncRequest;
+ bool mPumpingData{false};
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ uint32_t mLoadFlags{LOAD_NORMAL};
+ bool mQueriedProgressSink{true};
+ bool mSynthProgressEvents{false};
+ bool mAllowThreadRetargeting{true};
+ bool mWaitingOnAsyncRedirect{false};
+ bool mOpenRedirectChannel{false};
+ uint32_t mRedirectFlags{0};
+ mozilla::Maybe<ContentRange> mContentRange;
+
+ protected:
+ nsCString mContentType;
+ nsCString mContentCharset;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsresult mStatus{NS_OK};
+ uint32_t mContentDispositionHint{UINT32_MAX};
+ mozilla::UniquePtr<nsString> mContentDispositionFilename;
+ int64_t mContentLength{-1};
+ bool mWasOpened{false};
+ bool mCanceled{false};
+
+ friend class mozilla::net::PrivateBrowsingChannel<nsBaseChannel>;
+};
+
+#endif // !nsBaseChannel_h__
diff --git a/netwerk/base/nsBaseContentStream.cpp b/netwerk/base/nsBaseContentStream.cpp
new file mode 100644
index 0000000000..d59145f463
--- /dev/null
+++ b/netwerk/base/nsBaseContentStream.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBaseContentStream.h"
+#include "nsStreamUtils.h"
+
+//-----------------------------------------------------------------------------
+
+void nsBaseContentStream::DispatchCallback(bool async) {
+ if (!mCallback) return;
+
+ // It's important to clear mCallback and mCallbackTarget up-front because the
+ // OnInputStreamReady implementation may call our AsyncWait method.
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ if (async) {
+ callback = NS_NewInputStreamReadyEvent(
+ "nsBaseContentStream::DispatchCallback", mCallback, mCallbackTarget);
+ mCallback = nullptr;
+ } else {
+ callback.swap(mCallback);
+ }
+ mCallbackTarget = nullptr;
+
+ callback->OnInputStreamReady(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsISupports
+
+NS_IMPL_ADDREF(nsBaseContentStream)
+NS_IMPL_RELEASE(nsBaseContentStream)
+
+// We only support nsIAsyncInputStream when we are in non-blocking mode.
+NS_INTERFACE_MAP_BEGIN(nsBaseContentStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mNonBlocking)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsIInputStream
+
+NS_IMETHODIMP
+nsBaseContentStream::Close() {
+ return IsClosed() ? NS_OK : CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::Available(uint64_t* result) {
+ *result = 0;
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::StreamStatus() { return mStatus; }
+
+NS_IMETHODIMP
+nsBaseContentStream::Read(char* buf, uint32_t count, uint32_t* result) {
+ return ReadSegments(NS_CopySegmentToBuffer, buf, count, result);
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::ReadSegments(nsWriteSegmentFun fun, void* closure,
+ uint32_t count, uint32_t* result) {
+ *result = 0;
+
+ if (mStatus == NS_BASE_STREAM_CLOSED) return NS_OK;
+
+ // No data yet
+ if (!IsClosed() && IsNonBlocking()) return NS_BASE_STREAM_WOULD_BLOCK;
+
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::IsNonBlocking(bool* result) {
+ *result = mNonBlocking;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream::nsIAsyncInputStream
+
+NS_IMETHODIMP
+nsBaseContentStream::CloseWithStatus(nsresult status) {
+ if (IsClosed()) return NS_OK;
+
+ NS_ENSURE_ARG(NS_FAILED(status));
+ mStatus = status;
+
+ DispatchCallback();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseContentStream::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
+ uint32_t requestedCount,
+ nsIEventTarget* target) {
+ // Our _only_ consumer is nsInputStreamPump, so we simplify things here by
+ // making assumptions about how we will be called.
+ NS_ASSERTION(target, "unexpected parameter");
+ NS_ASSERTION(flags == 0, "unexpected parameter");
+ NS_ASSERTION(requestedCount == 0, "unexpected parameter");
+
+#ifdef DEBUG
+ bool correctThread;
+ target->IsOnCurrentThread(&correctThread);
+ NS_ASSERTION(correctThread, "event target must be on the current thread");
+#endif
+
+ mCallback = callback;
+ mCallbackTarget = target;
+
+ if (!mCallback) return NS_OK;
+
+ // If we're already closed, then dispatch this callback immediately.
+ if (IsClosed()) {
+ DispatchCallback();
+ return NS_OK;
+ }
+
+ OnCallbackPending();
+ return NS_OK;
+}
diff --git a/netwerk/base/nsBaseContentStream.h b/netwerk/base/nsBaseContentStream.h
new file mode 100644
index 0000000000..13a3766857
--- /dev/null
+++ b/netwerk/base/nsBaseContentStream.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBaseContentStream_h__
+#define nsBaseContentStream_h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsIEventTarget.h"
+#include "nsCOMPtr.h"
+
+//-----------------------------------------------------------------------------
+// nsBaseContentStream is designed to be subclassed with the intention of being
+// used to satisfy the nsBaseChannel::OpenContentStream method.
+//
+// The subclass typically overrides the default Available, ReadSegments and
+// CloseWithStatus methods. By default, Read is implemented in terms of
+// ReadSegments, and Close is implemented in terms of CloseWithStatus. If
+// CloseWithStatus is overriden, then the subclass will usually want to call
+// the base class' CloseWithStatus method before returning.
+//
+// If the stream is non-blocking, then readSegments may return the exception
+// NS_BASE_STREAM_WOULD_BLOCK if there is no data available and the stream is
+// not at the "end-of-file" or already closed. This error code must not be
+// returned from the Available implementation. When the caller receives this
+// error code, he may choose to call the stream's AsyncWait method, in which
+// case the base stream will have a non-null PendingCallback. When the stream
+// has data or encounters an error, it should be sure to dispatch a pending
+// callback if one exists (see DispatchCallback). The implementation of the
+// base stream's CloseWithStatus (and Close) method will ensure that any
+// pending callback is dispatched. It is the responsibility of the subclass
+// to ensure that the pending callback is dispatched when it wants to have its
+// ReadSegments method called again.
+
+class nsBaseContentStream : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit nsBaseContentStream(bool nonBlocking)
+ : mStatus(NS_OK), mNonBlocking(nonBlocking) {}
+
+ nsresult Status() { return mStatus; }
+ bool IsNonBlocking() { return mNonBlocking; }
+ bool IsClosed() { return NS_FAILED(mStatus); }
+
+ // Called to test if the stream has a pending callback.
+ bool HasPendingCallback() { return mCallback != nullptr; }
+
+ // The current dispatch target (may be null) for the pending callback if any.
+ nsIEventTarget* CallbackTarget() { return mCallbackTarget; }
+
+ // Called to dispatch a pending callback. If there is no pending callback,
+ // then this function does nothing. Pass true to this function to cause the
+ // callback to occur asynchronously; otherwise, the callback will happen
+ // before this function returns.
+ void DispatchCallback(bool async = true);
+
+ // Helper function to make code more self-documenting.
+ void DispatchCallbackSync() { DispatchCallback(false); }
+
+ protected:
+ virtual ~nsBaseContentStream() = default;
+
+ private:
+ // Called from the base stream's AsyncWait method when a pending callback
+ // is installed on the stream.
+ virtual void OnCallbackPending() {}
+
+ private:
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ nsresult mStatus;
+ bool mNonBlocking;
+};
+
+#endif // nsBaseContentStream_h__
diff --git a/netwerk/base/nsBufferedStreams.cpp b/netwerk/base/nsBufferedStreams.cpp
new file mode 100644
index 0000000000..2606352cc4
--- /dev/null
+++ b/netwerk/base/nsBufferedStreams.cpp
@@ -0,0 +1,1197 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBufferedStreams.h"
+#include "nsStreamUtils.h"
+#include "nsNetCID.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIEventTarget.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include <algorithm>
+
+#ifdef DEBUG_brendan
+# define METERING
+#endif
+
+#ifdef METERING
+# include <stdio.h>
+# define METER(x) x
+# define MAX_BIG_SEEKS 20
+
+static struct {
+ uint32_t mSeeksWithinBuffer;
+ uint32_t mSeeksOutsideBuffer;
+ uint32_t mBufferReadUponSeek;
+ uint32_t mBufferUnreadUponSeek;
+ uint32_t mBytesReadFromBuffer;
+ uint32_t mBigSeekIndex;
+ struct {
+ int64_t mOldOffset;
+ int64_t mNewOffset;
+ } mBigSeek[MAX_BIG_SEEKS];
+} bufstats;
+#else
+# define METER(x) /* nothing */
+#endif
+
+using namespace mozilla::ipc;
+using namespace mozilla;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedStream
+
+nsBufferedStream::~nsBufferedStream() { Close(); }
+
+NS_IMPL_ADDREF(nsBufferedStream)
+NS_IMPL_RELEASE(nsBufferedStream)
+
+NS_INTERFACE_MAP_BEGIN(nsBufferedStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsITellableStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, mSeekable)
+NS_INTERFACE_MAP_END
+
+nsresult nsBufferedStream::Init(nsISupports* aStream, uint32_t bufferSize) {
+ NS_ASSERTION(aStream, "need to supply a stream");
+ NS_ASSERTION(mStream == nullptr, "already inited");
+ mStream = aStream; // we keep a reference until nsBufferedStream::Close
+ mBufferSize = bufferSize;
+ mBufferStartOffset = 0;
+ mCursor = 0;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ mSeekable = seekable;
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ mBuffer = new (mozilla::fallible) char[bufferSize];
+ if (mBuffer == nullptr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+void nsBufferedStream::Close() {
+ // Drop the reference from nsBufferedStream::Init()
+ mStream = nullptr;
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ if (mBuffer) {
+ delete[] mBuffer;
+ mBuffer = nullptr;
+ mBufferSize = 0;
+ mBufferStartOffset = 0;
+ mCursor = 0;
+ mFillPoint = 0;
+ }
+#ifdef METERING
+ {
+ static FILE* tfp;
+ if (!tfp) {
+ tfp = fopen("/tmp/bufstats", "w");
+ if (tfp) {
+ setvbuf(tfp, nullptr, _IOLBF, 0);
+ }
+ }
+ if (tfp) {
+ fprintf(tfp, "seeks within buffer: %u\n", bufstats.mSeeksWithinBuffer);
+ fprintf(tfp, "seeks outside buffer: %u\n",
+ bufstats.mSeeksOutsideBuffer);
+ fprintf(tfp, "buffer read on seek: %u\n",
+ bufstats.mBufferReadUponSeek);
+ fprintf(tfp, "buffer unread on seek: %u\n",
+ bufstats.mBufferUnreadUponSeek);
+ fprintf(tfp, "bytes read from buffer: %u\n",
+ bufstats.mBytesReadFromBuffer);
+ for (uint32_t i = 0; i < bufstats.mBigSeekIndex; i++) {
+ fprintf(tfp, "bigseek[%u] = {old: %u, new: %u}\n", i,
+ bufstats.mBigSeek[i].mOldOffset,
+ bufstats.mBigSeek[i].mNewOffset);
+ }
+ }
+ }
+#endif
+}
+
+NS_IMETHODIMP
+nsBufferedStream::Seek(int32_t whence, int64_t offset) {
+ if (mStream == nullptr) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ // If the underlying stream isn't a random access store, then fail early.
+ // We could possibly succeed for the case where the seek position denotes
+ // something that happens to be read into the buffer, but that would make
+ // the failure data-dependent.
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> ras = do_QueryInterface(mStream, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("mStream doesn't QI to nsISeekableStream");
+ return rv;
+ }
+
+ int64_t absPos = 0;
+ switch (whence) {
+ case nsISeekableStream::NS_SEEK_SET:
+ absPos = offset;
+ break;
+ case nsISeekableStream::NS_SEEK_CUR:
+ absPos = mBufferStartOffset;
+ absPos += mCursor;
+ absPos += offset;
+ break;
+ case nsISeekableStream::NS_SEEK_END:
+ absPos = -1;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bogus seek whence parameter");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Let mCursor point into the existing buffer if the new position is
+ // between the current cursor and the mFillPoint "fencepost" -- the
+ // client may never get around to a Read or Write after this Seek.
+ // Read and Write worry about flushing and filling in that event.
+ // But if we're at EOF, make sure to pass the seek through to the
+ // underlying stream, because it may have auto-closed itself and
+ // needs to reopen.
+ uint32_t offsetInBuffer = uint32_t(absPos - mBufferStartOffset);
+ if (offsetInBuffer <= mFillPoint && !mEOF) {
+ METER(bufstats.mSeeksWithinBuffer++);
+ mCursor = offsetInBuffer;
+ return NS_OK;
+ }
+
+ METER(bufstats.mSeeksOutsideBuffer++);
+ METER(bufstats.mBufferReadUponSeek += mCursor);
+ METER(bufstats.mBufferUnreadUponSeek += mFillPoint - mCursor);
+ rv = Flush();
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ NS_WARNING(
+ "(debug) Flush returned error within nsBufferedStream::Seek, so we "
+ "exit early.");
+#endif
+ return rv;
+ }
+
+ rv = ras->Seek(whence, offset);
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ NS_WARNING(
+ "(debug) Error: ras->Seek() returned error within "
+ "nsBufferedStream::Seek, so we exit early.");
+#endif
+ return rv;
+ }
+
+ mEOF = false;
+
+ // Recompute whether the offset we're seeking to is in our buffer.
+ // Note that we need to recompute because Flush() might have
+ // changed mBufferStartOffset.
+ offsetInBuffer = uint32_t(absPos - mBufferStartOffset);
+ if (offsetInBuffer <= mFillPoint) {
+ // It's safe to just set mCursor to offsetInBuffer. In particular, we
+ // want to avoid calling Fill() here since we already have the data that
+ // was seeked to and calling Fill() might auto-close our underlying
+ // stream in some cases.
+ mCursor = offsetInBuffer;
+ return NS_OK;
+ }
+
+ METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS)
+ bufstats.mBigSeek[bufstats.mBigSeekIndex]
+ .mOldOffset = mBufferStartOffset + int64_t(mCursor));
+ const int64_t minus1 = -1;
+ if (absPos == minus1) {
+ // then we had the SEEK_END case, above
+ int64_t tellPos;
+ rv = ras->Tell(&tellPos);
+ mBufferStartOffset = tellPos;
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ mBufferStartOffset = absPos;
+ }
+ METER(if (bufstats.mBigSeekIndex < MAX_BIG_SEEKS)
+ bufstats.mBigSeek[bufstats.mBigSeekIndex++]
+ .mNewOffset = mBufferStartOffset);
+
+ mFillPoint = mCursor = 0;
+
+ // If we seeked back to the start, then don't fill the buffer
+ // right now in case this is a lazily-opened file stream.
+ // We'll fill on the first read, like we did initially.
+ if (whence == nsISeekableStream::NS_SEEK_SET && offset == 0) {
+ return NS_OK;
+ }
+ return Fill();
+}
+
+NS_IMETHODIMP
+nsBufferedStream::Tell(int64_t* result) {
+ if (mStream == nullptr) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ int64_t result64 = mBufferStartOffset;
+ result64 += mCursor;
+ *result = result64;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedStream::SetEOF() {
+ if (mStream == nullptr) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> ras = do_QueryInterface(mStream, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ras->SetEOF();
+ if (NS_SUCCEEDED(rv)) {
+ mEOF = true;
+ }
+
+ return rv;
+}
+
+nsresult nsBufferedStream::GetData(nsISupports** aResult) {
+ nsCOMPtr<nsISupports> stream(mStream);
+ stream.forget(aResult);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedInputStream
+
+NS_IMPL_ADDREF_INHERITED(nsBufferedInputStream, nsBufferedStream)
+NS_IMPL_RELEASE_INHERITED(nsBufferedInputStream, nsBufferedStream)
+
+NS_IMPL_CLASSINFO(nsBufferedInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_BUFFEREDINPUTSTREAM_CID)
+
+NS_INTERFACE_MAP_BEGIN(nsBufferedInputStream)
+ // Unfortunately there isn't a macro that combines ambiguous and conditional,
+ // and as far as I can tell, no other class would need such a macro.
+ if (mIsAsyncInputStream && aIID.Equals(NS_GET_IID(nsIInputStream))) {
+ foundInterface =
+ static_cast<nsIInputStream*>(static_cast<nsIAsyncInputStream*>(this));
+ } else if (!mIsAsyncInputStream && aIID.Equals(NS_GET_IID(nsIInputStream))) {
+ foundInterface = static_cast<nsIInputStream*>(
+ static_cast<nsIBufferedInputStream*>(this));
+ } else
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIBufferedInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
+ mIsIPCSerializable)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mIsAsyncInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
+ mIsAsyncInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
+ mIsCloneableInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, mIsInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength,
+ mIsAsyncInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback,
+ mIsAsyncInputStreamLength)
+ NS_IMPL_QUERY_CLASSINFO(nsBufferedInputStream)
+NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsBufferedInputStream, nsIInputStream,
+ nsIBufferedInputStream, nsISeekableStream,
+ nsITellableStream, nsIStreamBufferAccess)
+
+nsresult nsBufferedInputStream::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsBufferedInputStream> stream = new nsBufferedInputStream();
+ return stream->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Init(nsIInputStream* stream, uint32_t bufferSize) {
+ nsresult rv = nsBufferedStream::Init(stream, bufferSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ {
+ nsCOMPtr<nsIIPCSerializableInputStream> stream = do_QueryInterface(mStream);
+ mIsIPCSerializable = !!stream;
+ }
+
+ {
+ nsCOMPtr<nsIAsyncInputStream> stream = do_QueryInterface(mStream);
+ mIsAsyncInputStream = !!stream;
+ }
+
+ {
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+ mIsCloneableInputStream = !!stream;
+ }
+
+ {
+ nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream);
+ mIsInputStreamLength = !!stream;
+ }
+
+ {
+ nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream);
+ mIsAsyncInputStreamLength = !!stream;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIInputStream> nsBufferedInputStream::GetInputStream() {
+ // A non-null mStream implies Init() has been called.
+ MOZ_ASSERT(mStream);
+
+ nsIInputStream* out = nullptr;
+ DebugOnly<nsresult> rv = QueryInterface(NS_GET_IID(nsIInputStream),
+ reinterpret_cast<void**>(&out));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(out);
+
+ return already_AddRefed<nsIInputStream>(out);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Close() {
+ nsresult rv = NS_OK;
+ if (mStream) {
+ rv = Source()->Close();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "(debug) Error: Source()->Close() returned error in "
+ "bsBuffedInputStream::Close().");
+ }
+ }
+
+ nsBufferedStream::Close();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Available(uint64_t* result) {
+ *result = 0;
+
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ uint64_t avail = mFillPoint - mCursor;
+
+ uint64_t tmp;
+ nsresult rv = Source()->Available(&tmp);
+ if (NS_SUCCEEDED(rv)) {
+ avail += tmp;
+ }
+
+ if (avail) {
+ *result = avail;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::StreamStatus() {
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ if (mFillPoint - mCursor) {
+ return NS_OK;
+ }
+
+ return Source()->StreamStatus();
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Read(char* buf, uint32_t count, uint32_t* result) {
+ if (mBufferDisabled) {
+ if (!mStream) {
+ *result = 0;
+ return NS_OK;
+ }
+ nsresult rv = Source()->Read(buf, count, result);
+ if (NS_SUCCEEDED(rv)) {
+ mBufferStartOffset += *result; // so nsBufferedStream::Tell works
+ if (*result == 0) {
+ mEOF = true;
+ }
+ }
+ return rv;
+ }
+
+ return ReadSegments(NS_CopySegmentToBuffer, buf, count, result);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* result) {
+ *result = 0;
+
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ while (count > 0) {
+ uint32_t amt = std::min(count, mFillPoint - mCursor);
+ if (amt > 0) {
+ uint32_t read = 0;
+ rv = writer(static_cast<nsIBufferedInputStream*>(this), closure,
+ mBuffer + mCursor, *result, amt, &read);
+ if (NS_FAILED(rv)) {
+ // errors returned from the writer end here!
+ rv = NS_OK;
+ break;
+ }
+ *result += read;
+ count -= read;
+ mCursor += read;
+ } else {
+ rv = Fill();
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ break;
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (mFillPoint == mCursor) {
+ break;
+ }
+ }
+ }
+ return (*result > 0) ? NS_OK : rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::IsNonBlocking(bool* aNonBlocking) {
+ if (mStream) {
+ return Source()->IsNonBlocking(aNonBlocking);
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Fill() {
+ if (mBufferDisabled) {
+ return NS_OK;
+ }
+ NS_ENSURE_TRUE(mStream, NS_ERROR_NOT_INITIALIZED);
+
+ RecursiveMutexAutoLock lock(mBufferMutex);
+
+ nsresult rv;
+ int32_t rem = int32_t(mFillPoint - mCursor);
+ if (rem > 0) {
+ // slide the remainder down to the start of the buffer
+ // |<------------->|<--rem-->|<--->|
+ // b c f s
+ memcpy(mBuffer, mBuffer + mCursor, rem);
+ }
+ mBufferStartOffset += mCursor;
+ mFillPoint = rem;
+ mCursor = 0;
+
+ uint32_t amt;
+ rv = Source()->Read(mBuffer + mFillPoint, mBufferSize - mFillPoint, &amt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (amt == 0) {
+ mEOF = true;
+ }
+
+ mFillPoint += amt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(char*)
+nsBufferedInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) {
+ NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!");
+ if (mGetBufferCount != 0) {
+ return nullptr;
+ }
+
+ if (mBufferDisabled) {
+ return nullptr;
+ }
+
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ char* buf = mBuffer + mCursor;
+ uint32_t rem = mFillPoint - mCursor;
+ if (rem == 0) {
+ if (NS_FAILED(Fill())) {
+ return nullptr;
+ }
+ buf = mBuffer + mCursor;
+ rem = mFillPoint - mCursor;
+ }
+
+ uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask);
+ if (mod) {
+ uint32_t pad = aAlignMask + 1 - mod;
+ if (pad > rem) {
+ return nullptr;
+ }
+
+ memset(buf, 0, pad);
+ mCursor += pad;
+ buf += pad;
+ rem -= pad;
+ }
+
+ if (aLength > rem) {
+ return nullptr;
+ }
+ mGetBufferCount++;
+ return buf;
+}
+
+NS_IMETHODIMP_(void)
+nsBufferedInputStream::PutBuffer(char* aBuffer, uint32_t aLength) {
+ NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!");
+ if (--mGetBufferCount != 0) {
+ return;
+ }
+
+ NS_ASSERTION(mCursor + aLength <= mFillPoint, "PutBuffer botch");
+ mCursor += aLength;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::DisableBuffering() {
+ NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!");
+ NS_ASSERTION(mGetBufferCount == 0,
+ "DisableBuffer call between GetBuffer and PutBuffer!");
+ if (mGetBufferCount != 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Empty the buffer so nsBufferedStream::Tell works.
+ mBufferStartOffset += mCursor;
+ mFillPoint = mCursor = 0;
+ mBufferDisabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::EnableBuffering() {
+ NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!");
+ mBufferDisabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::GetUnbufferedStream(nsISupports** aStream) {
+ // Empty the buffer so subsequent i/o trumps any buffered data.
+ mBufferStartOffset += mCursor;
+ mFillPoint = mCursor = 0;
+
+ nsCOMPtr<nsISupports> stream = mStream;
+ stream.forget(aStream);
+ return NS_OK;
+}
+
+void nsBufferedInputStream::SerializedComplexity(uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ uint32_t* aPipes,
+ uint32_t* aTransferables) {
+ if (mStream) {
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream);
+ MOZ_ASSERT(stream);
+
+ InputStreamHelper::SerializedComplexity(stream, aMaxSize, aSizeUsed, aPipes,
+ aTransferables);
+ }
+}
+
+void nsBufferedInputStream::Serialize(InputStreamParams& aParams,
+ uint32_t aMaxSize, uint32_t* aSizeUsed) {
+ MOZ_ASSERT(aSizeUsed);
+ *aSizeUsed = 0;
+
+ BufferedInputStreamParams params;
+
+ if (mStream) {
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream);
+ MOZ_ASSERT(stream);
+
+ InputStreamParams wrappedParams;
+ InputStreamHelper::SerializeInputStream(stream, wrappedParams, aMaxSize,
+ aSizeUsed);
+
+ params.optionalStream().emplace(wrappedParams);
+ }
+
+ params.bufferSize() = mBufferSize;
+
+ aParams = params;
+}
+
+bool nsBufferedInputStream::Deserialize(const InputStreamParams& aParams) {
+ if (aParams.type() != InputStreamParams::TBufferedInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const BufferedInputStreamParams& params =
+ aParams.get_BufferedInputStreamParams();
+ const Maybe<InputStreamParams>& wrappedParams = params.optionalStream();
+
+ nsCOMPtr<nsIInputStream> stream;
+ if (wrappedParams.isSome()) {
+ stream = InputStreamHelper::DeserializeInputStream(wrappedParams.ref());
+ if (!stream) {
+ NS_WARNING("Failed to deserialize wrapped stream!");
+ return false;
+ }
+ }
+
+ nsresult rv = Init(stream, params.bufferSize());
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::CloseWithStatus(nsresult aStatus) { return Close(); }
+
+NS_IMETHODIMP
+nsBufferedInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ nsCOMPtr<nsIAsyncInputStream> stream = do_QueryInterface(mStream);
+ if (!stream) {
+ // Stream is probably closed. Callback, if not nullptr, can be executed
+ // immediately
+ if (!aCallback) {
+ return NS_OK;
+ }
+
+ if (aEventTarget) {
+ nsCOMPtr<nsIInputStreamCallback> callable = NS_NewInputStreamReadyEvent(
+ "nsBufferedInputStream::OnInputStreamReady", aCallback, aEventTarget);
+ return callable->OnInputStreamReady(this);
+ }
+
+ aCallback->OnInputStreamReady(this);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (NS_WARN_IF(mAsyncWaitCallback && aCallback &&
+ mAsyncWaitCallback != aCallback)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mAsyncWaitCallback = aCallback;
+ }
+
+ return stream->AsyncWait(callback, aFlags, aRequestedCount, aEventTarget);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mMutex);
+
+ // We have been canceled in the meanwhile.
+ if (!mAsyncWaitCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncWaitCallback);
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamReady(this);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::GetData(nsIInputStream** aResult) {
+ nsCOMPtr<nsISupports> stream;
+ nsBufferedStream::GetData(getter_AddRefs(stream));
+ nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(stream);
+ inputStream.forget(aResult);
+ return NS_OK;
+}
+
+// nsICloneableInputStream interface
+
+NS_IMETHODIMP
+nsBufferedInputStream::GetCloneable(bool* aCloneable) {
+ *aCloneable = false;
+
+ RecursiveMutexAutoLock lock(mBufferMutex);
+
+ // If we don't have the buffer, the inputStream has been already closed.
+ // If mBufferStartOffset is not 0, the stream has been seeked or read.
+ // In both case the cloning is not supported.
+ if (!mBuffer || mBufferStartOffset) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+
+ // GetCloneable is infallible.
+ NS_ENSURE_TRUE(stream, NS_OK);
+
+ return stream->GetCloneable(aCloneable);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Clone(nsIInputStream** aResult) {
+ RecursiveMutexAutoLock lock(mBufferMutex);
+
+ if (!mBuffer || mBufferStartOffset) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+ NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsresult rv = stream->Clone(getter_AddRefs(clonedStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIBufferedInputStream> bis = new nsBufferedInputStream();
+ rv = bis->Init(clonedStream, mBufferSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult =
+ static_cast<nsBufferedInputStream*>(bis.get())->GetInputStream().take();
+
+ return NS_OK;
+}
+
+// nsIInputStreamLength
+
+NS_IMETHODIMP
+nsBufferedInputStream::Length(int64_t* aLength) {
+ nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream);
+ NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE);
+
+ return stream->Length(aLength);
+}
+
+// nsIAsyncInputStreamLength
+
+NS_IMETHODIMP
+nsBufferedInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback,
+ nsIEventTarget* aEventTarget) {
+ nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream);
+ if (!stream) {
+ // Stream is probably closed. Callback, if not nullptr, can be executed
+ // immediately
+ if (aCallback) {
+ const RefPtr<nsBufferedInputStream> self = this;
+ const nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback;
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "nsBufferedInputStream::OnInputStreamLengthReady",
+ [self, callback] { callback->OnInputStreamLengthReady(self, -1); });
+
+ if (aEventTarget) {
+ aEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ } else {
+ runnable->Run();
+ }
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback ? this : nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+ mAsyncInputStreamLengthCallback = aCallback;
+ }
+
+ MOZ_ASSERT(stream);
+ return stream->AsyncLengthWait(callback, aEventTarget);
+}
+
+// nsIInputStreamLengthCallback
+
+NS_IMETHODIMP
+nsBufferedInputStream::OnInputStreamLengthReady(
+ nsIAsyncInputStreamLength* aStream, int64_t aLength) {
+ nsCOMPtr<nsIInputStreamLengthCallback> callback;
+ {
+ MutexAutoLock lock(mMutex);
+ // We have been canceled in the meanwhile.
+ if (!mAsyncInputStreamLengthCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncInputStreamLengthCallback);
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamLengthReady(this, aLength);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedOutputStream
+
+NS_IMPL_ADDREF_INHERITED(nsBufferedOutputStream, nsBufferedStream)
+NS_IMPL_RELEASE_INHERITED(nsBufferedOutputStream, nsBufferedStream)
+// This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for
+// non-nullness of mSafeStream.
+NS_INTERFACE_MAP_BEGIN(nsBufferedOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISafeOutputStream, mSafeStream)
+ NS_INTERFACE_MAP_ENTRY(nsIBufferedOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess)
+NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream)
+
+nsresult nsBufferedOutputStream::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsBufferedOutputStream> stream = new nsBufferedOutputStream();
+ return stream->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Init(nsIOutputStream* stream, uint32_t bufferSize) {
+ // QI stream to an nsISafeOutputStream, to see if we should support it
+ mSafeStream = do_QueryInterface(stream);
+
+ return nsBufferedStream::Init(stream, bufferSize);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Close() {
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ nsresult rv1, rv2 = NS_OK;
+
+ rv1 = Flush();
+
+#ifdef DEBUG
+ if (NS_FAILED(rv1)) {
+ NS_WARNING(
+ "(debug) Flush() inside nsBufferedOutputStream::Close() returned error "
+ "(rv1).");
+ }
+#endif
+
+ // If we fail to Flush all the data, then we close anyway and drop the
+ // remaining data in the buffer. We do this because it's what Unix does
+ // for fclose and close. However, we report the error from Flush anyway.
+ if (mStream) {
+ rv2 = Sink()->Close();
+#ifdef DEBUG
+ if (NS_FAILED(rv2)) {
+ NS_WARNING(
+ "(debug) Sink->Close() inside nsBufferedOutputStream::Close() "
+ "returned error (rv2).");
+ }
+#endif
+ }
+ nsBufferedStream::Close();
+
+ if (NS_FAILED(rv1)) {
+ return rv1;
+ }
+ if (NS_FAILED(rv2)) {
+ return rv2;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::StreamStatus() {
+ return mStream ? Sink()->StreamStatus() : NS_BASE_STREAM_CLOSED;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Write(const char* buf, uint32_t count,
+ uint32_t* result) {
+ nsresult rv = NS_OK;
+ uint32_t written = 0;
+ *result = 0;
+ if (!mStream) {
+ // We special case this situation.
+ // We should catch the failure, NS_BASE_STREAM_CLOSED ASAP, here.
+ // If we don't, eventually Flush() is called in the while loop below
+ // after so many writes.
+ // However, Flush() returns NS_OK when mStream is null (!!),
+ // and we don't get a meaningful error, NS_BASE_STREAM_CLOSED,
+ // soon enough when we use buffered output.
+#ifdef DEBUG
+ NS_WARNING(
+ "(info) nsBufferedOutputStream::Write returns NS_BASE_STREAM_CLOSED "
+ "immediately (mStream==null).");
+#endif
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ while (count > 0) {
+ uint32_t amt = std::min(count, mBufferSize - mCursor);
+ if (amt > 0) {
+ memcpy(mBuffer + mCursor, buf + written, amt);
+ written += amt;
+ count -= amt;
+ mCursor += amt;
+ if (mFillPoint < mCursor) mFillPoint = mCursor;
+ } else {
+ NS_ASSERTION(mFillPoint, "loop in nsBufferedOutputStream::Write!");
+ rv = Flush();
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ NS_WARNING(
+ "(debug) Flush() returned error in nsBufferedOutputStream::Write.");
+#endif
+ break;
+ }
+ }
+ }
+ *result = written;
+ return (written > 0) ? NS_OK : rv;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::Flush() {
+ nsresult rv;
+ uint32_t amt;
+ if (!mStream) {
+ // Stream already cancelled/flushed; probably because of previous error.
+ return NS_OK;
+ }
+ // optimize : some code within C-C needs to call Seek -> Flush() often.
+ if (mFillPoint == 0) {
+ return NS_OK;
+ }
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ rv = Sink()->Write(mBuffer, mFillPoint, &amt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mBufferStartOffset += amt;
+ if (amt == mFillPoint) {
+ mFillPoint = mCursor = 0;
+ return NS_OK; // flushed everything
+ }
+
+ // slide the remainder down to the start of the buffer
+ // |<-------------->|<---|----->|
+ // b a c s
+ uint32_t rem = mFillPoint - amt;
+ memmove(mBuffer, mBuffer + amt, rem);
+ mFillPoint = mCursor = rem;
+ return NS_ERROR_FAILURE; // didn't flush all
+}
+
+// nsISafeOutputStream
+NS_IMETHODIMP
+nsBufferedOutputStream::Finish() {
+ // flush the stream, to write out any buffered data...
+ nsresult rv1 = nsBufferedOutputStream::Flush();
+ nsresult rv2 = NS_OK;
+
+ if (NS_FAILED(rv1)) {
+ NS_WARNING(
+ "(debug) nsBufferedOutputStream::Flush() failed in "
+ "nsBufferedOutputStream::Finish()! Possible dataloss.");
+
+ rv2 = Sink()->Close();
+ if (NS_FAILED(rv2)) {
+ NS_WARNING(
+ "(debug) Sink()->Close() failed in nsBufferedOutputStream::Finish()! "
+ "Possible dataloss.");
+ }
+ } else {
+ rv2 = mSafeStream->Finish();
+ if (NS_FAILED(rv2)) {
+ NS_WARNING(
+ "(debug) mSafeStream->Finish() failed within "
+ "nsBufferedOutputStream::Flush()! Possible dataloss.");
+ }
+ }
+
+ // ... and close the buffered stream, so any further attempts to flush/close
+ // the buffered stream won't cause errors.
+ nsBufferedStream::Close();
+
+ // We want to return the errors precisely from Finish()
+ // and mimick the existing error handling in
+ // nsBufferedOutputStream::Close() as reference.
+
+ if (NS_FAILED(rv1)) {
+ return rv1;
+ }
+ if (NS_FAILED(rv2)) {
+ return rv2;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::WriteFrom(nsIInputStream* inStr, uint32_t count,
+ uint32_t* _retval) {
+ return WriteSegments(NS_CopyStreamToSegment, inStr, count, _retval);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* _retval) {
+ *_retval = 0;
+ nsresult rv;
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ while (count > 0) {
+ uint32_t left = std::min(count, mBufferSize - mCursor);
+ if (left == 0) {
+ rv = Flush();
+ if (NS_FAILED(rv)) {
+ return (*_retval > 0) ? NS_OK : rv;
+ }
+
+ continue;
+ }
+
+ uint32_t read = 0;
+ rv = reader(this, closure, mBuffer + mCursor, *_retval, left, &read);
+
+ if (NS_FAILED(rv)) { // If we have read some data, return ok
+ return (*_retval > 0) ? NS_OK : rv;
+ }
+ mCursor += read;
+ *_retval += read;
+ count -= read;
+ mFillPoint = std::max(mFillPoint, mCursor);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::IsNonBlocking(bool* aNonBlocking) {
+ if (mStream) {
+ return Sink()->IsNonBlocking(aNonBlocking);
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP_(char*)
+nsBufferedOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) {
+ NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!");
+ if (mGetBufferCount != 0) {
+ return nullptr;
+ }
+
+ if (mBufferDisabled) {
+ return nullptr;
+ }
+
+ RecursiveMutexAutoLock lock(mBufferMutex);
+ char* buf = mBuffer + mCursor;
+ uint32_t rem = mBufferSize - mCursor;
+ if (rem == 0) {
+ if (NS_FAILED(Flush())) {
+ return nullptr;
+ }
+ buf = mBuffer + mCursor;
+ rem = mBufferSize - mCursor;
+ }
+
+ uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask);
+ if (mod) {
+ uint32_t pad = aAlignMask + 1 - mod;
+ if (pad > rem) {
+ return nullptr;
+ }
+
+ memset(buf, 0, pad);
+ mCursor += pad;
+ buf += pad;
+ rem -= pad;
+ }
+
+ if (aLength > rem) {
+ return nullptr;
+ }
+ mGetBufferCount++;
+ return buf;
+}
+
+NS_IMETHODIMP_(void)
+nsBufferedOutputStream::PutBuffer(char* aBuffer, uint32_t aLength) {
+ NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!");
+ if (--mGetBufferCount != 0) {
+ return;
+ }
+
+ NS_ASSERTION(mCursor + aLength <= mBufferSize, "PutBuffer botch");
+ mCursor += aLength;
+ if (mFillPoint < mCursor) {
+ mFillPoint = mCursor;
+ }
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::DisableBuffering() {
+ NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!");
+ NS_ASSERTION(mGetBufferCount == 0,
+ "DisableBuffer call between GetBuffer and PutBuffer!");
+ if (mGetBufferCount != 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Empty the buffer so nsBufferedStream::Tell works.
+ nsresult rv = Flush();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mBufferDisabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::EnableBuffering() {
+ NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!");
+ mBufferDisabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::GetUnbufferedStream(nsISupports** aStream) {
+ // Empty the buffer so subsequent i/o trumps any buffered data.
+ if (mFillPoint) {
+ nsresult rv = Flush();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsISupports> stream = mStream;
+ stream.forget(aStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::GetData(nsIOutputStream** aResult) {
+ nsCOMPtr<nsISupports> stream;
+ nsBufferedStream::GetData(getter_AddRefs(stream));
+ nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(stream);
+ outputStream.forget(aResult);
+ return NS_OK;
+}
+#undef METER
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/base/nsBufferedStreams.h b/netwerk/base/nsBufferedStreams.h
new file mode 100644
index 0000000000..929c544209
--- /dev/null
+++ b/netwerk/base/nsBufferedStreams.h
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBufferedStreams_h__
+#define nsBufferedStreams_h__
+
+#include "nsIBufferedStreams.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIStreamBufferAccess.h"
+#include "nsCOMPtr.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsICloneableInputStream.h"
+#include "nsIInputStreamLength.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RecursiveMutex.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedStream : public nsISeekableStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+
+ nsBufferedStream() = default;
+
+ void Close();
+
+ protected:
+ virtual ~nsBufferedStream();
+
+ nsresult Init(nsISupports* stream, uint32_t bufferSize);
+ nsresult GetData(nsISupports** aResult);
+ NS_IMETHOD Fill() = 0;
+ NS_IMETHOD Flush() = 0;
+
+ uint32_t mBufferSize{0};
+ char* mBuffer MOZ_GUARDED_BY(mBufferMutex){nullptr};
+
+ mozilla::RecursiveMutex mBufferMutex{"nsBufferedStream::mBufferMutex"};
+
+ // mBufferStartOffset is the offset relative to the start of mStream.
+ int64_t mBufferStartOffset{0};
+
+ // mCursor is the read cursor for input streams, or write cursor for
+ // output streams, and is relative to mBufferStartOffset.
+ uint32_t mCursor{0};
+
+ // mFillPoint is the amount available in the buffer for input streams,
+ // or the high watermark of bytes written into the buffer, and therefore
+ // is relative to mBufferStartOffset.
+ uint32_t mFillPoint{0};
+
+ nsCOMPtr<nsISupports> mStream; // cast to appropriate subclass
+
+ bool mBufferDisabled{false};
+ bool mEOF{false}; // True if mStream is at EOF
+ bool mSeekable{true};
+ uint8_t mGetBufferCount{0};
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedInputStream final : public nsBufferedStream,
+ public nsIBufferedInputStream,
+ public nsIStreamBufferAccess,
+ public nsIIPCSerializableInputStream,
+ public nsIAsyncInputStream,
+ public nsIInputStreamCallback,
+ public nsICloneableInputStream,
+ public nsIInputStreamLength,
+ public nsIAsyncInputStreamLength,
+ public nsIInputStreamLengthCallback {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIBUFFEREDINPUTSTREAM
+ NS_DECL_NSISTREAMBUFFERACCESS
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMLENGTH
+ NS_DECL_NSIASYNCINPUTSTREAMLENGTH
+ NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK
+
+ nsBufferedInputStream() = default;
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ nsIInputStream* Source() { return (nsIInputStream*)mStream.get(); }
+
+ /**
+ * If there's a reference/pointer to an nsBufferedInputStream BEFORE calling
+ * Init() AND the intent is to ultimately convert/assign that
+ * reference/pointer to an nsIInputStream, DO NOT use that initial
+ * reference/pointer. Instead, use the value of QueryInterface-ing to an
+ * nsIInputStream (and, again, the QueryInterface must be performed after
+ * Init()). This is because nsBufferedInputStream has multiple underlying
+ * nsIInputStreams (one from nsIBufferedInputStream and one from
+ * nsIAsyncInputStream), and the correct base nsIInputStream to use will be
+ * unknown until the final value of mIsAsyncInputStream is set in Init().
+ *
+ * This method, however, does just that but also hides the QI details and
+ * will assert if called before Init().
+ */
+ already_AddRefed<nsIInputStream> GetInputStream();
+
+ protected:
+ virtual ~nsBufferedInputStream() = default;
+
+ NS_IMETHOD Fill() override;
+ NS_IMETHOD Flush() override { return NS_OK; } // no-op for input streams
+
+ mozilla::Mutex mMutex MOZ_UNANNOTATED{"nsBufferedInputStream::mMutex"};
+
+ // This value is protected by mutex.
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
+
+ // This value is protected by mutex.
+ nsCOMPtr<nsIInputStreamLengthCallback> mAsyncInputStreamLengthCallback;
+
+ bool mIsIPCSerializable{true};
+ bool mIsAsyncInputStream{false};
+ bool mIsCloneableInputStream{false};
+ bool mIsInputStreamLength{false};
+ bool mIsAsyncInputStreamLength{false};
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedOutputStream : public nsBufferedStream,
+ public nsISafeOutputStream,
+ public nsIBufferedOutputStream,
+ public nsIStreamBufferAccess {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSISAFEOUTPUTSTREAM
+ NS_DECL_NSIBUFFEREDOUTPUTSTREAM
+ NS_DECL_NSISTREAMBUFFERACCESS
+
+ nsBufferedOutputStream() = default;
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ nsIOutputStream* Sink() { return (nsIOutputStream*)mStream.get(); }
+
+ protected:
+ virtual ~nsBufferedOutputStream() { nsBufferedOutputStream::Close(); }
+
+ NS_IMETHOD Fill() override { return NS_OK; } // no-op for output streams
+
+ nsCOMPtr<nsISafeOutputStream> mSafeStream; // QI'd from mStream
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif // nsBufferedStreams_h__
diff --git a/netwerk/base/nsDNSPrefetch.cpp b/netwerk/base/nsDNSPrefetch.cpp
new file mode 100644
index 0000000000..4c5a3a0fd6
--- /dev/null
+++ b/netwerk/base/nsDNSPrefetch.cpp
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDNSPrefetch.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+#include "nsIDNSAdditionalInfo.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsICancelable.h"
+#include "nsIURI.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Preferences.h"
+
+static mozilla::StaticRefPtr<nsIDNSService> sDNSService;
+
+nsresult nsDNSPrefetch::Initialize(nsIDNSService* aDNSService) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sDNSService = aDNSService;
+ return NS_OK;
+}
+
+nsresult nsDNSPrefetch::Shutdown() {
+ sDNSService = nullptr;
+ return NS_OK;
+}
+
+nsDNSPrefetch::nsDNSPrefetch(nsIURI* aURI,
+ mozilla::OriginAttributes& aOriginAttributes,
+ nsIRequest::TRRMode aTRRMode,
+ nsIDNSListener* aListener, bool storeTiming)
+ : mOriginAttributes(aOriginAttributes),
+ mStoreTiming(storeTiming),
+ mTRRMode(aTRRMode),
+ mListener(do_GetWeakReference(aListener)) {
+ aURI->GetAsciiHost(mHostname);
+ aURI->GetPort(&mPort);
+}
+
+nsDNSPrefetch::nsDNSPrefetch(nsIURI* aURI,
+ mozilla::OriginAttributes& aOriginAttributes,
+ nsIRequest::TRRMode aTRRMode)
+ : mOriginAttributes(aOriginAttributes),
+ mStoreTiming(false),
+ mTRRMode(aTRRMode),
+ mListener(nullptr) {
+ aURI->GetAsciiHost(mHostname);
+}
+
+nsresult nsDNSPrefetch::Prefetch(nsIDNSService::DNSFlags flags) {
+ if (mHostname.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ if (!sDNSService) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ if (mStoreTiming) mStartTimestamp = mozilla::TimeStamp::Now();
+ // If AsyncResolve fails, for example because prefetching is disabled,
+ // then our timing will be useless. However, in such a case,
+ // mEndTimestamp will be a null timestamp and callers should check
+ // TimingsValid() before using the timing.
+ nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget();
+
+ flags |= nsIDNSService::GetFlagsFromTRRMode(mTRRMode);
+
+ return sDNSService->AsyncResolveNative(
+ mHostname, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ flags | nsIDNSService::RESOLVE_SPECULATE, nullptr, this, target,
+ mOriginAttributes, getter_AddRefs(tmpOutstanding));
+}
+
+nsresult nsDNSPrefetch::PrefetchLow(nsIDNSService::DNSFlags aFlags) {
+ return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW | aFlags);
+}
+
+nsresult nsDNSPrefetch::PrefetchMedium(nsIDNSService::DNSFlags aFlags) {
+ return Prefetch(nsIDNSService::RESOLVE_PRIORITY_MEDIUM | aFlags);
+}
+
+nsresult nsDNSPrefetch::PrefetchHigh(nsIDNSService::DNSFlags aFlags) {
+ return Prefetch(aFlags);
+}
+
+namespace {
+
+class HTTPSRRListener final : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ explicit HTTPSRRListener(
+ std::function<void(nsIDNSHTTPSSVCRecord*)>&& aCallback)
+ : mResultCallback(std::move(aCallback)) {}
+
+ private:
+ ~HTTPSRRListener() = default;
+ std::function<void(nsIDNSHTTPSSVCRecord*)> mResultCallback;
+};
+
+NS_IMPL_ISUPPORTS(HTTPSRRListener, nsIDNSListener)
+
+NS_IMETHODIMP
+HTTPSRRListener::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRec,
+ nsresult aStatus) {
+ if (NS_FAILED(aStatus)) {
+ mResultCallback(nullptr);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord = do_QueryInterface(aRec);
+ mResultCallback(httpsRecord);
+ return NS_OK;
+}
+
+}; // namespace
+
+nsresult nsDNSPrefetch::FetchHTTPSSVC(
+ bool aRefreshDNS, bool aPrefetch,
+ std::function<void(nsIDNSHTTPSSVCRecord*)>&& aCallback) {
+ if (!sDNSService) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget();
+ nsIDNSService::DNSFlags flags = nsIDNSService::GetFlagsFromTRRMode(mTRRMode);
+ if (aRefreshDNS) {
+ flags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ if (aPrefetch) {
+ flags |= nsIDNSService::RESOLVE_SPECULATE;
+ }
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+ nsCOMPtr<nsIDNSListener> listener = new HTTPSRRListener(std::move(aCallback));
+ nsCOMPtr<nsIDNSAdditionalInfo> info;
+ if (mPort != -1) {
+ sDNSService->NewAdditionalInfo(""_ns, mPort, getter_AddRefs(info));
+ }
+ return sDNSService->AsyncResolveNative(
+ mHostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, flags, info, listener,
+ target, mOriginAttributes, getter_AddRefs(tmpOutstanding));
+}
+
+NS_IMPL_ISUPPORTS(nsDNSPrefetch, nsIDNSListener)
+
+NS_IMETHODIMP
+nsDNSPrefetch::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ if (mStoreTiming) {
+ mEndTimestamp = mozilla::TimeStamp::Now();
+ }
+ nsCOMPtr<nsIDNSListener> listener = do_QueryReferent(mListener);
+ if (listener) {
+ listener->OnLookupComplete(request, rec, status);
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/base/nsDNSPrefetch.h b/netwerk/base/nsDNSPrefetch.h
new file mode 100644
index 0000000000..7cad00b588
--- /dev/null
+++ b/netwerk/base/nsDNSPrefetch.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDNSPrefetch_h___
+#define nsDNSPrefetch_h___
+
+#include <functional>
+
+#include "nsIWeakReferenceUtils.h"
+#include "nsString.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasePrincipal.h"
+
+#include "nsIDNSListener.h"
+#include "nsIRequest.h"
+#include "nsIDNSService.h"
+
+class nsIURI;
+class nsIDNSHTTPSSVCRecord;
+
+class nsDNSPrefetch final : public nsIDNSListener {
+ ~nsDNSPrefetch() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ nsDNSPrefetch(nsIURI* aURI, mozilla::OriginAttributes& aOriginAttributes,
+ nsIRequest::TRRMode aTRRMode, nsIDNSListener* aListener,
+ bool storeTiming);
+ // For fetching HTTPS RR.
+ nsDNSPrefetch(nsIURI* aURI, mozilla::OriginAttributes& aOriginAttributes,
+ nsIRequest::TRRMode aTRRMode);
+ bool TimingsValid() const {
+ return !mStartTimestamp.IsNull() && !mEndTimestamp.IsNull();
+ }
+ // Only use the two timings if TimingsValid() returns true
+ const mozilla::TimeStamp& StartTimestamp() const { return mStartTimestamp; }
+ const mozilla::TimeStamp& EndTimestamp() const { return mEndTimestamp; }
+
+ static nsresult Initialize(nsIDNSService* aDNSService);
+ static nsresult Shutdown();
+
+ // Call one of the following methods to start the Prefetch.
+ nsresult PrefetchHigh(
+ nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS);
+ nsresult PrefetchMedium(
+ nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS);
+ nsresult PrefetchLow(
+ nsIDNSService::DNSFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS);
+
+ nsresult FetchHTTPSSVC(
+ bool aRefreshDNS, bool aPrefetch,
+ std::function<void(nsIDNSHTTPSSVCRecord*)>&& aCallback);
+
+ private:
+ nsCString mHostname;
+ int32_t mPort{-1};
+ mozilla::OriginAttributes mOriginAttributes;
+ bool mStoreTiming;
+ nsIRequest::TRRMode mTRRMode;
+ mozilla::TimeStamp mStartTimestamp;
+ mozilla::TimeStamp mEndTimestamp;
+ nsWeakPtr mListener;
+
+ nsresult Prefetch(nsIDNSService::DNSFlags flags);
+};
+
+#endif
diff --git a/netwerk/base/nsDirectoryIndexStream.cpp b/netwerk/base/nsDirectoryIndexStream.cpp
new file mode 100644
index 0000000000..b9b840172e
--- /dev/null
+++ b/netwerk/base/nsDirectoryIndexStream.cpp
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ The converts a filesystem directory into an "HTTP index" stream per
+ Lou Montulli's original spec:
+
+ http://www.mozilla.org/projects/netlib/dirindexformat.html
+
+ */
+
+#include "nsEscape.h"
+#include "nsDirectoryIndexStream.h"
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "nsIFile.h"
+#include "nsNativeCharsetUtils.h"
+
+// NOTE: This runs on the _file transport_ thread.
+// The problem is that now that we're actually doing something with the data,
+// we want to do stuff like i18n sorting. However, none of the collation stuff
+// is threadsafe.
+// So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current
+// behaviour, though. See bug 99382.
+
+using namespace mozilla;
+static LazyLogModule gLog("nsDirectoryIndexStream");
+
+nsDirectoryIndexStream::nsDirectoryIndexStream() {
+ MOZ_LOG(gLog, LogLevel::Debug, ("nsDirectoryIndexStream[%p]: created", this));
+}
+
+static int compare(nsIFile* aElement1, nsIFile* aElement2) {
+ if (!NS_IsNativeUTF8()) {
+ // don't check for errors, because we can't report them anyway
+ nsAutoString name1, name2;
+ aElement1->GetLeafName(name1);
+ aElement2->GetLeafName(name2);
+
+ // Note - we should do the collation to do sorting. Why don't we?
+ // Because that is _slow_. Using TestProtocols to list file:///dev/
+ // goes from 3 seconds to 22. (This may be why nsXULSortService is
+ // so slow as well).
+ // Does this have bad effects? Probably, but since nsXULTree appears
+ // to use the raw RDF literal value as the sort key (which ammounts to an
+ // strcmp), it won't be any worse, I think.
+ // This could be made faster, by creating the keys once,
+ // but CompareString could still be smarter - see bug 99383 - bbaetz
+ // NB - 99393 has been WONTFIXed. So if the I18N code is ever made
+ // threadsafe so that this matters, we'd have to pass through a
+ // struct { nsIFile*, uint8_t* } with the pre-calculated key.
+ return Compare(name1, name2);
+ }
+
+ nsAutoCString name1, name2;
+ aElement1->GetNativeLeafName(name1);
+ aElement2->GetNativeLeafName(name2);
+
+ return Compare(name1, name2);
+}
+
+nsresult nsDirectoryIndexStream::Init(nsIFile* aDir) {
+ nsresult rv;
+ bool isDir;
+ rv = aDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ MOZ_ASSERT(isDir, "not a directory");
+ if (!isDir) return NS_ERROR_ILLEGAL_VALUE;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: initialized on %s", this,
+ aDir->HumanReadablePath().get()));
+ }
+
+ // Sigh. We have to allocate on the heap because there are no
+ // assignment operators defined.
+ nsCOMPtr<nsIDirectoryEnumerator> iter;
+ rv = aDir->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv)) return rv;
+
+ // Now lets sort, because clients expect it that way
+ // XXX - should we do so here, or when the first item is requested?
+ // XXX - use insertion sort instead?
+
+ nsCOMPtr<nsIFile> file;
+ while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) {
+ mArray.AppendObject(file); // addrefs
+ }
+
+ mArray.Sort(compare);
+
+ mBuf.AppendLiteral("200: filename content-length last-modified file-type\n");
+
+ return NS_OK;
+}
+
+nsDirectoryIndexStream::~nsDirectoryIndexStream() {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: destroyed", this));
+}
+
+nsresult nsDirectoryIndexStream::Create(nsIFile* aDir,
+ nsIInputStream** aResult) {
+ RefPtr<nsDirectoryIndexStream> result = new nsDirectoryIndexStream();
+ if (!result) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = result->Init(aDir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsDirectoryIndexStream, nsIInputStream)
+
+// The below routines are proxied to the UI thread!
+NS_IMETHODIMP
+nsDirectoryIndexStream::Close() {
+ mStatus = NS_BASE_STREAM_CLOSED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::Available(uint64_t* aLength) {
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ // If there's data in our buffer, use that
+ if (mOffset < (int32_t)mBuf.Length()) {
+ *aLength = mBuf.Length() - mOffset;
+ return NS_OK;
+ }
+
+ // Returning one byte is not ideal, but good enough
+ *aLength = (mPos < mArray.Count()) ? 1 : 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::StreamStatus() { return mStatus; }
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aReadCount) {
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *aReadCount = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ uint32_t nread = 0;
+
+ // If anything is enqueued (or left-over) in mBuf, then feed it to
+ // the reader first.
+ while (mOffset < (int32_t)mBuf.Length() && aCount != 0) {
+ *(aBuf++) = char(mBuf.CharAt(mOffset++));
+ --aCount;
+ ++nread;
+ }
+
+ // Room left?
+ if (aCount > 0) {
+ mOffset = 0;
+ mBuf.Truncate();
+
+ // Okay, now we'll suck stuff off of our iterator into the mBuf...
+ while (uint32_t(mBuf.Length()) < aCount) {
+ bool more = mPos < mArray.Count();
+ if (!more) break;
+
+ // don't addref, for speed - an addref happened when it
+ // was placed in the array, so it's not going to go stale
+ nsIFile* current = mArray.ObjectAt(mPos);
+ ++mPos;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: iterated %s", this,
+ current->HumanReadablePath().get()));
+ }
+
+ // rjc: don't return hidden files/directories!
+ // bbaetz: why not?
+ nsresult rv;
+#ifndef XP_UNIX
+ bool hidden = false;
+ current->IsHidden(&hidden);
+ if (hidden) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: skipping hidden file/directory",
+ this));
+ continue;
+ }
+#endif
+
+ int64_t fileSize = 0;
+ current->GetFileSize(&fileSize);
+
+ PRTime fileInfoModifyTime = 0;
+ current->GetLastModifiedTime(&fileInfoModifyTime);
+ fileInfoModifyTime *= PR_USEC_PER_MSEC;
+
+ mBuf.AppendLiteral("201: ");
+
+ // The "filename" field
+ if (!NS_IsNativeUTF8()) {
+ nsAutoString leafname;
+ rv = current->GetLeafName(leafname);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString escaped;
+ if (!leafname.IsEmpty() &&
+ NS_Escape(NS_ConvertUTF16toUTF8(leafname), escaped, url_Path)) {
+ mBuf.Append(escaped);
+ mBuf.Append(' ');
+ }
+ } else {
+ nsAutoCString leafname;
+ rv = current->GetNativeLeafName(leafname);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString escaped;
+ if (!leafname.IsEmpty() && NS_Escape(leafname, escaped, url_Path)) {
+ mBuf.Append(escaped);
+ mBuf.Append(' ');
+ }
+ }
+
+ // The "content-length" field
+ mBuf.AppendInt(fileSize, 10);
+ mBuf.Append(' ');
+
+ // The "last-modified" field
+ PRExplodedTime tm;
+ PR_ExplodeTime(fileInfoModifyTime, PR_GMTParameters, &tm);
+ {
+ char buf[64];
+ PR_FormatTimeUSEnglish(
+ buf, sizeof(buf), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
+ mBuf.Append(buf);
+ }
+
+ // The "file-type" field
+ bool isFile = true;
+ current->IsFile(&isFile);
+ if (isFile) {
+ mBuf.AppendLiteral("FILE ");
+ } else {
+ bool isDir;
+ rv = current->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ if (isDir) {
+ mBuf.AppendLiteral("DIRECTORY ");
+ } else {
+ bool isLink;
+ rv = current->IsSymlink(&isLink);
+ if (NS_FAILED(rv)) return rv;
+ if (isLink) {
+ mBuf.AppendLiteral("SYMBOLIC-LINK ");
+ }
+ }
+ }
+
+ mBuf.Append('\n');
+ }
+
+ // ...and once we've either run out of directory entries, or
+ // filled up the buffer, then we'll push it to the reader.
+ while (mOffset < (int32_t)mBuf.Length() && aCount != 0) {
+ *(aBuf++) = char(mBuf.CharAt(mOffset++));
+ --aCount;
+ ++nread;
+ }
+ }
+
+ *aReadCount = nread;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = false;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsDirectoryIndexStream.h b/netwerk/base/nsDirectoryIndexStream.h
new file mode 100644
index 0000000000..6bdebc7cfe
--- /dev/null
+++ b/netwerk/base/nsDirectoryIndexStream.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDirectoryIndexStream_h__
+#define nsDirectoryIndexStream_h__
+
+#include "mozilla/Attributes.h"
+
+#include "nsString.h"
+#include "nsIInputStream.h"
+#include "nsCOMArray.h"
+
+class nsIFile;
+
+class nsDirectoryIndexStream final : public nsIInputStream {
+ private:
+ nsCString mBuf;
+ int32_t mOffset{0};
+ nsresult mStatus{NS_OK};
+
+ int32_t mPos{0}; // position within mArray
+ nsCOMArray<nsIFile> mArray; // file objects within the directory
+
+ nsDirectoryIndexStream();
+ /**
+ * aDir will only be used on the calling thread.
+ */
+ nsresult Init(nsIFile* aDir);
+ ~nsDirectoryIndexStream();
+
+ public:
+ /**
+ * aDir will only be used on the calling thread.
+ */
+ static nsresult Create(nsIFile* aDir, nsIInputStream** aResult);
+
+ // nsISupportsInterface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIInputStream interface
+ NS_DECL_NSIINPUTSTREAM
+};
+
+#endif // nsDirectoryIndexStream_h__
diff --git a/netwerk/base/nsDownloader.cpp b/netwerk/base/nsDownloader.cpp
new file mode 100644
index 0000000000..b6d3240226
--- /dev/null
+++ b/netwerk/base/nsDownloader.cpp
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDownloader.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsNetUtil.h"
+#include "nsCRTGlue.h"
+
+nsDownloader::~nsDownloader() {
+ if (mLocation && mLocationIsTemp) {
+ // release the sink first since it may still hold an open file
+ // descriptor to mLocation. this needs to happen before the
+ // file can be removed otherwise the Remove call will fail.
+ if (mSink) {
+ mSink->Close();
+ mSink = nullptr;
+ }
+
+ nsresult rv = mLocation->Remove(false);
+ if (NS_FAILED(rv)) NS_ERROR("unable to remove temp file");
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsDownloader, nsIDownloader, nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+nsDownloader::Init(nsIDownloadObserver* observer, nsIFile* location) {
+ mObserver = observer;
+ mLocation = location;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnStartRequest(nsIRequest* request) {
+ nsresult rv;
+ if (!mLocation) {
+ nsCOMPtr<nsIFile> location;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(location));
+ if (NS_FAILED(rv)) return rv;
+
+ char buf[13];
+ NS_MakeRandomString(buf, 8);
+ memcpy(buf + 8, ".tmp", 5);
+ rv = location->AppendNative(nsDependentCString(buf, 12));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = location->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_FAILED(rv)) return rv;
+
+ location.swap(mLocation);
+ mLocationIsTemp = true;
+ }
+
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(mSink), mLocation);
+ if (NS_FAILED(rv)) return rv;
+
+ // we could wrap this output stream with a buffered output stream,
+ // but it shouldn't be necessary since we will be writing large
+ // chunks given to us via OnDataAvailable.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnStopRequest(nsIRequest* request, nsresult status) {
+ if (mSink) {
+ mSink->Close();
+ mSink = nullptr;
+ }
+
+ mObserver->OnDownloadComplete(this, request, status, mLocation);
+ mObserver = nullptr;
+
+ return NS_OK;
+}
+
+nsresult nsDownloader::ConsumeData(nsIInputStream* in, void* closure,
+ const char* fromRawSegment,
+ uint32_t toOffset, uint32_t count,
+ uint32_t* writeCount) {
+ nsDownloader* self = (nsDownloader*)closure;
+ if (self->mSink) return self->mSink->Write(fromRawSegment, count, writeCount);
+
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloader::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) {
+ uint32_t n;
+ return inStr->ReadSegments(ConsumeData, this, count, &n);
+}
diff --git a/netwerk/base/nsDownloader.h b/netwerk/base/nsDownloader.h
new file mode 100644
index 0000000000..fe95dd6ac2
--- /dev/null
+++ b/netwerk/base/nsDownloader.h
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDownloader_h__
+#define nsDownloader_h__
+
+#include "nsIDownloader.h"
+#include "nsCOMPtr.h"
+
+class nsIFile;
+class nsIOutputStream;
+
+class nsDownloader : public nsIDownloader {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOWNLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsDownloader() = default;
+
+ protected:
+ virtual ~nsDownloader();
+
+ static nsresult ConsumeData(nsIInputStream* in, void* closure,
+ const char* fromRawSegment, uint32_t toOffset,
+ uint32_t count, uint32_t* writeCount);
+
+ nsCOMPtr<nsIDownloadObserver> mObserver;
+ nsCOMPtr<nsIFile> mLocation;
+ nsCOMPtr<nsIOutputStream> mSink;
+ bool mLocationIsTemp{false};
+};
+
+#endif // nsDownloader_h__
diff --git a/netwerk/base/nsFileStreams.cpp b/netwerk/base/nsFileStreams.cpp
new file mode 100644
index 0000000000..32281f6d02
--- /dev/null
+++ b/netwerk/base/nsFileStreams.cpp
@@ -0,0 +1,1031 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ipc/IPCMessageUtils.h"
+
+#if defined(XP_UNIX)
+# include <unistd.h>
+#elif defined(XP_WIN)
+# include <windows.h>
+# include "nsILocalFileWin.h"
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+#include "private/pprio.h"
+#include "prerror.h"
+
+#include "IOActivityMonitor.h"
+#include "nsFileStreams.h"
+#include "nsIFile.h"
+#include "nsReadLine.h"
+#include "nsIClassInfoImpl.h"
+#include "nsLiteralString.h"
+#include "nsSocketTransport2.h" // for ErrorAccordingToNSPR()
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/RandomAccessStreamParams.h"
+#include "mozilla/Unused.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "nsNetCID.h"
+#include "nsXULAppAPI.h"
+
+using FileHandleType = mozilla::ipc::FileDescriptor::PlatformHandleType;
+
+using namespace mozilla::ipc;
+using namespace mozilla::net;
+
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileStreamBase
+
+nsFileStreamBase::~nsFileStreamBase() {
+ // We don't want to try to rewrind the stream when shutting down.
+ mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
+
+ Close();
+}
+
+NS_IMPL_ISUPPORTS(nsFileStreamBase, nsISeekableStream, nsITellableStream,
+ nsIFileMetadata)
+
+NS_IMETHODIMP
+nsFileStreamBase::Seek(int32_t whence, int64_t offset) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence);
+ if (cnt == int64_t(-1)) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::Tell(int64_t* result) {
+ if (mState == eDeferredOpen && !(mOpenParams.ioFlags & PR_APPEND)) {
+ *result = 0;
+ return NS_OK;
+ }
+
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR);
+ if (cnt == int64_t(-1)) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::SetEOF() {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#if defined(XP_UNIX)
+ // Some system calls require an EOF offset.
+ int64_t offset;
+ rv = Tell(&offset);
+ if (NS_FAILED(rv)) return rv;
+#endif
+
+#if defined(XP_UNIX)
+ if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) {
+ NS_ERROR("ftruncate failed");
+ return NS_ERROR_FAILURE;
+ }
+#elif defined(XP_WIN)
+ if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(mFD))) {
+ NS_ERROR("SetEndOfFile failed");
+ return NS_ERROR_FAILURE;
+ }
+#else
+ // XXX not implemented
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetSize(int64_t* _retval) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRFileInfo64 info;
+ if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
+ return NS_BASE_STREAM_OSERROR;
+ }
+
+ *_retval = int64_t(info.size);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetLastModified(int64_t* _retval) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRFileInfo64 info;
+ if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
+ return NS_BASE_STREAM_OSERROR;
+ }
+
+ int64_t modTime = int64_t(info.modifyTime);
+ if (modTime == 0) {
+ *_retval = 0;
+ } else {
+ *_retval = modTime / int64_t(PR_USEC_PER_MSEC);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetFileDescriptor(PRFileDesc** _retval) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = mFD;
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::Close() {
+ if (mState == eClosed) {
+ return NS_OK;
+ }
+
+ CleanUpOpen();
+
+ nsresult rv = NS_OK;
+ if (mFD) {
+ if (PR_Close(mFD) == PR_FAILURE) rv = NS_BASE_STREAM_OSERROR;
+ mFD = nullptr;
+ mState = eClosed;
+ }
+ return rv;
+}
+
+nsresult nsFileStreamBase::Available(uint64_t* aResult) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // PR_Available with files over 4GB returns an error, so we have to
+ // use the 64-bit version of PR_Available.
+ int64_t avail = PR_Available64(mFD);
+ if (avail == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ // If available is greater than 4GB, return 4GB
+ *aResult = (uint64_t)avail;
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aResult) {
+ nsresult rv = DoPendingOpen();
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int32_t bytesRead = PR_Read(mFD, aBuf, aCount);
+ if (bytesRead == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ *aResult = bytesRead;
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* aResult) {
+ // ReadSegments is not implemented because it would be inefficient when
+ // the writer does not consume all data. If you want to call ReadSegments,
+ // wrap a BufferedInputStream around the file stream. That will call
+ // Read().
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsFileStreamBase::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::Flush(void) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t cnt = PR_Sync(mFD);
+ if (cnt == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::StreamStatus() {
+ switch (mState) {
+ case eUnitialized:
+ MOZ_CRASH("This should not happen.");
+ return NS_ERROR_FAILURE;
+
+ case eDeferredOpen:
+ return NS_OK;
+
+ case eOpened:
+ MOZ_ASSERT(mFD);
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+
+ case eClosed:
+ MOZ_ASSERT(!mFD);
+ return NS_BASE_STREAM_CLOSED;
+
+ case eError:
+ return mErrorValue;
+ }
+
+ MOZ_CRASH("Invalid mState value.");
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsFileStreamBase::Write(const char* buf, uint32_t count,
+ uint32_t* result) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t cnt = PR_Write(mFD, buf, count);
+ if (cnt == -1) {
+ return NS_ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::WriteFrom(nsIInputStream* inStr, uint32_t count,
+ uint32_t* _retval) {
+ MOZ_ASSERT_UNREACHABLE("WriteFrom (see source comment)");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+nsresult nsFileStreamBase::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+nsresult nsFileStreamBase::MaybeOpen(nsIFile* aFile, int32_t aIoFlags,
+ int32_t aPerm, bool aDeferred) {
+ NS_ENSURE_STATE(aFile);
+
+ mOpenParams.ioFlags = aIoFlags;
+ mOpenParams.perm = aPerm;
+
+ if (aDeferred) {
+ // Clone the file, as it may change between now and the deferred open
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aFile->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOpenParams.localFile = std::move(file);
+ NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED);
+
+ mState = eDeferredOpen;
+ return NS_OK;
+ }
+
+ mOpenParams.localFile = aFile;
+
+ // Following call open() at main thread.
+ // Main thread might be blocked, while open a remote file.
+ return DoOpen();
+}
+
+void nsFileStreamBase::CleanUpOpen() { mOpenParams.localFile = nullptr; }
+
+nsresult nsFileStreamBase::DoOpen() {
+ MOZ_ASSERT(mState == eDeferredOpen || mState == eUnitialized ||
+ mState == eClosed);
+ NS_ASSERTION(!mFD, "Already have a file descriptor!");
+ NS_ASSERTION(mOpenParams.localFile, "Must have a file to open");
+
+ PRFileDesc* fd;
+ nsresult rv;
+
+ if (mOpenParams.ioFlags & PR_CREATE_FILE) {
+ nsCOMPtr<nsIFile> parent;
+ mOpenParams.localFile->GetParent(getter_AddRefs(parent));
+
+ // Result doesn't need to be checked. If the file's parent path does not
+ // exist, make it. If it does exist, do nothing.
+ if (parent) {
+ mozilla::Unused << parent->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ }
+ }
+
+#ifdef XP_WIN
+ if (mBehaviorFlags & nsIFileInputStream::SHARE_DELETE) {
+ nsCOMPtr<nsILocalFileWin> file = do_QueryInterface(mOpenParams.localFile);
+ MOZ_ASSERT(file);
+
+ rv = file->OpenNSPRFileDescShareDelete(mOpenParams.ioFlags,
+ mOpenParams.perm, &fd);
+ } else
+#endif // XP_WIN
+ {
+ rv = mOpenParams.localFile->OpenNSPRFileDesc(mOpenParams.ioFlags,
+ mOpenParams.perm, &fd);
+ }
+
+ if (rv == NS_OK && IOActivityMonitor::IsActive()) {
+ auto nativePath = mOpenParams.localFile->NativePath();
+ if (!nativePath.IsEmpty()) {
+// registering the file to the activity monitor
+#ifdef XP_WIN
+ // 16 bits unicode
+ IOActivityMonitor::MonitorFile(
+ fd, NS_ConvertUTF16toUTF8(nativePath.get()).get());
+#else
+ // 8 bit unicode
+ IOActivityMonitor::MonitorFile(fd, nativePath.get());
+#endif
+ }
+ }
+
+ CleanUpOpen();
+
+ if (NS_FAILED(rv)) {
+ mState = eError;
+ mErrorValue = rv;
+ return rv;
+ }
+
+ mFD = fd;
+ mState = eOpened;
+
+ return NS_OK;
+}
+
+nsresult nsFileStreamBase::DoPendingOpen() {
+ switch (mState) {
+ case eUnitialized:
+ MOZ_CRASH("This should not happen.");
+ return NS_ERROR_FAILURE;
+
+ case eDeferredOpen:
+ return DoOpen();
+
+ case eOpened:
+ MOZ_ASSERT(mFD);
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+
+ case eClosed:
+ MOZ_ASSERT(!mFD);
+ return NS_BASE_STREAM_CLOSED;
+
+ case eError:
+ return mErrorValue;
+ }
+
+ MOZ_CRASH("Invalid mState value.");
+ return NS_ERROR_FAILURE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileInputStream
+
+NS_IMPL_ADDREF_INHERITED(nsFileInputStream, nsFileStreamBase)
+NS_IMPL_RELEASE_INHERITED(nsFileInputStream, nsFileStreamBase)
+
+NS_IMPL_CLASSINFO(nsFileInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_LOCALFILEINPUTSTREAM_CID)
+
+NS_INTERFACE_MAP_BEGIN(nsFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIFileInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsILineInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+ NS_IMPL_QUERY_CLASSINFO(nsFileInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, IsCloneable())
+NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream, nsIInputStream,
+ nsIFileInputStream, nsISeekableStream,
+ nsITellableStream, nsILineInputStream)
+
+nsresult nsFileInputStream::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsFileInputStream> stream = new nsFileInputStream();
+ return stream->QueryInterface(aIID, aResult);
+}
+
+nsresult nsFileInputStream::Open(nsIFile* aFile, int32_t aIOFlags,
+ int32_t aPerm) {
+ nsresult rv = NS_OK;
+
+ // If the previous file is open, close it
+ if (mFD) {
+ rv = Close();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Open the file
+ if (aIOFlags == -1) aIOFlags = PR_RDONLY;
+ if (aPerm == -1) aPerm = 0;
+
+ return MaybeOpen(aFile, aIOFlags, aPerm,
+ mBehaviorFlags & nsIFileInputStream::DEFER_OPEN);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Init(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
+ int32_t aBehaviorFlags) {
+ NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
+ NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = aBehaviorFlags;
+ mState = eUnitialized;
+
+ mFile = aFile;
+ mIOFlags = aIOFlags;
+ mPerm = aPerm;
+
+ return Open(aFile, aIOFlags, aPerm);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Close() {
+ // If this stream has already been closed, do nothing.
+ if (mState == eClosed) {
+ return NS_OK;
+ }
+
+ // Get the cache position at the time the file was close. This allows
+ // NS_SEEK_CUR on a closed file that has been opened with
+ // REOPEN_ON_REWIND.
+ if (mBehaviorFlags & REOPEN_ON_REWIND) {
+ // Get actual position. Not one modified by subclasses
+ nsFileStreamBase::Tell(&mCachedPosition);
+ }
+
+ // explicitly clear mLineBuffer in case this stream is reopened
+ mLineBuffer = nullptr;
+ return nsFileStreamBase::Close();
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ nsresult rv = nsFileStreamBase::Read(aBuf, aCount, _retval);
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ // Don't warn if this is a deffered file not found.
+ return rv;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Check if we're at the end of file and need to close
+ if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) {
+ Close();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult) {
+ if (!mLineBuffer) {
+ mLineBuffer = mozilla::MakeUnique<nsLineBuffer<char>>();
+ }
+ return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Seek(int32_t aWhence, int64_t aOffset) {
+ return SeekInternal(aWhence, aOffset);
+}
+
+nsresult nsFileInputStream::SeekInternal(int32_t aWhence, int64_t aOffset,
+ bool aClearBuf) {
+ nsresult rv = DoPendingOpen();
+ if (rv != NS_OK && rv != NS_BASE_STREAM_CLOSED) {
+ return rv;
+ }
+
+ if (aClearBuf) {
+ mLineBuffer = nullptr;
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ if (mBehaviorFlags & REOPEN_ON_REWIND) {
+ rv = Open(mFile, mIOFlags, mPerm);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the file was closed, and we do a relative seek, use the
+ // position we cached when we closed the file to seek to the right
+ // location.
+ if (aWhence == NS_SEEK_CUR) {
+ aWhence = NS_SEEK_SET;
+ aOffset += mCachedPosition;
+ }
+ // If we're trying to seek to the start then we're done, so
+ // return early to avoid Seek from calling DoPendingOpen and
+ // opening the underlying file earlier than necessary.
+ if (aWhence == NS_SEEK_SET && aOffset == 0) {
+ return NS_OK;
+ }
+ } else {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ }
+
+ return nsFileStreamBase::Seek(aWhence, aOffset);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Tell(int64_t* aResult) {
+ return nsFileStreamBase::Tell(aResult);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Available(uint64_t* aResult) {
+ return nsFileStreamBase::Available(aResult);
+}
+
+NS_IMETHODIMP
+nsFileInputStream::StreamStatus() { return nsFileStreamBase::StreamStatus(); }
+
+void nsFileInputStream::SerializedComplexity(uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ uint32_t* aPipes,
+ uint32_t* aTransferables) {
+ *aTransferables = 1;
+}
+
+void nsFileInputStream::Serialize(InputStreamParams& aParams, uint32_t aMaxSize,
+ uint32_t* aSizeUsed) {
+ MOZ_ASSERT(aSizeUsed);
+ *aSizeUsed = 0;
+
+ FileInputStreamParams params;
+
+ if (NS_SUCCEEDED(DoPendingOpen())) {
+ MOZ_ASSERT(mFD);
+ FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
+ NS_ASSERTION(fd, "This should never be null!");
+
+ params.fileDescriptor() = FileDescriptor(fd);
+
+ Close();
+ } else {
+ NS_WARNING(
+ "This file has not been opened (or could not be opened). "
+ "Sending an invalid file descriptor to the other process!");
+
+ params.fileDescriptor() = FileDescriptor();
+ }
+
+ int32_t behaviorFlags = mBehaviorFlags;
+
+ // The receiving process (or thread) is going to have an open file
+ // descriptor automatically so transferring this flag is meaningless.
+ behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
+
+ params.behaviorFlags() = behaviorFlags;
+ params.ioFlags() = mIOFlags;
+
+ aParams = params;
+}
+
+bool nsFileInputStream::Deserialize(const InputStreamParams& aParams) {
+ NS_ASSERTION(!mFD, "Already have a file descriptor?!");
+ NS_ASSERTION(mState == nsFileStreamBase::eUnitialized, "Deferring open?!");
+ NS_ASSERTION(!mFile, "Should never have a file here!");
+ NS_ASSERTION(!mPerm, "This should always be 0!");
+
+ if (aParams.type() != InputStreamParams::TFileInputStreamParams) {
+ NS_WARNING("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const FileInputStreamParams& params = aParams.get_FileInputStreamParams();
+
+ const FileDescriptor& fd = params.fileDescriptor();
+
+ if (fd.IsValid()) {
+ auto rawFD = fd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return false;
+ }
+ mFD = fileDesc;
+ mState = eOpened;
+ } else {
+ NS_WARNING("Received an invalid file descriptor!");
+ mState = eError;
+ mErrorValue = NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ mBehaviorFlags = params.behaviorFlags();
+
+ if (!XRE_IsParentProcess()) {
+ // A child process shouldn't close when it reads the end because it will
+ // not be able to reopen the file later.
+ mBehaviorFlags &= ~nsIFileInputStream::CLOSE_ON_EOF;
+
+ // A child process will not be able to reopen the file so this flag is
+ // meaningless.
+ mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
+ }
+
+ mIOFlags = params.ioFlags();
+
+ return true;
+}
+
+bool nsFileInputStream::IsCloneable() const {
+ // This inputStream is cloneable only if has been created using Init() and
+ // it owns a nsIFile. This is not true when it is deserialized from IPC.
+ return XRE_IsParentProcess() && mFile;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::GetCloneable(bool* aCloneable) {
+ *aCloneable = IsCloneable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::Clone(nsIInputStream** aResult) {
+ MOZ_ASSERT(IsCloneable());
+ return NS_NewLocalFileInputStream(aResult, mFile, mIOFlags, mPerm,
+ mBehaviorFlags);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileOutputStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream, nsFileStreamBase,
+ nsIOutputStream, nsIFileOutputStream)
+
+nsresult nsFileOutputStream::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsFileOutputStream> stream = new nsFileOutputStream();
+ return stream->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
+ NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = behaviorFlags;
+ mState = eUnitialized;
+
+ if (ioFlags == -1) ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
+ if (perm <= 0) perm = 0664;
+
+ return MaybeOpen(file, ioFlags, perm,
+ mBehaviorFlags & nsIFileOutputStream::DEFER_OPEN);
+}
+
+nsresult nsFileOutputStream::InitWithFileDescriptor(
+ const mozilla::ipc::FileDescriptor& aFd) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
+ NS_ERROR_ALREADY_INITIALIZED);
+
+ if (aFd.IsValid()) {
+ auto rawFD = aFd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return NS_ERROR_FAILURE;
+ }
+ mFD = fileDesc;
+ mState = eOpened;
+ } else {
+ mState = eError;
+ mErrorValue = NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileOutputStream::Preallocate(int64_t aLength) {
+ if (!mFD) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mozilla::fallocate(mFD, aLength)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAtomicFileOutputStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream, nsFileOutputStream,
+ nsISafeOutputStream, nsIOutputStream,
+ nsIFileOutputStream)
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags) {
+ // While `PR_APPEND` is not supported, `-1` is used as `ioFlags` parameter
+ // in some places, and `PR_APPEND | PR_TRUNCATE` does not require appending
+ // to existing file. So, throw an exception only if `PR_APPEND` is
+ // explicitly specified without `PR_TRUNCATE`.
+ if ((ioFlags & PR_APPEND) && !(ioFlags & PR_TRUNCATE)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return nsFileOutputStream::Init(file, ioFlags, perm, behaviorFlags);
+}
+
+nsresult nsAtomicFileOutputStream::DoOpen() {
+ // Make sure mOpenParams.localFile will be empty if we bail somewhere in
+ // this function
+ nsCOMPtr<nsIFile> file;
+ file.swap(mOpenParams.localFile);
+
+ if (!file) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ nsresult rv = file->Exists(&mTargetFileExists);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Can't tell if target file exists");
+ mTargetFileExists =
+ true; // Safer to assume it exists - we just do more work.
+ }
+
+ // follow symlinks, for two reasons:
+ // 1) if a user has deliberately set up a profile file as a symlink, we
+ // honor it
+ // 2) to make the MoveToNative() in Finish() an atomic operation (which may
+ // not be the case if moving across directories on different
+ // filesystems).
+ nsCOMPtr<nsIFile> tempResult;
+ rv = file->Clone(getter_AddRefs(tempResult));
+ if (NS_SUCCEEDED(rv) && mTargetFileExists) {
+ tempResult->Normalize();
+ }
+
+ if (NS_SUCCEEDED(rv) && mTargetFileExists) {
+ // Abort if |file| is not writable; it won't work as an output stream.
+ bool isWritable;
+ if (NS_SUCCEEDED(file->IsWritable(&isWritable)) && !isWritable) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
+ uint32_t origPerm;
+ if (NS_FAILED(file->GetPermissions(&origPerm))) {
+ NS_ERROR("Can't get permissions of target file");
+ origPerm = mOpenParams.perm;
+ }
+
+ // XXX What if |perm| is more restrictive then |origPerm|?
+ // This leaves the user supplied permissions as they were.
+ rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ // nsFileOutputStream::DoOpen will work on the temporary file, so we
+ // prepare it and place it in mOpenParams.localFile.
+ mOpenParams.localFile = tempResult;
+ mTempFile = tempResult;
+ mTargetFile = file;
+ rv = nsFileOutputStream::DoOpen();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Close() {
+ nsresult rv = nsFileOutputStream::Close();
+
+ // the consumer doesn't want the original file overwritten -
+ // so clean up by removing the temp file.
+ if (mTempFile) {
+ mTempFile->Remove(false);
+ mTempFile = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Finish() {
+ nsresult rv = nsFileOutputStream::Close();
+
+ // if there is no temp file, don't try to move it over the original target.
+ // It would destroy the targetfile if close() is called twice.
+ if (!mTempFile) return rv;
+
+ // Only overwrite if everything was ok, and the temp file could be closed.
+ if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) {
+ NS_ENSURE_STATE(mTargetFile);
+
+ if (!mTargetFileExists) {
+ // If the target file did not exist when we were initialized, then the
+ // temp file we gave out was actually a reference to the target file.
+ // since we succeeded in writing to the temp file (and hence succeeded
+ // in writing to the target file), there is nothing more to do.
+#ifdef DEBUG
+ bool equal;
+ if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal) {
+ NS_WARNING("mTempFile not equal to mTargetFile");
+ }
+#endif
+ } else {
+ nsAutoString targetFilename;
+ rv = mTargetFile->GetLeafName(targetFilename);
+ if (NS_SUCCEEDED(rv)) {
+ // This will replace target.
+ rv = mTempFile->MoveTo(nullptr, targetFilename);
+ if (NS_FAILED(rv)) mTempFile->Remove(false);
+ }
+ }
+ } else {
+ mTempFile->Remove(false);
+
+ // if writing failed, propagate the failure code to the caller.
+ if (NS_FAILED(mWriteResult)) rv = mWriteResult;
+ }
+ mTempFile = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAtomicFileOutputStream::Write(const char* buf, uint32_t count,
+ uint32_t* result) {
+ nsresult rv = nsFileOutputStream::Write(buf, count, result);
+ if (NS_SUCCEEDED(mWriteResult)) {
+ if (NS_FAILED(rv)) {
+ mWriteResult = rv;
+ } else if (count != *result) {
+ mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
+ }
+
+ if (NS_FAILED(mWriteResult) && count > 0) {
+ NS_WARNING("writing to output stream failed! data may be lost");
+ }
+ }
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsSafeFileOutputStream
+
+NS_IMETHODIMP
+nsSafeFileOutputStream::Finish() {
+ (void)Flush();
+ return nsAtomicFileOutputStream::Finish();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileRandomAccessStream
+
+nsresult nsFileRandomAccessStream::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream();
+ return stream->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileRandomAccessStream, nsFileStreamBase,
+ nsIRandomAccessStream, nsIFileRandomAccessStream,
+ nsIInputStream, nsIOutputStream)
+
+NS_IMETHODIMP
+nsFileRandomAccessStream::GetInputStream(nsIInputStream** aInputStream) {
+ nsCOMPtr<nsIInputStream> inputStream(this);
+
+ inputStream.forget(aInputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileRandomAccessStream::GetOutputStream(nsIOutputStream** aOutputStream) {
+ nsCOMPtr<nsIOutputStream> outputStream(this);
+
+ outputStream.forget(aOutputStream);
+ return NS_OK;
+}
+
+nsIInputStream* nsFileRandomAccessStream::InputStream() { return this; }
+
+nsIOutputStream* nsFileRandomAccessStream::OutputStream() { return this; }
+
+RandomAccessStreamParams nsFileRandomAccessStream::Serialize(
+ nsIInterfaceRequestor* aCallbacks) {
+ FileRandomAccessStreamParams params;
+
+ if (NS_SUCCEEDED(DoPendingOpen())) {
+ MOZ_ASSERT(mFD);
+ FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
+ MOZ_ASSERT(fd, "This should never be null!");
+
+ params.fileDescriptor() = FileDescriptor(fd);
+
+ Close();
+ } else {
+ NS_WARNING(
+ "This file has not been opened (or could not be opened). "
+ "Sending an invalid file descriptor to the other process!");
+
+ params.fileDescriptor() = FileDescriptor();
+ }
+
+ int32_t behaviorFlags = mBehaviorFlags;
+
+ // The receiving process (or thread) is going to have an open file
+ // descriptor automatically so transferring this flag is meaningless.
+ behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
+
+ params.behaviorFlags() = behaviorFlags;
+
+ return params;
+}
+
+bool nsFileRandomAccessStream::Deserialize(
+ RandomAccessStreamParams& aStreamParams) {
+ MOZ_ASSERT(!mFD, "Already have a file descriptor?!");
+ MOZ_ASSERT(mState == nsFileStreamBase::eUnitialized, "Deferring open?!");
+
+ if (aStreamParams.type() !=
+ RandomAccessStreamParams::TFileRandomAccessStreamParams) {
+ NS_WARNING("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const FileRandomAccessStreamParams& params =
+ aStreamParams.get_FileRandomAccessStreamParams();
+
+ const FileDescriptor& fd = params.fileDescriptor();
+
+ if (fd.IsValid()) {
+ auto rawFD = fd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return false;
+ }
+ mFD = fileDesc;
+ mState = eOpened;
+ } else {
+ NS_WARNING("Received an invalid file descriptor!");
+ mState = eError;
+ mErrorValue = NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ mBehaviorFlags = params.behaviorFlags();
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsFileRandomAccessStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
+ NS_ERROR_ALREADY_INITIALIZED);
+
+ mBehaviorFlags = behaviorFlags;
+ mState = eUnitialized;
+
+ if (ioFlags == -1) ioFlags = PR_RDWR;
+ if (perm <= 0) perm = 0;
+
+ return MaybeOpen(file, ioFlags, perm,
+ mBehaviorFlags & nsIFileRandomAccessStream::DEFER_OPEN);
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/base/nsFileStreams.h b/netwerk/base/nsFileStreams.h
new file mode 100644
index 0000000000..d402bffb24
--- /dev/null
+++ b/netwerk/base/nsFileStreams.h
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFileStreams_h__
+#define nsFileStreams_h__
+
+#include "mozilla/UniquePtr.h"
+#include "nsIFileStreams.h"
+#include "nsIFile.h"
+#include "nsICloneableInputStream.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIRandomAccessStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsILineInputStream.h"
+#include "nsCOMPtr.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsReadLine.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace ipc {
+class FileDescriptor;
+} // namespace ipc
+} // namespace mozilla
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileStreamBase : public nsISeekableStream, public nsIFileMetadata {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+ NS_DECL_NSIFILEMETADATA
+
+ nsFileStreamBase() = default;
+
+ protected:
+ virtual ~nsFileStreamBase();
+
+ nsresult Close();
+ nsresult Available(uint64_t* aResult);
+ nsresult Read(char* aBuf, uint32_t aCount, uint32_t* aResult);
+ nsresult ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval);
+ nsresult IsNonBlocking(bool* aNonBlocking);
+ nsresult Flush();
+ nsresult StreamStatus();
+ nsresult Write(const char* aBuf, uint32_t aCount, uint32_t* result);
+ nsresult WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval);
+ nsresult WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval);
+
+ PRFileDesc* mFD{nullptr};
+
+ /**
+ * Flags describing our behavior. See the IDL file for possible values.
+ */
+ int32_t mBehaviorFlags{0};
+
+ enum {
+ // This is the default value. It will be changed by Deserialize or Init.
+ eUnitialized,
+ // The opening has been deferred. See DEFER_OPEN.
+ eDeferredOpen,
+ // The file has been opened. mFD is not null.
+ eOpened,
+ // The file has been closed. mFD is null.
+ eClosed,
+ // Something bad happen in the Open() or in Deserialize(). The actual
+ // error value is stored in mErrorValue.
+ eError
+ } mState{eUnitialized};
+
+ struct OpenParams {
+ nsCOMPtr<nsIFile> localFile;
+ int32_t ioFlags = 0;
+ int32_t perm = 0;
+ };
+
+ /**
+ * Data we need to do an open.
+ */
+ OpenParams mOpenParams;
+
+ nsresult mErrorValue{NS_ERROR_FAILURE};
+
+ /**
+ * Prepares the data we need to open the file, and either does the open now
+ * by calling DoOpen(), or leaves it to be opened later by a call to
+ * DoPendingOpen().
+ */
+ nsresult MaybeOpen(nsIFile* aFile, int32_t aIoFlags, int32_t aPerm,
+ bool aDeferred);
+
+ /**
+ * Cleans up data prepared in MaybeOpen.
+ */
+ void CleanUpOpen();
+
+ /**
+ * Open the file. This is called either from MaybeOpen (during Init)
+ * or from DoPendingOpen (if DEFER_OPEN is used when initializing this
+ * stream). The default behavior of DoOpen is to open the file and save the
+ * file descriptor.
+ */
+ virtual nsresult DoOpen();
+
+ /**
+ * Based on mState, this method does the opening, return an error, or do
+ * nothing. If the return value is not NS_OK, please, return it back to the
+ * callee.
+ */
+ inline nsresult DoPendingOpen();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// nsFileInputStream is cloneable only on the parent process because only there
+// it can open the same file multiple times.
+
+class nsFileInputStream : public nsFileStreamBase,
+ public nsIFileInputStream,
+ public nsILineInputStream,
+ public nsIIPCSerializableInputStream,
+ public nsICloneableInputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILEINPUTSTREAM
+ NS_DECL_NSILINEINPUTSTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+
+ NS_IMETHOD Close() override;
+ NS_IMETHOD Tell(int64_t* aResult) override;
+ NS_IMETHOD Available(uint64_t* _retval) override;
+ NS_IMETHOD StreamStatus() override;
+ NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override;
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override {
+ return nsFileStreamBase::ReadSegments(aWriter, aClosure, aCount, _retval);
+ }
+ NS_IMETHOD IsNonBlocking(bool* _retval) override {
+ return nsFileStreamBase::IsNonBlocking(_retval);
+ }
+
+ // Overrided from nsFileStreamBase
+ NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override;
+
+ nsFileInputStream() : mLineBuffer(nullptr) {}
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ protected:
+ virtual ~nsFileInputStream() = default;
+
+ nsresult SeekInternal(int32_t aWhence, int64_t aOffset,
+ bool aClearBuf = true);
+
+ mozilla::UniquePtr<nsLineBuffer<char>> mLineBuffer;
+
+ /**
+ * The file being opened.
+ */
+ nsCOMPtr<nsIFile> mFile;
+ /**
+ * The IO flags passed to Init() for the file open.
+ */
+ int32_t mIOFlags{0};
+ /**
+ * The permissions passed to Init() for the file open.
+ */
+ int32_t mPerm{0};
+
+ /**
+ * Cached position for Tell for automatically reopening streams.
+ */
+ int64_t mCachedPosition{0};
+
+ protected:
+ /**
+ * Internal, called to open a file. Parameters are the same as their
+ * Init() analogues.
+ */
+ nsresult Open(nsIFile* file, int32_t ioFlags, int32_t perm);
+
+ bool IsCloneable() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileOutputStream : public nsFileStreamBase, public nsIFileOutputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILEOUTPUTSTREAM
+ NS_FORWARD_NSIOUTPUTSTREAM(nsFileStreamBase::)
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+ nsresult InitWithFileDescriptor(const mozilla::ipc::FileDescriptor& aFd);
+
+ protected:
+ virtual ~nsFileOutputStream() = default;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A safe file output stream that overwrites the destination file only
+ * once writing is complete. This protects against incomplete writes
+ * due to the process or the thread being interrupted or crashed.
+ */
+class nsAtomicFileOutputStream : public nsFileOutputStream,
+ public nsISafeOutputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSISAFEOUTPUTSTREAM
+
+ nsAtomicFileOutputStream() = default;
+
+ virtual nsresult DoOpen() override;
+
+ NS_IMETHOD Close() override;
+ NS_IMETHOD Write(const char* buf, uint32_t count, uint32_t* result) override;
+ NS_IMETHOD Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags) override;
+
+ protected:
+ virtual ~nsAtomicFileOutputStream() = default;
+
+ nsCOMPtr<nsIFile> mTargetFile;
+ nsCOMPtr<nsIFile> mTempFile;
+
+ bool mTargetFileExists{true};
+ nsresult mWriteResult{NS_OK}; // Internally set in Write()
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A safe file output stream that overwrites the destination file only
+ * once writing + flushing is complete. This protects against more
+ * classes of software/hardware errors than nsAtomicFileOutputStream,
+ * at the expense of being more costly to the disk, OS and battery.
+ */
+class nsSafeFileOutputStream : public nsAtomicFileOutputStream {
+ public:
+ NS_IMETHOD Finish() override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileRandomAccessStream : public nsFileStreamBase,
+ public nsIFileRandomAccessStream,
+ public nsIInputStream,
+ public nsIOutputStream {
+ public:
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSITELLABLESTREAM(nsFileStreamBase::)
+ NS_FORWARD_NSISEEKABLESTREAM(nsFileStreamBase::)
+ NS_DECL_NSIRANDOMACCESSSTREAM
+ NS_DECL_NSIFILERANDOMACCESSSTREAM
+ NS_FORWARD_NSIINPUTSTREAM(nsFileStreamBase::)
+
+ // Can't use NS_FORWARD_NSIOUTPUTSTREAM due to overlapping methods
+ // Close() and IsNonBlocking()
+ NS_IMETHOD Flush() override { return nsFileStreamBase::Flush(); }
+ NS_IMETHOD Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) override {
+ return nsFileStreamBase::Write(aBuf, aCount, _retval);
+ }
+ NS_IMETHOD WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval) override {
+ return nsFileStreamBase::WriteFrom(aFromStream, aCount, _retval);
+ }
+ NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override {
+ return nsFileStreamBase::WriteSegments(aReader, aClosure, aCount, _retval);
+ }
+
+ protected:
+ virtual ~nsFileRandomAccessStream() = default;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif // nsFileStreams_h__
diff --git a/netwerk/base/nsIArrayBufferInputStream.idl b/netwerk/base/nsIArrayBufferInputStream.idl
new file mode 100644
index 0000000000..9ba1dc73fa
--- /dev/null
+++ b/netwerk/base/nsIArrayBufferInputStream.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+%{C++
+#include "mozilla/UniquePtr.h"
+%}
+native Bytes(mozilla::UniquePtr<uint8_t[]>);
+
+/**
+ * nsIArrayBufferInputStream
+ *
+ * Provides scriptable methods for initializing a nsIInputStream
+ * implementation with an ArrayBuffer.
+ */
+[scriptable, builtinclass, uuid(3014dde6-aa1c-41db-87d0-48764a3710f6)]
+interface nsIArrayBufferInputStream : nsIInputStream
+{
+ /**
+ * SetData - assign an ArrayBuffer to the input stream.
+ *
+ * @param buffer - stream data
+ * @param byteOffset - stream data offset
+ * @param byteLen - stream data length
+ */
+ [binaryname(SetDataFromJS)]
+ void setData(in jsval buffer, in uint64_t byteOffset, in uint64_t byteLen);
+
+ /**
+ * SetData - assign data to the input stream.
+ *
+ * @param aBytes - stream data
+ * @param byteLen - stream data length
+ */
+ [noscript, nostdcall, binaryname(SetData)]
+ void setDataNative(in Bytes bytes, in uint64_t byteLen);
+};
diff --git a/netwerk/base/nsIAsyncStreamCopier.idl b/netwerk/base/nsIAsyncStreamCopier.idl
new file mode 100644
index 0000000000..633fe72b6e
--- /dev/null
+++ b/netwerk/base/nsIAsyncStreamCopier.idl
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIRequestObserver;
+interface nsIEventTarget;
+
+// You should prefer nsIAsyncStreamCopier2
+[scriptable, uuid(5a19ca27-e041-4aca-8287-eb248d4c50c0)]
+interface nsIAsyncStreamCopier : nsIRequest
+{
+ /**
+ * Initialize the stream copier.
+ *
+ * @param aSource
+ * contains the data to be copied.
+ * @param aSink
+ * specifies the destination for the data.
+ * @param aTarget
+ * specifies the thread on which the copy will occur. a null value
+ * is permitted and will cause the copy to occur on an unspecified
+ * background thread.
+ * @param aSourceBuffered
+ * true if aSource implements ReadSegments.
+ * @param aSinkBuffered
+ * true if aSink implements WriteSegments.
+ * @param aChunkSize
+ * specifies how many bytes to read/write at a time. this controls
+ * the granularity of the copying. it should match the segment size
+ * of the "buffered" streams involved.
+ * @param aCloseSource
+ * true if aSource should be closed after copying.
+ * @param aCloseSink
+ * true if aSink should be closed after copying.
+ *
+ * NOTE: at least one of the streams must be buffered. If you do not know
+ * whether your streams are buffered, you should use nsIAsyncStreamCopier2
+ * instead.
+ */
+ void init(in nsIInputStream aSource,
+ in nsIOutputStream aSink,
+ in nsIEventTarget aTarget,
+ in boolean aSourceBuffered,
+ in boolean aSinkBuffered,
+ in unsigned long aChunkSize,
+ in boolean aCloseSource,
+ in boolean aCloseSink);
+
+ /**
+ * asyncCopy triggers the start of the copy. The observer will be notified
+ * when the copy completes.
+ *
+ * @param aObserver
+ * receives notifications.
+ * @param aObserverContext
+ * passed to observer methods.
+ */
+ void asyncCopy(in nsIRequestObserver aObserver,
+ in nsISupports aObserverContext);
+};
diff --git a/netwerk/base/nsIAsyncStreamCopier2.idl b/netwerk/base/nsIAsyncStreamCopier2.idl
new file mode 100644
index 0000000000..7de793f51e
--- /dev/null
+++ b/netwerk/base/nsIAsyncStreamCopier2.idl
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIRequestObserver;
+interface nsIEventTarget;
+
+[scriptable, uuid(a5b2decf-4ede-4801-8b38-e5fe5db46bf2)]
+interface nsIAsyncStreamCopier2 : nsIRequest
+{
+ /**
+ * Initialize the stream copier.
+ *
+ * If neither the source nor the sink are buffered, buffering will
+ * be automatically added to the sink.
+ *
+ *
+ * @param aSource
+ * contains the data to be copied.
+ * @param aSink
+ * specifies the destination for the data.
+ * @param aTarget
+ * specifies the thread on which the copy will occur. a null value
+ * is permitted and will cause the copy to occur on an unspecified
+ * background thread.
+ * @param aChunkSize
+ * specifies how many bytes to read/write at a time. this controls
+ * the granularity of the copying. it should match the segment size
+ * of the "buffered" streams involved.
+ * @param aCloseSource
+ * true if aSource should be closed after copying (this is generally
+ * the desired behavior).
+ * @param aCloseSink
+ * true if aSink should be closed after copying (this is generally
+ * the desired behavior).
+ */
+ void init(in nsIInputStream aSource,
+ in nsIOutputStream aSink,
+ in nsIEventTarget aTarget,
+ in unsigned long aChunkSize,
+ in boolean aCloseSource,
+ in boolean aCloseSink);
+
+ /**
+ * asyncCopy triggers the start of the copy. The observer will be notified
+ * when the copy completes.
+ *
+ * @param aObserver
+ * receives notifications.
+ * @param aObserverContext
+ * passed to observer methods.
+ */
+ void asyncCopy(in nsIRequestObserver aObserver,
+ in nsISupports aObserverContext);
+};
diff --git a/netwerk/base/nsIAsyncVerifyRedirectCallback.idl b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl
new file mode 100644
index 0000000000..8c81a142f8
--- /dev/null
+++ b/netwerk/base/nsIAsyncVerifyRedirectCallback.idl
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(8d171460-a716-41f1-92be-8c659db39b45)]
+interface nsIAsyncVerifyRedirectCallback : nsISupports
+{
+ /**
+ * Complement to nsIChannelEventSink asynchronous callback. The result of
+ * the redirect decision is passed through this callback.
+ *
+ * @param result
+ * Result of the redirect veto decision. If FAILED the redirect has been
+ * vetoed. If SUCCEEDED the redirect has been allowed by all consumers.
+ */
+ void onRedirectVerifyCallback(in nsresult result);
+};
diff --git a/netwerk/base/nsIAuthInformation.idl b/netwerk/base/nsIAuthInformation.idl
new file mode 100644
index 0000000000..a9a74891b1
--- /dev/null
+++ b/netwerk/base/nsIAuthInformation.idl
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * A object that hold authentication information. The caller of
+ * nsIAuthPrompt2::promptUsernameAndPassword or
+ * nsIAuthPrompt2::promptPasswordAsync provides an object implementing this
+ * interface; the prompt implementation can then read the values here to prefill
+ * the dialog. After the user entered the authentication information, it should
+ * set the attributes of this object to indicate to the caller what was entered
+ * by the user.
+ */
+[scriptable, uuid(0d73639c-2a92-4518-9f92-28f71fea5f20)]
+interface nsIAuthInformation : nsISupports
+{
+ /** @name Flags */
+ /* @{ */
+ /**
+ * This dialog belongs to a network host.
+ */
+ const uint32_t AUTH_HOST = 1;
+
+ /**
+ * This dialog belongs to a proxy.
+ */
+ const uint32_t AUTH_PROXY = 2;
+
+ /**
+ * This dialog needs domain information. The user interface should show a
+ * domain field, prefilled with the domain attribute's value.
+ */
+ const uint32_t NEED_DOMAIN = 4;
+
+ /**
+ * This dialog only asks for password information. Authentication prompts
+ * SHOULD NOT show a username field. Attempts to change the username field
+ * will have no effect. nsIAuthPrompt2 implementations should, however, show
+ * its initial value to the user in some form. For example, a paragraph in
+ * the dialog might say "Please enter your password for user jsmith at
+ * server intranet".
+ *
+ * This flag is mutually exclusive with #NEED_DOMAIN.
+ */
+ const uint32_t ONLY_PASSWORD = 8;
+
+ /**
+ * We have already tried to log in for this channel
+ * (with auth values from a previous promptAuth call),
+ * but it failed, so we now ask the user to provide a new, correct login.
+ *
+ * @see also RFC 2616, Section 10.4.2
+ */
+ const uint32_t PREVIOUS_FAILED = 16;
+
+ /**
+ * A cross-origin sub-resource requests an authentication.
+ * The message presented to users must reflect that.
+ */
+ const uint32_t CROSS_ORIGIN_SUB_RESOURCE = 32;
+ /* @} */
+
+ /**
+ * Flags describing this dialog. A bitwise OR of the flag values
+ * above.
+ *
+ * It is possible that neither #AUTH_HOST nor #AUTH_PROXY are set.
+ *
+ * Auth prompts should ignore flags they don't understand; especially, they
+ * should not throw an exception because of an unsupported flag.
+ */
+ readonly attribute unsigned long flags;
+
+ /**
+ * The server-supplied realm of the authentication as defined in RFC 2617.
+ * Can be the empty string if the protocol does not support realms.
+ * Otherwise, this is a human-readable string like "Secret files".
+ */
+ readonly attribute AString realm;
+
+ /**
+ * The authentication scheme used for this request, if applicable. If the
+ * protocol for this authentication does not support schemes, this will be
+ * the empty string. Otherwise, this will be a string such as "basic" or
+ * "digest". This string will always be in lowercase.
+ */
+ readonly attribute AUTF8String authenticationScheme;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * On return, this parameter should contain the username entered by
+ * the user.
+ * This field can only be changed if the #ONLY_PASSWORD flag is not set.
+ */
+ attribute AString username;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * The password should not be shown in clear.
+ * On return, this parameter should contain the password entered by
+ * the user.
+ */
+ attribute AString password;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * On return, this parameter should contain the domain entered by
+ * the user.
+ * This attribute is only used if flags include #NEED_DOMAIN.
+ */
+ attribute AString domain;
+};
diff --git a/netwerk/base/nsIAuthModule.idl b/netwerk/base/nsIAuthModule.idl
new file mode 100644
index 0000000000..87985f2a57
--- /dev/null
+++ b/netwerk/base/nsIAuthModule.idl
@@ -0,0 +1,146 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+[uuid(6e35dbc0-49ef-4e2c-b1ea-b72ec64450a2)]
+interface nsIAuthModule : nsISupports
+{
+ /**
+ * Default behavior.
+ */
+ const unsigned long REQ_DEFAULT = 0;
+
+ /**
+ * Client and server will be authenticated.
+ */
+ const unsigned long REQ_MUTUAL_AUTH = (1 << 0);
+
+ /**
+ * The server is allowed to impersonate the client. The REQ_MUTUAL_AUTH
+ * flag may also need to be specified in order for this flag to take
+ * effect.
+ */
+ const unsigned long REQ_DELEGATE = (1 << 1);
+
+ /**
+ * The authentication is required for a proxy connection.
+ */
+ const unsigned long REQ_PROXY_AUTH = (1 << 2);
+
+ /**
+ * Flags used for telemetry.
+ */
+ const unsigned long NTLM_MODULE_SAMBA_AUTH_PROXY = 0;
+ const unsigned long NTLM_MODULE_SAMBA_AUTH_DIRECT = 1;
+ const unsigned long NTLM_MODULE_WIN_API_PROXY = 2;
+ const unsigned long NTLM_MODULE_WIN_API_DIRECT = 3;
+ const unsigned long NTLM_MODULE_GENERIC_PROXY = 4;
+ const unsigned long NTLM_MODULE_GENERIC_DIRECT = 5;
+ const unsigned long NTLM_MODULE_KERBEROS_PROXY = 6;
+ const unsigned long NTLM_MODULE_KERBEROS_DIRECT = 7;
+
+ /** Other flags may be defined in the future */
+
+ /**
+ * Called to initialize an auth module. The other methods cannot be called
+ * unless this method succeeds.
+ *
+ * @param aServiceName
+ * the service name, which may be null if not applicable (e.g., for
+ * NTLM, this parameter should be null).
+ * @param aServiceFlags
+ * a bitwise-or of the REQ_ flags defined above (pass REQ_DEFAULT
+ * for default behavior).
+ * @param aDomain
+ * the authentication domain, which may be null if not applicable.
+ * @param aUsername
+ * the user's login name
+ * @param aPassword
+ * the user's password
+ */
+ void init(in ACString aServiceName,
+ in unsigned long aServiceFlags,
+ in AString aDomain,
+ in AString aUsername,
+ in AString aPassword);
+
+ /**
+ * Called to get the next token in a sequence of authentication steps.
+ *
+ * @param aInToken
+ * A buffer containing the input token (e.g., a challenge from a
+ * server). This may be null.
+ * @param aInTokenLength
+ * The length of the input token.
+ * @param aOutToken
+ * If getNextToken succeeds, then aOutToken will point to a buffer
+ * to be sent in response to the server challenge. The length of
+ * this buffer is given by aOutTokenLength. The buffer at aOutToken
+ * must be recycled with a call to free.
+ * @param aOutTokenLength
+ * If getNextToken succeeds, then aOutTokenLength contains the
+ * length of the buffer (number of bytes) pointed to by aOutToken.
+ */
+ void getNextToken([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+ /**
+ * Once a security context has been established through calls to GetNextToken()
+ * it may be used to protect data exchanged between client and server. Calls
+ * to Wrap() are used to protect items of data to be sent to the server.
+ *
+ * @param aInToken
+ * A buffer containing the data to be sent to the server
+ * @param aInTokenLength
+ * The length of the input token
+ * @param confidential
+ * If set to true, Wrap() will encrypt the data, otherwise data will
+ * just be integrity protected (checksummed)
+ * @param aOutToken
+ * A buffer containing the resulting data to be sent to the server
+ * @param aOutTokenLength
+ * The length of the output token buffer
+ *
+ * Wrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying authentication
+ * mechanism does not support security layers.
+ */
+ void wrap([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ in boolean confidential,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+
+ /**
+ * Unwrap() is used to unpack, decrypt, and verify the checksums on data
+ * returned by a server when security layers are in use.
+ *
+ * @param aInToken
+ * A buffer containing the data received from the server
+ * @param aInTokenLength
+ * The length of the input token
+ * @param aOutToken
+ * A buffer containing the plaintext data from the server
+ * @param aOutTokenLength
+ * The length of the output token buffer
+ *
+ * Unwrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying
+ * authentication mechanism does not support security layers.
+ */
+ void unwrap([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+
+%{C++
+ /**
+ * Create a new instance of an auth module.
+ *
+ * @param aType
+ * The type of the auth module to be constructed.
+ */
+ static already_AddRefed<nsIAuthModule> CreateInstance(const char* aType);
+%}
+};
diff --git a/netwerk/base/nsIAuthPrompt.idl b/netwerk/base/nsIAuthPrompt.idl
new file mode 100644
index 0000000000..fbbac6ee9f
--- /dev/null
+++ b/netwerk/base/nsIAuthPrompt.idl
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrompt;
+
+[scriptable, uuid(358089f9-ee4b-4711-82fd-bcd07fc62061)]
+interface nsIAuthPrompt : nsISupports
+{
+ const uint32_t SAVE_PASSWORD_NEVER = 0;
+ const uint32_t SAVE_PASSWORD_FOR_SESSION = 1;
+ const uint32_t SAVE_PASSWORD_PERMANENTLY = 2;
+
+ /**
+ * Puts up a text input dialog with OK and Cancel buttons.
+ * Note: prompt uses separate args for the "in" and "out" values of the
+ * input field, whereas the other functions use a single inout arg.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param defaultText The default text to display in the text input box.
+ * @param result The value entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean prompt(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ in wstring defaultText,
+ out wstring result);
+
+ /**
+ * Puts up a username/password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param user The username entered in the dialog.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean promptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring user,
+ inout wstring pwd);
+
+ /**
+ * Puts up an async username/password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param user The username entered in the dialog.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return promise resolving to true for OK, false for Cancel
+ */
+ Promise asyncPromptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring user,
+ inout wstring pwd);
+
+ /**
+ * Puts up a password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test. If a username is
+ * specified (http://user@site.com) it will be used
+ * when matching existing logins or saving new ones.
+ * If no username is specified, only password-only
+ * logins will be matched or saved.
+ * Note: if a username is specified, the username
+ * should be escaped.
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean promptPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring pwd);
+
+ /**
+ * Puts up an async password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test. If a username is
+ * specified (http://user@site.com) it will be used
+ * when matching existing logins or saving new ones.
+ * If no username is specified, only password-only
+ * logins will be matched or saved.
+ * Note: if a username is specified, the username
+ * should be escaped.
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return promise resolving to true for OK, false for Cancel
+ */
+ Promise asyncPromptPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring pwd);
+};
diff --git a/netwerk/base/nsIAuthPrompt2.idl b/netwerk/base/nsIAuthPrompt2.idl
new file mode 100644
index 0000000000..d94d50dfcc
--- /dev/null
+++ b/netwerk/base/nsIAuthPrompt2.idl
@@ -0,0 +1,104 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthPromptCallback;
+interface nsIChannel;
+interface nsICancelable;
+interface nsIAuthInformation;
+
+/**
+ * An interface allowing to prompt for a username and password. This interface
+ * is usually acquired using getInterface on notification callbacks or similar.
+ * It can be used to prompt users for authentication information, either
+ * synchronously or asynchronously.
+ */
+[scriptable, uuid(651395EB-8612-4876-8AC0-A88D4DCE9E1E)]
+interface nsIAuthPrompt2 : nsISupports
+{
+ /** @name Security Levels */
+ /* @{ */
+ /**
+ * The password will be sent unencrypted. No security provided.
+ */
+ const uint32_t LEVEL_NONE = 0;
+ /**
+ * Password will be sent encrypted, but the connection is otherwise
+ * insecure.
+ */
+ const uint32_t LEVEL_PW_ENCRYPTED = 1;
+ /**
+ * The connection, both for password and data, is secure.
+ */
+ const uint32_t LEVEL_SECURE = 2;
+ /* @} */
+
+ /**
+ * Requests a username and a password. Implementations will commonly show a
+ * dialog with a username and password field, depending on flags also a
+ * domain field.
+ *
+ * @param aChannel
+ * The channel that requires authentication.
+ * @param level
+ * One of the level constants from above. See there for descriptions
+ * of the levels.
+ * @param authInfo
+ * Authentication information object. The implementation should fill in
+ * this object with the information entered by the user before
+ * returning.
+ *
+ * @retval true
+ * Authentication can proceed using the values in the authInfo
+ * object.
+ * @retval false
+ * Authentication should be cancelled, usually because the user did
+ * not provide username/password.
+ *
+ * @note Exceptions thrown from this function will be treated like a
+ * return value of false.
+ * @deprecated use asyncPromptAuth
+ */
+ boolean promptAuth(in nsIChannel aChannel,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+
+ /**
+ * Asynchronously prompt the user for a username and password.
+ * This has largely the same semantics as promptUsernameAndPassword(),
+ * but must return immediately after calling and return the entered
+ * data in a callback.
+ *
+ * If the user closes the dialog using a cancel button or similar,
+ * the callback's nsIAuthPromptCallback::onAuthCancelled method must be
+ * called.
+ * Calling nsICancelable::cancel on the returned object SHOULD close the
+ * dialog and MUST call nsIAuthPromptCallback::onAuthCancelled on the provided
+ * callback.
+ *
+ * This implementation may:
+ *
+ * 1) Coalesce identical prompts. This means prompts that are guaranteed to
+ * want the same auth information from the user. A single prompt will be
+ * shown; then the callbacks for all the coalesced prompts will be notified
+ * with the resulting auth information.
+ * 2) Serialize prompts that are all in the same "context" (this might mean
+ * application-wide, for a given window, or something else depending on
+ * the user interface) so that the user is not deluged with prompts.
+ *
+ * @throw
+ * This method may throw any exception when the prompt fails to queue e.g
+ * because of out-of-memory error. It must not throw when the prompt
+ * could already be potentially shown to the user. In that case information
+ * about the failure has to come through the callback. This way we
+ * prevent multiple dialogs shown to the user because consumer may fall
+ * back to synchronous prompt on synchronous failure of this method.
+ */
+ nsICancelable asyncPromptAuth(in nsIChannel aChannel,
+ in nsIAuthPromptCallback aCallback,
+ in nsISupports aContext,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+};
diff --git a/netwerk/base/nsIAuthPromptAdapterFactory.idl b/netwerk/base/nsIAuthPromptAdapterFactory.idl
new file mode 100644
index 0000000000..e763d47146
--- /dev/null
+++ b/netwerk/base/nsIAuthPromptAdapterFactory.idl
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthPrompt;
+interface nsIAuthPrompt2;
+
+/**
+ * An interface for wrapping nsIAuthPrompt interfaces to make
+ * them usable via an nsIAuthPrompt2 interface.
+ */
+[scriptable, uuid(60e46383-bb9a-4860-8962-80d9c5c05ddc)]
+interface nsIAuthPromptAdapterFactory : nsISupports
+{
+ /**
+ * Wrap an object implementing nsIAuthPrompt so that it's usable via
+ * nsIAuthPrompt2.
+ */
+ nsIAuthPrompt2 createAdapter(in nsIAuthPrompt aPrompt);
+};
diff --git a/netwerk/base/nsIAuthPromptCallback.idl b/netwerk/base/nsIAuthPromptCallback.idl
new file mode 100644
index 0000000000..27f89f04c0
--- /dev/null
+++ b/netwerk/base/nsIAuthPromptCallback.idl
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthInformation;
+
+/**
+ * Interface for callback methods for the asynchronous nsIAuthPrompt2 method.
+ * Callers MUST call exactly one method if nsIAuthPrompt2::promptPasswordAsync
+ * returns successfully. They MUST NOT call any method on this interface before
+ * promptPasswordAsync returns.
+ */
+[scriptable, uuid(bdc387d7-2d29-4cac-92f1-dd75d786631d)]
+interface nsIAuthPromptCallback : nsISupports
+{
+ /**
+ * Authentication information is available.
+ *
+ * @param aContext
+ * The context as passed to promptPasswordAsync
+ * @param aAuthInfo
+ * Authentication information. Must be the same object that was passed
+ * to promptPasswordAsync.
+ *
+ * @note Any exceptions thrown from this method should be ignored.
+ */
+ void onAuthAvailable(in nsISupports aContext,
+ in nsIAuthInformation aAuthInfo);
+
+ /**
+ * Notification that the prompt was cancelled.
+ *
+ * @param aContext
+ * The context that was passed to promptPasswordAsync.
+ * @param userCancel
+ * If false, this prompt was cancelled by calling the
+ * the cancel method on the nsICancelable; otherwise,
+ * it was cancelled by the user.
+ */
+ void onAuthCancelled(in nsISupports aContext, in boolean userCancel);
+};
diff --git a/netwerk/base/nsIAuthPromptProvider.idl b/netwerk/base/nsIAuthPromptProvider.idl
new file mode 100644
index 0000000000..e8ff122ec2
--- /dev/null
+++ b/netwerk/base/nsIAuthPromptProvider.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: idl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(bd9dc0fa-68ce-47d0-8859-6418c2ae8576)]
+interface nsIAuthPromptProvider : nsISupports
+{
+ /**
+ * Normal (non-proxy) prompt request.
+ */
+ const uint32_t PROMPT_NORMAL = 0;
+
+ /**
+ * Proxy auth request.
+ */
+ const uint32_t PROMPT_PROXY = 1;
+
+ /**
+ * Request a prompt interface for the given prompt reason;
+ * @throws NS_ERROR_NOT_AVAILABLE if no prompt is allowed or
+ * available for the given reason.
+ *
+ * @param aPromptReason The reason for the auth prompt;
+ * one of #PROMPT_NORMAL or #PROMPT_PROXY
+ * @param iid The desired interface, e.g.
+ * NS_GET_IID(nsIAuthPrompt2).
+ * @returns an nsIAuthPrompt2 interface, or throws NS_ERROR_NOT_AVAILABLE
+ */
+ void getAuthPrompt(in uint32_t aPromptReason, in nsIIDRef iid,
+ [iid_is(iid),retval] out nsQIResult result);
+};
diff --git a/netwerk/base/nsIBackgroundFileSaver.idl b/netwerk/base/nsIBackgroundFileSaver.idl
new file mode 100644
index 0000000000..0b26852c28
--- /dev/null
+++ b/netwerk/base/nsIBackgroundFileSaver.idl
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+interface nsIBackgroundFileSaverObserver;
+interface nsIFile;
+
+/**
+ * Allows saving data to a file, while handling all the input/output on a
+ * background thread, including the initial file name assignment and any
+ * subsequent renaming of the target file.
+ *
+ * This interface is designed for file downloads. Generally, they start in the
+ * temporary directory, while the user is selecting the final name. Then, they
+ * are moved to the chosen target directory with a ".part" extension appended to
+ * the file name. Finally, they are renamed when the download is completed.
+ *
+ * Components implementing both nsIBackgroundFileSaver and nsIStreamListener
+ * allow data to be fed using an implementation of OnDataAvailable that never
+ * blocks the calling thread. They suspend the request that drives the stream
+ * listener in case too much data is being fed, and resume it when the data has
+ * been written. Calling OnStopRequest does not necessarily close the target
+ * file, and the Finish method must be called to complete the operation.
+ *
+ * Components implementing both nsIBackgroundFileSaver and nsIAsyncOutputStream
+ * allow data to be fed directly to the non-blocking output stream, that however
+ * may return NS_BASE_STREAM_WOULD_BLOCK in case too much data is being fed.
+ * Closing the output stream does not necessarily close the target file, and the
+ * Finish method must be called to complete the operation.
+ *
+ * @remarks Implementations may require the consumer to always call Finish. If
+ * the object reference is released without calling Finish, a memory
+ * leak may occur, and the target file might be kept locked. All
+ * public methods of the interface may only be called from the main
+ * thread.
+ */
+[scriptable, uuid(c43544a4-682c-4262-b407-2453d26e660d)]
+interface nsIBackgroundFileSaver : nsISupports
+{
+ /**
+ * This observer receives notifications when the target file name changes and
+ * when the operation completes, successfully or not.
+ *
+ * @remarks A strong reference to the observer is held. Notification events
+ * are dispatched to the thread that created the object that
+ * implements nsIBackgroundFileSaver.
+ */
+ attribute nsIBackgroundFileSaverObserver observer;
+
+ /**
+ * An Array of Array of Array of bytes, representing a chain of
+ * X.509 certificates, the last of which signed the downloaded file.
+ * Each list may belong to a different signer and contain certificates
+ * all the way up to the root.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * In case this is called before the onSaveComplete method has been
+ * called to notify success, or enableSignatureInfo has not been
+ * called.
+ */
+ readonly attribute Array<Array<Array<uint8_t> > > signatureInfo;
+
+ /**
+ * The SHA-256 hash, in raw bytes, associated with the data that was saved.
+ *
+ * In case the enableAppend method has been called, the hash computation
+ * includes the contents of the existing file, if any.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * In case the enableSha256 method has not been called, or before the
+ * onSaveComplete method has been called to notify success.
+ */
+ readonly attribute ACString sha256Hash;
+
+ /**
+ * Instructs the component to compute the signatureInfo of the target file,
+ * and make it available in the signatureInfo property.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableSignatureInfo();
+
+ /**
+ * Instructs the component to compute the SHA-256 hash of the target file, and
+ * make it available in the sha256Hash property.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableSha256();
+
+ /**
+ * Instructs the component to append data to the initial target file, that
+ * will be specified by the first call to the setTarget method, instead of
+ * overwriting the file.
+ *
+ * If the initial target file does not exist, this method has no effect.
+ *
+ * @remarks This must be set on the main thread before the first call to
+ * setTarget.
+ */
+ void enableAppend();
+
+ /**
+ * Sets the name of the output file to be written. The target can be changed
+ * after data has already been fed, in which case the existing file will be
+ * moved to the new destination.
+ *
+ * In case the specified file already exists, and this method is called for
+ * the first time, the file may be either overwritten or appended to, based on
+ * whether the enableAppend method was called. Subsequent calls always
+ * overwrite the specified target file with the previously saved data.
+ *
+ * No file will be written until this function is called at least once. It's
+ * recommended not to feed any data until the output file is set.
+ *
+ * If an input/output error occurs with the specified file, the save operation
+ * fails. Failure is notified asynchronously through the observer.
+ *
+ * @param aTarget
+ * New output file to be written.
+ * @param aKeepPartial
+ * Indicates whether aFile should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled. This is
+ * generally set for downloads that use temporary ".part" files.
+ */
+ void setTarget(in nsIFile aTarget, in bool aKeepPartial);
+
+ /**
+ * Terminates access to the output file, then notifies the observer with the
+ * specified status code. A failure code will force the operation to be
+ * canceled, in which case the output file will be deleted if requested.
+ *
+ * This forces the involved streams to be closed, thus no more data should be
+ * fed to the component after this method has been called.
+ *
+ * This is the last method that should be called on this object, and the
+ * target file name cannot be changed anymore after this method has been
+ * called. Conversely, before calling this method, the file can still be
+ * renamed even if all the data has been fed.
+ *
+ * @param aStatus
+ * Result code that determines whether the operation should succeed or
+ * be canceled, and is notified to the observer. If the operation
+ * fails meanwhile for other reasons, or the observer has been already
+ * notified of completion, this status code is ignored.
+ */
+ void finish(in nsresult aStatus);
+};
+
+[scriptable, uuid(ee7058c3-6e54-4411-b76b-3ce87b76fcb6)]
+interface nsIBackgroundFileSaverObserver : nsISupports
+{
+ /**
+ * Called when the name of the output file has been determined. This function
+ * may be called more than once if the target file is renamed while saving.
+ *
+ * @param aSaver
+ * Reference to the object that raised the notification.
+ * @param aTarget
+ * Name of the file that is being written.
+ */
+ void onTargetChange(in nsIBackgroundFileSaver aSaver, in nsIFile aTarget);
+
+ /**
+ * Called when the operation completed, and the target file has been closed.
+ * If the operation succeeded, the target file is ready to be used, otherwise
+ * it might have been already deleted.
+ *
+ * @param aSaver
+ * Reference to the object that raised the notification.
+ * @param aStatus
+ * Result code that determines whether the operation succeeded or
+ * failed, as well as the failure reason.
+ */
+ void onSaveComplete(in nsIBackgroundFileSaver aSaver, in nsresult aStatus);
+};
diff --git a/netwerk/base/nsIBufferedStreams.idl b/netwerk/base/nsIBufferedStreams.idl
new file mode 100644
index 0000000000..46f0112900
--- /dev/null
+++ b/netwerk/base/nsIBufferedStreams.idl
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+#include "nsIOutputStream.idl"
+
+/**
+ * An input stream that reads ahead and keeps a buffer coming from another input
+ * stream so that fewer accesses to the underlying stream are necessary.
+ */
+[scriptable, builtinclass, uuid(616f5b48-da09-11d3-8cda-0060b0fc14a3)]
+interface nsIBufferedInputStream : nsIInputStream
+{
+ /**
+ * @param fillFromStream - add buffering to this stream
+ * @param bufferSize - specifies the maximum buffer size
+ */
+ void init(in nsIInputStream fillFromStream,
+ in unsigned long bufferSize);
+
+ /**
+ * Get the wrapped data stream
+ */
+ readonly attribute nsIInputStream data;
+};
+
+/**
+ * An output stream that stores up data to write out to another output stream
+ * and does the entire write only when the buffer is full, so that fewer writes
+ * to the underlying output stream are necessary.
+ */
+[scriptable, builtinclass, uuid(6476378a-da09-11d3-8cda-0060b0fc14a3)]
+interface nsIBufferedOutputStream : nsIOutputStream
+{
+ /**
+ * @param sinkToStream - add buffering to this stream
+ * @param bufferSize - specifies the maximum buffer size
+ */
+ void init(in nsIOutputStream sinkToStream,
+ in unsigned long bufferSize);
+
+ /**
+ * Get the wrapped data stream
+ */
+ readonly attribute nsIOutputStream data;
+};
diff --git a/netwerk/base/nsIByteRangeRequest.idl b/netwerk/base/nsIByteRangeRequest.idl
new file mode 100644
index 0000000000..4a10126d69
--- /dev/null
+++ b/netwerk/base/nsIByteRangeRequest.idl
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(C1B1F426-7E83-4759-9F88-0E1B17F49366)]
+interface nsIByteRangeRequest : nsISupports
+{
+ /**
+ * Returns true IFF this request is a byte range request, otherwise it
+ * returns false (This is effectively the same as checking to see if
+ * |startRequest| is zero and |endRange| is the content length.)
+ */
+ readonly attribute boolean isByteRangeRequest;
+
+ /**
+ * Absolute start position in remote file for this request.
+ */
+ readonly attribute long long startRange;
+
+ /**
+ * Absolute end postion in remote file for this request
+ */
+ readonly attribute long long endRange;
+};
diff --git a/netwerk/base/nsICacheInfoChannel.idl b/netwerk/base/nsICacheInfoChannel.idl
new file mode 100644
index 0000000000..b7bb44093c
--- /dev/null
+++ b/netwerk/base/nsICacheInfoChannel.idl
@@ -0,0 +1,207 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAsyncOutputStream;
+interface nsIInputStream;
+
+%{C++
+#include "nsTArray.h"
+namespace mozilla {
+namespace net {
+class PreferredAlternativeDataTypeParams;
+}
+} // namespace mozilla
+%}
+
+[ref] native ConstPreferredAlternativeDataTypeArray(const nsTArray<mozilla::net::PreferredAlternativeDataTypeParams>);
+
+[scriptable, uuid(1fb8ccf2-5fa5-45ec-bc57-8c8022a5d0d3)]
+interface nsIInputStreamReceiver : nsISupports
+{
+ void onInputStreamReady(in nsIInputStream aStream);
+};
+
+[scriptable, builtinclass, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)]
+interface nsICacheInfoChannel : nsISupports
+{
+ /**
+ * Get the number of times the cache entry has been opened. This attribute is
+ * equivalent to nsICachingChannel.cacheToken.fetchCount.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the cache entry or the alternate data
+ * cache entry cannot be read.
+ */
+ readonly attribute uint32_t cacheTokenFetchCount;
+
+ /**
+ * Get expiration time from cache token. This attribute is equivalent to
+ * nsICachingChannel.cacheToken.expirationTime.
+ */
+ readonly attribute uint32_t cacheTokenExpirationTime;
+
+ /**
+ * TRUE if this channel's data is being loaded from the cache. This value
+ * is undefined before the channel fires its OnStartRequest notification
+ * and after the channel fires its OnStopRequest notification.
+ */
+ boolean isFromCache();
+
+ /**
+ * Returns true if the channel raced the cache and network requests.
+ * In order to determine if the response is coming from the cache or the
+ * network, the consumer can check isFromCache().
+ * The method can only be called after the channel fires its OnStartRequest
+ * notification.
+ */
+ boolean isRacing();
+
+ /**
+ * The unique ID of the corresponding nsICacheEntry from which the response is
+ * retrieved. By comparing the returned value, we can judge whether the data
+ * of two distinct nsICacheInfoChannels is from the same nsICacheEntry. This
+ * scenario could be useful when verifying whether the alternative data from
+ * one nsICacheInfochannel matches the main data from another one.
+ *
+ * Note: NS_ERROR_NOT_AVAILABLE is thrown when a nsICacheInfoChannel has no
+ * valid corresponding nsICacheEntry.
+ */
+ uint64_t getCacheEntryId();
+
+ /**
+ * Set/get the cache key. This integer uniquely identifies the data in
+ * the cache for this channel.
+ *
+ * A cache key retrieved from a particular instance of nsICacheInfoChannel
+ * could be set on another instance of nsICacheInfoChannel provided the
+ * underlying implementations are compatible and provided the new
+ * channel instance was created with the same URI. The implementation of
+ * nsICacheInfoChannel would be expected to use the cache entry identified
+ * by the cache token. Depending on the value of nsIRequest::loadFlags,
+ * the cache entry may be validated, overwritten, or simply read.
+ *
+ * The cache key may be 0 indicating that the URI of the channel is
+ * sufficient to locate the same cache entry. Setting a 0 cache key
+ * is likewise valid.
+ */
+ attribute unsigned long cacheKey;
+
+ /**
+ * Tells the channel to behave as if the LOAD_FROM_CACHE flag has been set,
+ * but without affecting the loads for the entire loadGroup in case of this
+ * channel being the default load group's channel.
+ */
+ attribute boolean allowStaleCacheContent;
+
+ /**
+ * Tells the priority for LOAD_CACHE is raised over LOAD_BYPASS_CACHE or
+ * LOAD_BYPASS_LOCAL_CACHE in case those flags are set at the same time.
+ */
+ attribute boolean preferCacheLoadOverBypass;
+
+ cenum PreferredAlternativeDataDeliveryType : 8 {
+ /**
+ * Do not deliver alternative data stream.
+ */
+ NONE = 0,
+
+ /**
+ * Deliver alternative data stream upon additional request.
+ */
+ ASYNC = 1,
+
+ /**
+ * Deliver alternative data stream during IPC parent/child serialization.
+ */
+ SERIALIZE = 2,
+ };
+
+ /**
+ * Tells the channel to be force validated during soft reload.
+ */
+ attribute boolean forceValidateCacheContent;
+
+ /**
+ * Calling this method instructs the channel to serve the alternative data
+ * if that was previously saved in the cache, otherwise it will serve the
+ * real data.
+ * @param type
+ * a string identifying the alt-data format
+ * @param contentType
+ * the contentType for which the preference applies.
+ * an empty contentType means the preference applies for ANY contentType
+ * @param deliverAltData
+ * if false, also if alt-data is available, the channel will deliver
+ * the original data.
+ *
+ * The method may be called several times, with different type and contentType.
+ *
+ * Must be called before AsyncOpen.
+ */
+ void preferAlternativeDataType(in ACString type, in ACString contentType,
+ in nsICacheInfoChannel_PreferredAlternativeDataDeliveryType deliverAltData);
+
+ /**
+ * Get the preferred alternative data type set by preferAlternativeDataType().
+ * The returned types stand for the desired data type instead of the type of the
+ * information retrieved from the network stack.
+ */
+ [noscript, notxpcom, nostdcall]
+ ConstPreferredAlternativeDataTypeArray preferredAlternativeDataTypes();
+
+ /**
+ * Holds the type of the alternative data representation that the channel
+ * is returning.
+ * Is empty string if no alternative data representation was requested, or
+ * if the requested representation wasn't found in the cache.
+ * Can only be called during or after OnStartRequest.
+ */
+ readonly attribute ACString alternativeDataType;
+
+ /**
+ * If preferAlternativeDataType() has been called passing deliverAltData
+ * equal to false, this attribute will expose the alt-data inputStream if
+ * avaiable.
+ */
+ readonly attribute nsIInputStream alternativeDataInputStream;
+
+ /**
+ * Sometimes when the channel is delivering alt-data, we may want to somehow
+ * access the original content too. This method asynchronously opens the
+ * input stream and delivers it to the receiver.
+ */
+ void getOriginalInputStream(in nsIInputStreamReceiver aReceiver);
+
+ /**
+ * Opens and returns an output stream that a consumer may use to save an
+ * alternate representation of the data.
+ * Must be called after the OnStopRequest that delivered the real data.
+ * The consumer may choose to replace the saved alt representation.
+ * Opening the output stream will fail if there are any open input streams
+ * reading the already saved alt representation. After successfully opening
+ * an output stream, if there is an error before the entire alt data can be
+ * written successfully, the client must signal failure by passing an error
+ * code to CloseWithStatus().
+ *
+ * @param type
+ * type of the alternative data representation
+ * @param predictedSize
+ * Predicted size of the data that will be written. It's used to decide
+ * whether the resulting entry would exceed size limit, in which case
+ * an error is thrown. If the size isn't known in advance, -1 should be
+ * passed.
+ */
+ nsIAsyncOutputStream openAlternativeOutputStream(in ACString type, in long long predictedSize);
+};
+
+%{ C++
+namespace mozilla {
+namespace net {
+
+using PreferredAlternativeDataDeliveryTypeIPC = nsICacheInfoChannel::PreferredAlternativeDataDeliveryType;
+
+}
+} // namespace mozilla
+%}
diff --git a/netwerk/base/nsICachingChannel.idl b/netwerk/base/nsICachingChannel.idl
new file mode 100644
index 0000000000..3623423ff7
--- /dev/null
+++ b/netwerk/base/nsICachingChannel.idl
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICacheInfoChannel.idl"
+
+interface nsIFile;
+
+/**
+ * A channel may optionally implement this interface to allow clients
+ * to affect its behavior with respect to how it uses the cache service.
+ *
+ * This interface provides:
+ * 1) Support for "stream as file" semantics (for JAR and plugins).
+ * 2) Support for "pinning" cached data in the cache (for printing and save-as).
+ * 3) Support for uniquely identifying cached data in cases when the URL
+ * is insufficient (e.g., HTTP form submission).
+ */
+[scriptable, builtinclass, uuid(dd1d6122-5ecf-4fe4-8f0f-995e7ab3121a)]
+interface nsICachingChannel : nsICacheInfoChannel
+{
+ /**
+ * Set/get the cache token... uniquely identifies the data in the cache.
+ * Holding a reference to this token prevents the cached data from being
+ * removed.
+ *
+ * A cache token retrieved from a particular instance of nsICachingChannel
+ * could be set on another instance of nsICachingChannel provided the
+ * underlying implementations are compatible. The implementation of
+ * nsICachingChannel would be expected to only read from the cache entry
+ * identified by the cache token and not try to validate it.
+ *
+ * The cache token can be QI'd to a nsICacheEntryInfo if more detail
+ * about the cache entry is needed (e.g., expiration time).
+ */
+ attribute nsISupports cacheToken;
+
+ /**
+ * Instructs the channel to only store the metadata of the entry, and not
+ * the content. When reading an existing entry, this automatically sets
+ * LOAD_ONLY_IF_MODIFIED flag.
+ * Must be called before asyncOpen().
+ */
+ attribute boolean cacheOnlyMetadata;
+
+ /**
+ * Tells the channel to use the pinning storage.
+ */
+ attribute boolean pin;
+
+ /**
+ * Overrides cache validation for a time specified in seconds.
+ *
+ * @param aSecondsToTheFuture
+ *
+ */
+ void forceCacheEntryValidFor(in unsigned long aSecondsToTheFuture);
+
+ /**************************************************************************
+ * Caching channel specific load flags:
+ */
+
+ /**
+ * This load flag inhibits fetching from the net. An error of
+ * NS_ERROR_DOCUMENT_NOT_CACHED will be sent to the listener's
+ * onStopRequest if network IO is necessary to complete the request.
+ *
+ * This flag can be used to find out whether fetching this URL would
+ * cause validation of the cache entry via the network.
+ *
+ * Combining this flag with LOAD_BYPASS_LOCAL_CACHE will cause all
+ * loads to fail.
+ */
+ const unsigned long LOAD_NO_NETWORK_IO = 1 << 26;
+
+ /**
+ * This load flag causes the local cache to be skipped when fetching a
+ * request. Unlike LOAD_BYPASS_CACHE, it does not force an end-to-end load
+ * (i.e., it does not affect proxy caches).
+ */
+ const unsigned long LOAD_BYPASS_LOCAL_CACHE = 1 << 28;
+
+ /**
+ * This load flag causes the local cache to be skipped if the request
+ * would otherwise block waiting to access the cache.
+ */
+ const unsigned long LOAD_BYPASS_LOCAL_CACHE_IF_BUSY = 1 << 29;
+
+ /**
+ * This load flag inhibits fetching from the net if the data in the cache
+ * has been evicted. An error of NS_ERROR_DOCUMENT_NOT_CACHED will be sent
+ * to the listener's onStopRequest in this case. This flag is set
+ * automatically when the application is offline.
+ */
+ const unsigned long LOAD_ONLY_FROM_CACHE = 1 << 30;
+
+ /**
+ * This load flag controls what happens when a document would be loaded
+ * from the cache to satisfy a call to AsyncOpen. If this attribute is
+ * set to TRUE, then the document will not be loaded from the cache. A
+ * stream listener can check nsICachingChannel::isFromCache to determine
+ * if the AsyncOpen will actually result in data being streamed.
+ *
+ * If this flag has been set, and the request can be satisfied via the
+ * cache, then the OnDataAvailable events will be skipped. The listener
+ * will only see OnStartRequest followed by OnStopRequest.
+ */
+ const unsigned long LOAD_ONLY_IF_MODIFIED = 1 << 31;
+};
diff --git a/netwerk/base/nsICancelable.idl b/netwerk/base/nsICancelable.idl
new file mode 100644
index 0000000000..c558dc6e27
--- /dev/null
+++ b/netwerk/base/nsICancelable.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface provides a means to cancel an operation that is in progress.
+ */
+[scriptable, uuid(d94ac0a0-bb18-46b8-844e-84159064b0bd)]
+interface nsICancelable : nsISupports
+{
+ /**
+ * Call this method to request that this object abort whatever operation it
+ * may be performing.
+ *
+ * @param aReason
+ * Pass a failure code to indicate the reason why this operation is
+ * being canceled. It is an error to pass a success code.
+ */
+ void cancel(in nsresult aReason);
+};
diff --git a/netwerk/base/nsICaptivePortalService.idl b/netwerk/base/nsICaptivePortalService.idl
new file mode 100644
index 0000000000..e4867765d7
--- /dev/null
+++ b/netwerk/base/nsICaptivePortalService.idl
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(b5fd5629-d04c-4138-9529-9311f291ecd4)]
+interface nsICaptivePortalServiceCallback : nsISupports
+{
+ /**
+ * Invoke callbacks after captive portal detection finished.
+ */
+ void complete(in bool success, in nsresult error);
+};
+
+/**
+ * Service used for captive portal detection.
+ * The service is only active in the main process. It is also available in the
+ * content process, but only to mirror the captive portal state from the main
+ * process.
+ */
+[scriptable, uuid(bdbe0555-fc3d-4f7b-9205-c309ceb2d641)]
+interface nsICaptivePortalService : nsISupports
+{
+ const long UNKNOWN = 0;
+ const long NOT_CAPTIVE = 1;
+ const long UNLOCKED_PORTAL = 2;
+ const long LOCKED_PORTAL = 3;
+
+ /**
+ * Called from XPCOM to trigger a captive portal recheck.
+ * A network request will only be performed if no other checks are currently
+ * ongoing.
+ * Will not do anything if called in the content process.
+ */
+ void recheckCaptivePortal();
+
+ /**
+ * Returns the state of the captive portal.
+ */
+ readonly attribute long state;
+
+%{C++
+ int32_t State() {
+ int32_t result = nsICaptivePortalService::UNKNOWN;
+ GetState(&result);
+ return result;
+ }
+%}
+
+ /**
+ * Returns the time difference between NOW and the last time a request was
+ * completed in milliseconds.
+ */
+ readonly attribute unsigned long long lastChecked;
+};
+
+%{C++
+/**
+ * This observer notification will be emitted when the captive portal state
+ * changes. After receiving it, the ContentParent will send an IPC message
+ * to the ContentChild, which will set the state in the captive portal service
+ * in the child.
+ */
+#define NS_IPC_CAPTIVE_PORTAL_SET_STATE "ipc:network:captive-portal-set-state"
+
+/**
+ * This notification will be emitted when the captive portal service has
+ * determined that we can connect to the internet.
+ * The service will pass either "captive" if there is an unlocked captive portal
+ * present, or "clear" if no captive portal was detected.
+ * Note: this notification only gets sent in the parent process.
+ */
+#define NS_CAPTIVE_PORTAL_CONNECTIVITY "network:captive-portal-connectivity"
+
+/**
+ * Similar to NS_CAPTIVE_PORTAL_CONNECTIVITY but only gets dispatched when
+ * the connectivity changes from UNKNOWN to NOT_CAPTIVE or from LOCKED_PORTAL
+ * to UNLOCKED_PORTAL.
+ */
+#define NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED "network:captive-portal-connectivity-changed"
+
+%}
diff --git a/netwerk/base/nsIChannel.idl b/netwerk/base/nsIChannel.idl
new file mode 100644
index 0000000000..2269c4faa8
--- /dev/null
+++ b/netwerk/base/nsIChannel.idl
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+#include "nsILoadInfo.idl"
+
+interface nsIInputStream;
+interface nsIInterfaceRequestor;
+interface nsIStreamListener;
+interface nsITransportSecurityInfo;
+interface nsIURI;
+
+%{C++
+#include "nsCOMPtr.h"
+%}
+
+/**
+ * The nsIChannel interface allows clients to construct "GET" requests for
+ * specific protocols, and manage them in a uniform way. Once a channel is
+ * created (via nsIIOService::newChannel), parameters for that request may
+ * be set by using the channel attributes, or by QI'ing to a subclass of
+ * nsIChannel for protocol-specific parameters. Then, the URI can be fetched
+ * by calling nsIChannel::open or nsIChannel::asyncOpen.
+ *
+ * After a request has been completed, the channel is still valid for accessing
+ * protocol-specific results. For example, QI'ing to nsIHttpChannel allows
+ * response headers to be retrieved for the corresponding http transaction.
+ *
+ * This interface must be used only from the XPCOM main thread.
+ */
+[scriptable, uuid(2c389865-23db-4aa7-9fe5-60cc7b00697e)]
+interface nsIChannel : nsIRequest
+{
+ /**
+ * The original URI used to construct the channel. This is used in
+ * the case of a redirect or URI "resolution" (e.g. resolving a
+ * resource: URI to a file: URI) so that the original pre-redirect
+ * URI can still be obtained. This is never null. Attempts to
+ * set it to null must throw.
+ *
+ * NOTE: this is distinctly different from the http Referer (referring URI),
+ * which is typically the page that contained the original URI (accessible
+ * from nsIHttpChannel).
+ *
+ * NOTE: originalURI isn't yet set on the new channel when
+ * asyncOnChannelRedirect is called.
+ */
+ attribute nsIURI originalURI;
+
+ /**
+ * The URI corresponding to the channel. Its value is immutable.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The owner, corresponding to the entity that is responsible for this
+ * channel. Used by the security manager to grant or deny privileges to
+ * mobile code loaded from this channel.
+ *
+ * NOTE: this is a strong reference to the owner, so if the owner is also
+ * holding a strong reference to the channel, care must be taken to
+ * explicitly drop its reference to the channel.
+ */
+ attribute nsISupports owner;
+
+ /**
+ * The notification callbacks for the channel. This is set by clients, who
+ * wish to provide a means to receive progress, status and protocol-specific
+ * notifications. If this value is NULL, the channel implementation may use
+ * the notification callbacks from its load group. The channel may also
+ * query the notification callbacks from its load group if its notification
+ * callbacks do not supply the requested interface.
+ *
+ * Interfaces commonly requested include: nsIProgressEventSink, nsIPrompt,
+ * and nsIAuthPrompt/nsIAuthPrompt2.
+ *
+ * When the channel is done, it must not continue holding references to
+ * this object.
+ *
+ * NOTE: A channel implementation should take care when "caching" an
+ * interface pointer queried from its notification callbacks. If the
+ * notification callbacks are changed, then a cached interface pointer may
+ * become invalid and may therefore need to be re-queried.
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Transport-level security information (if any) corresponding to the
+ * channel.
+ *
+ * NOTE: In some circumstances TLS information is propagated onto
+ * non-nsIHttpChannel objects to indicate that their contents were likely
+ * delivered over TLS all the same.
+ *
+ * FIXME(bz, bug 1528449) is that still true now that
+ * document.open() doesn't do this?
+ */
+ readonly attribute nsITransportSecurityInfo securityInfo;
+
+ /**
+ * The MIME type of the channel's content if available.
+ *
+ * NOTE: the content type can often be wrongly specified (e.g., wrong file
+ * extension, wrong MIME type, wrong document type stored on a server, etc.),
+ * and the caller most likely wants to verify with the actual data.
+ *
+ * Setting contentType before the channel has been opened provides a hint
+ * to the channel as to what the MIME type is. The channel may ignore this
+ * hint in deciding on the actual MIME type that it will report.
+ *
+ * Setting contentType after onStartRequest has been fired or after open()
+ * is called will override the type determined by the channel.
+ *
+ * Setting contentType between the time that asyncOpen() is called and the
+ * time when onStartRequest is fired has undefined behavior at this time.
+ *
+ * The value of the contentType attribute is a lowercase string. A value
+ * assigned to this attribute will be parsed and normalized as follows:
+ * 1- any parameters (delimited with a ';') will be stripped.
+ * 2- if a charset parameter is given, then its value will replace the
+ * the contentCharset attribute of the channel.
+ * 3- the stripped contentType will be lowercased.
+ * Any implementation of nsIChannel must follow these rules.
+ */
+ attribute ACString contentType;
+
+ /**
+ * The character set of the channel's content if available and if applicable.
+ * This attribute only applies to textual data.
+ *
+ * The value of the contentCharset attribute is a mixedcase string.
+ */
+ attribute ACString contentCharset;
+
+ /**
+ * The length of the data associated with the channel if available. A value
+ * of -1 indicates that the content length is unknown. Note that this is a
+ * 64-bit value and obsoletes the "content-length" property used on some
+ * channels.
+ */
+ attribute int64_t contentLength;
+
+ /**
+ * Synchronously open the channel.
+ *
+ * @return blocking input stream to the channel's data.
+ *
+ * NOTE: nsIChannel implementations are not required to implement this
+ * method. Moreover, since this method may block the calling thread, it
+ * should not be called on a thread that processes UI events. Like any
+ * other nsIChannel method it must not be called on any thread other
+ * than the XPCOM main thread.
+ *
+ * NOTE: Implementations should throw NS_ERROR_IN_PROGRESS if the channel
+ * is reopened.
+ */
+ nsIInputStream open();
+
+ /**
+ * Asynchronously open this channel. Data is fed to the specified stream
+ * listener as it becomes available. The stream listener's methods are
+ * called on the thread that calls asyncOpen and are not called until
+ * after asyncOpen returns. If asyncOpen returns successfully, the
+ * channel promises to call at least onStartRequest and onStopRequest.
+ *
+ * If the nsIRequest object passed to the stream listener's methods is not
+ * this channel, an appropriate onChannelRedirect notification needs to be
+ * sent to the notification callbacks before onStartRequest is called.
+ * Once onStartRequest is called, all following method calls on aListener
+ * will get the request that was passed to onStartRequest.
+ *
+ * If the channel's and loadgroup's notification callbacks do not provide
+ * an nsIChannelEventSink when onChannelRedirect would be called, that's
+ * equivalent to having called onChannelRedirect.
+ *
+ * If asyncOpen returns successfully, the channel is responsible for
+ * keeping itself alive until it has called onStopRequest on aListener or
+ * called onChannelRedirect.
+ *
+ * Implementations are allowed to synchronously add themselves to the
+ * associated load group (if any).
+ *
+ * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the
+ * channel is reopened.
+ * NOTE: Implementations should throw an error if the channel has been
+ * cancelled prior asyncOpen being called.
+ *
+ * @param aListener the nsIStreamListener implementation
+ * @see nsIChannelEventSink for onChannelRedirect
+ */
+ void asyncOpen(in nsIStreamListener aListener);
+
+ /**
+ * True if the channel has been canceled.
+ */
+ [must_use] readonly attribute boolean canceled;
+
+ /**************************************************************************
+ * Channel specific load flags:
+ *
+ * Bits 16-31 are reserved for future use by this interface or one of its
+ * derivatives (e.g., see nsICachingChannel).
+ */
+
+ /**
+ * Set (e.g., by the docshell) to indicate whether or not the channel
+ * corresponds to a document URI.
+ * While setting this flag is sufficient to mark a channel as a document
+ * load, _checking_ whether the channel is a document load requires the use
+ * of the new channel.isDocument
+ */
+ const unsigned long LOAD_DOCUMENT_URI = 1 << 16;
+
+ /**
+ * If the end consumer for this load has been retargeted after discovering
+ * its content, this flag will be set:
+ */
+ const unsigned long LOAD_RETARGETED_DOCUMENT_URI = 1 << 17;
+
+ /**
+ * This flag is set to indicate that this channel is replacing another
+ * channel. This means that:
+ *
+ * 1) the stream listener this channel will be notifying was initially
+ * passed to the asyncOpen method of some other channel
+ *
+ * and
+ *
+ * 2) this channel's URI is a better identifier of the resource being
+ * accessed than this channel's originalURI.
+ *
+ * This flag can be set, for example, for redirects or for cases when a
+ * single channel has multiple parts to it (and thus can follow
+ * onStopRequest with another onStartRequest/onStopRequest pair, each pair
+ * for a different request).
+ */
+ const unsigned long LOAD_REPLACE = 1 << 18;
+
+ /**
+ * Set (e.g., by the docshell) to indicate whether or not the channel
+ * corresponds to an initial document URI load (e.g., link click).
+ */
+ const unsigned long LOAD_INITIAL_DOCUMENT_URI = 1 << 19;
+
+ /**
+ * Set (e.g., by the URILoader) to indicate whether or not the end consumer
+ * for this load has been determined.
+ */
+ const unsigned long LOAD_TARGETED = 1 << 20;
+
+ /**
+ * If this flag is set, the channel should call the content sniffers as
+ * described in nsNetCID.h about NS_CONTENT_SNIFFER_CATEGORY.
+ *
+ * Note: Channels may ignore this flag; however, new channel implementations
+ * should only do so with good reason.
+ */
+ const unsigned long LOAD_CALL_CONTENT_SNIFFERS = 1 << 21;
+
+ /**
+ * This flag tells the channel to bypass URL classifier service check
+ * when opening the channel.
+ */
+ const unsigned long LOAD_BYPASS_URL_CLASSIFIER = 1 << 22;
+
+ /**
+ * If this flag is set, the media-type content sniffer will be allowed
+ * to override any server-set content-type. Otherwise it will only
+ * be allowed to override "no content type" and application/octet-stream.
+ */
+ const unsigned long LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE = 1 << 23;
+
+ /**
+ * Set to let explicitely provided credentials be used over credentials
+ * we have cached previously. In some situations like form login using HTTP
+ * auth via XMLHttpRequest we need to let consumers override the cached
+ * credentials explicitely. For form login 403 response instead of 401 is
+ * usually used to prevent an auth dialog. But any code other then 401/7
+ * will leave original credentials in the cache and there is then no way
+ * to override them for the same user name.
+ */
+ const unsigned long LOAD_EXPLICIT_CREDENTIALS = 1 << 24;
+
+ /**
+ * Set to force bypass of any service worker interception of the channel.
+ */
+ const unsigned long LOAD_BYPASS_SERVICE_WORKER = 1 << 25;
+
+ // nsICachingChannel load flags begin at bit 26.
+
+ /**
+ * Access to the type implied or stated by the Content-Disposition header
+ * if available and if applicable. This allows determining inline versus
+ * attachment.
+ *
+ * Setting contentDisposition provides a hint to the channel about the
+ * disposition. If the hint is DISPOSITION_ATTACHMENT and a normal
+ * Content-Disposition header is present, the hinted value will always be
+ * used. If the hint is DISPOSITION_FORCE_INLINE then the disposition is
+ * inline and the header is not used. The value from Content-Disposition
+ * header is only used when the hinted value is not DISPOSITION_INLINE or
+ * DISPOSITION_FORCE_INLINE.
+ * If the header is missing the hinted value will be used if set.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either
+ * doesn't exist for this type of channel or is empty, and return
+ * DISPOSITION_ATTACHMENT if an invalid/noncompliant value is present.
+ */
+ attribute unsigned long contentDisposition;
+ const unsigned long DISPOSITION_INLINE = 0;
+ const unsigned long DISPOSITION_ATTACHMENT = 1;
+ const unsigned long DISPOSITION_FORCE_INLINE = 2;
+
+ /**
+ * Access to the filename portion of the Content-Disposition header if
+ * available and if applicable. This allows getting the preferred filename
+ * without having to parse it out yourself.
+ *
+ * Setting contentDispositionFilename provides a hint to the channel about
+ * the disposition. If a normal Content-Disposition header is present its
+ * value will always be used. If it is missing the hinted value will be
+ * used if set.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header doesn't
+ * exist for this type of channel, if the header is empty, if the header
+ * doesn't contain a filename portion, or the value of the filename
+ * attribute is empty/missing.
+ */
+ attribute AString contentDispositionFilename;
+
+ /**
+ * Access to the raw Content-Disposition header if available and applicable.
+ *
+ * Implementations should throw NS_ERROR_NOT_AVAILABLE if the header either
+ * doesn't exist for this type of channel or is empty.
+ *
+ * @deprecated Use contentDisposition/contentDispositionFilename instead.
+ */
+ readonly attribute ACString contentDispositionHeader;
+
+ /**
+ * The LoadInfo object contains information about a network load, why it
+ * was started, and how we plan on using the resulting response.
+ * If a network request is redirected, the new channel will receive a new
+ * LoadInfo object. The new object will contain mostly the same
+ * information as the pre-redirect one, but updated as appropriate.
+ * For detailed information about what parts of LoadInfo are updated on
+ * redirect, see documentation on individual properties.
+ */
+ attribute nsILoadInfo loadInfo;
+
+ /**
+ * Returns true if the channel is used to create a document.
+ * It returns true if the loadFlags have LOAD_DOCUMENT_URI set, or if
+ * LOAD_HTML_OBJECT_DATA is set and the channel has the appropriate
+ * MIME type.
+ * Note: May have the wrong value if called before OnStartRequest as we
+ * don't know the MIME type yet.
+ */
+ readonly attribute bool isDocument;
+
+%{ C++
+ inline bool IsDocument()
+ {
+ bool isDocument = false;
+ if (NS_SUCCEEDED(GetIsDocument(&isDocument)) && isDocument) {
+ return true;
+ }
+ return false;
+ }
+
+ inline already_AddRefed<nsILoadInfo> LoadInfo()
+ {
+ nsCOMPtr<nsILoadInfo> result;
+ mozilla::DebugOnly<nsresult> rv = GetLoadInfo(getter_AddRefs(result));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && result);
+ return result.forget();
+ }
+%}
+
+};
+
+[builtinclass, scriptable, uuid(1ebbff64-d742-4f4a-aad5-4df2d1eb937a)]
+interface nsIIdentChannel : nsIChannel
+{
+ /**
+ * Unique ID of the channel, shared between parent and child. Needed if
+ * the channel activity needs to be monitored across process boundaries,
+ * like in devtools net monitor. See bug 1274556.
+ */
+ [must_use] attribute uint64_t channelId;
+
+%{ C++
+ inline uint64_t ChannelId()
+ {
+ uint64_t value = 0;
+ if (NS_SUCCEEDED(GetChannelId(&value))) {
+ return value;
+ }
+ return 0;
+ }
+%}
+};
diff --git a/netwerk/base/nsIChannelEventSink.idl b/netwerk/base/nsIChannelEventSink.idl
new file mode 100644
index 0000000000..56484e933f
--- /dev/null
+++ b/netwerk/base/nsIChannelEventSink.idl
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIAsyncVerifyRedirectCallback;
+
+/**
+ * Implement this interface to receive control over various channel events.
+ * Channels will try to get this interface from a channel's
+ * notificationCallbacks or, if not available there, from the loadGroup's
+ * notificationCallbacks.
+ *
+ * These methods are called before onStartRequest.
+ */
+[scriptable, uuid(0197720d-37ed-4e75-8956-d0d296e4d8a6)]
+interface nsIChannelEventSink : nsISupports
+{
+ /**
+ * This is a temporary redirect. New requests for this resource should
+ * continue to use the URI of the old channel.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_TEMPORARY = 1 << 0;
+
+ /**
+ * This is a permanent redirect. New requests for this resource should use
+ * the URI of the new channel (This might be an HTTP 301 reponse).
+ * If this flag is not set, this is a temporary redirect.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_PERMANENT = 1 << 1;
+
+ /**
+ * This is an internal redirect, i.e. it was not initiated by the remote
+ * server, but is specific to the channel implementation.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_INTERNAL = 1 << 2;
+
+ /**
+ * This is a special-cased redirect coming from hitting HSTS upgrade
+ * redirect from http to https only. In some cases this type of redirect
+ * may be considered as safe despite not being the-same-origin redirect.
+ */
+ const unsigned long REDIRECT_STS_UPGRADE = 1 << 3;
+
+ /**
+ * This is a internal redirect used to handle http authentication retries.
+ * Upon receiving a 401 or 407 the channel gets redirected to a new channel
+ * (same URL) that performs the request with the appropriate credentials.
+ * Auth retry to the server must be made after redirecting to a new channel
+ */
+ const unsigned long REDIRECT_AUTH_RETRY = 1 << 4;
+
+ /**
+ * Called when a redirect occurs. This may happen due to an HTTP 3xx status
+ * code. The purpose of this method is to notify the sink that a redirect
+ * is about to happen, but also to give the sink the right to veto the
+ * redirect by throwing or passing a failure-code in the callback.
+ *
+ * Note that vetoing the redirect simply means that |newChannel| will not
+ * be opened. It is important to understand that |oldChannel| will continue
+ * loading as if it received a HTTP 200, which includes notifying observers
+ * and possibly display or process content attached to the HTTP response.
+ * If the sink wants to prevent this loading it must explicitly deal with
+ * it, e.g. by calling |oldChannel->Cancel()|
+ *
+ * There is a certain freedom in implementing this method:
+ *
+ * If the return-value indicates success, a callback on |callback| is
+ * required. This callback can be done from within asyncOnChannelRedirect
+ * (effectively making the call synchronous) or at some point later
+ * (making the call asynchronous). Repeat: A callback must be done
+ * if this method returns successfully.
+ *
+ * If the return value indicates error (method throws an exception)
+ * the redirect is vetoed and no callback must be done. Repeat: No
+ * callback must be done if this method throws!
+ *
+ * NOTE: originalURI isn't yet set on the new channel when
+ * asyncOnChannelRedirect is called.
+ *
+ * @see nsIAsyncVerifyRedirectCallback::onRedirectVerifyCallback()
+ *
+ * @param oldChannel
+ * The channel that's being redirected.
+ * @param newChannel
+ * The new channel. This channel is not opened yet.
+ * @param flags
+ * Flags indicating the type of redirect. A bitmask consisting
+ * of flags from above.
+ * One of REDIRECT_TEMPORARY and REDIRECT_PERMANENT will always be
+ * set.
+ * @param callback
+ * Object to inform about the async result of this method
+ *
+ * @throw <any> Throwing an exception will cause the redirect to be
+ * cancelled
+ */
+ void asyncOnChannelRedirect(in nsIChannel oldChannel,
+ in nsIChannel newChannel,
+ in unsigned long flags,
+ in nsIAsyncVerifyRedirectCallback callback);
+};
diff --git a/netwerk/base/nsIChildChannel.idl b/netwerk/base/nsIChildChannel.idl
new file mode 100644
index 0000000000..3b57013536
--- /dev/null
+++ b/netwerk/base/nsIChildChannel.idl
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+/**
+ * Implemented by content side of IPC protocols.
+ */
+
+[scriptable, uuid(c45b92ae-4f07-41dd-b0ef-aa044eeabb1e)]
+interface nsIChildChannel : nsISupports
+{
+ /**
+ * Create the chrome side of the IPC protocol and join an existing 'real'
+ * channel on the parent process. The id is provided by
+ * nsIRedirectChannelRegistrar on the chrome process and pushed to the child
+ * protocol as an argument to event starting a redirect.
+ *
+ * Primarilly used in HttpChannelChild::Redirect1Begin on a newly created
+ * child channel, where the new channel is intended to be created on the
+ * child process.
+ */
+ void connectParent(in uint32_t registrarId);
+
+ /**
+ * As AsyncOpen is called on the chrome process for redirect target channels,
+ * we have to inform the child side of the protocol of that fact by a special
+ * method.
+ */
+ void completeRedirectSetup(in nsIStreamListener aListener);
+};
diff --git a/netwerk/base/nsIClassOfService.idl b/netwerk/base/nsIClassOfService.idl
new file mode 100644
index 0000000000..5426eabbcc
--- /dev/null
+++ b/netwerk/base/nsIClassOfService.idl
@@ -0,0 +1,103 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIClassOfService.idl
+ *
+ * Used to express class dependencies and characteristics - complimentary to
+ * nsISupportsPriority which is used to express weight
+ *
+ * Channels that implement this interface may make use of this
+ * information in different ways.
+ */
+
+// convenience class for passing around the class of service
+%{C++
+namespace mozilla::net {
+class ClassOfService;
+}
+%}
+native ClassOfService(mozilla::net::ClassOfService);
+
+[scriptable, uuid(1ccb58ec-5e07-4cf9-a30d-ac5490d23b41)]
+interface nsIClassOfService : nsISupports
+{
+ attribute unsigned long classFlags;
+ attribute bool incremental;
+
+ void clearClassFlags(in unsigned long flags);
+ void addClassFlags(in unsigned long flags);
+ void setClassOfService(in ClassOfService s);
+
+ // All these flags have a (de)prioritization effect.
+
+ // In the HTTP/1 world the priority is considered for all requests inside a so
+ // called 'Request Context' which is a context common to all sub-resources
+ // belonging to a single top level window (RequestContextService). Requests
+ // marked with the Leader flag are blocking (preventing from being sent to the
+ // server) all other resource loads except those marked with the Unblocked
+ // flag. Other classes run in parallel - neither being blocked no ;r blocking.
+ // The Leader flag is used only for <head> blocking resources (sync and
+ // defer javascript resources and stylesheets.) Purpose is to deliver these
+ // first-paint and domcontentloaded blocking resources as soon as possbile.
+
+ // In the HTTP/2 world it's different. Priorities are done only per HTTP/2
+ // session, normally we have one session per one origin (including origin
+ // attributes!) Requests are dispatched (sent) immediately on an HTTP/2
+ // session. Each session has artificial streams (groups) relating to the class
+ // of service flags (Leader, Other, Background, Speculative, Follower,
+ // UrgentStart), each such a stream is given a different weight (only way to
+ // give a stream a priority in HTTP/2) reflecting the desired request group
+ // priority. Actual request streams are then dependent on these artificial
+ // streams (groups). nsISupportsPriority of each request is passed as a weight
+ // on the HTTP/2 stream to prioritize streams in the same group. A stream can
+ // also be dependent on other stream. We have dependency of Followers on
+ // Leaders, hence everything set the Follower flag should be processed by the
+ // server after Leaders. Same for Speculative being dependent on Background. The
+ // tree is created and used here:
+ // https://searchfox.org/mozilla-central/rev/cc280c4be94ff8cf64a27cc9b3d6831ffa49fa45/netwerk/protocol/http/Http2Session.cpp#1053-1070
+ // https://searchfox.org/mozilla-central/rev/cc280c4be94ff8cf64a27cc9b3d6831ffa49fa45/netwerk/protocol/http/Http2Stream.cpp#1338
+ // For detailed description of how HTTP/2 server should handle the priorities
+ // and dependencies see:
+ // https://developers.google.com/web/fundamentals/performance/http2/#stream_prioritization
+ // Please note that the dependecies and weights we are sending to the server
+ // are only suggestions, the server may completely ignore it.
+
+ // Leaders (should) block all other resources except Unblocked. This flag
+ // also priortizes HTTP cache reading queue by blocking all other cache
+ // requests.
+ const unsigned long Leader = 1 << 0;
+ // The Follower flag is currently unused!
+ const unsigned long Follower = 1 << 1;
+ // The Speculative flag is currently unused!
+ const unsigned long Speculative = 1 << 2;
+ // The Background flag is currently only used for Beacon.
+ const unsigned long Background = 1 << 3;
+ // Requests marked with this flag are not blocked by Leaders. This is mainly
+ // used for probing-like XMLHttpRequests that may block delivery of head
+ // blocking resources, e.g. CSS files tailored for the UA.
+ const unsigned long Unblocked = 1 << 4;
+ // Throttleable flag allows response throttling of the resource load. Note
+ // that this functionality is currently disabled.
+ const unsigned long Throttleable = 1 << 5;
+ // UrgentStart makes the request temporarily extend HTTP/1 connection
+ // parallelism limits. Used mainly for navigational requests (top level html)
+ // and any request considered coming from a user interaction to make reaction
+ // of the browser as fast as possible and not blocked.
+ const unsigned long UrgentStart = 1 << 6;
+ // Specifically disables throttling under any circumstances, used for media
+ // responses mainly.
+ const unsigned long DontThrottle = 1 << 7;
+ // Enforce tailing on this load; any of Leader, Unblocked, UrgentStart,
+ // TailForbidden overrule this flag (disable tailing.)
+ const unsigned long Tail = 1 << 8;
+ // Tailing may be engaged regardless if the load is marked Unblocked when some
+ // other conditions are met later, like when the load is found to be a
+ // tracker.
+ const unsigned long TailAllowed = 1 << 9;
+ // Tailing not allowed under any circumstances or combination of flags.
+ const unsigned long TailForbidden = 1 << 10;
+};
diff --git a/netwerk/base/nsIClassifiedChannel.idl b/netwerk/base/nsIClassifiedChannel.idl
new file mode 100644
index 0000000000..482c524cbf
--- /dev/null
+++ b/netwerk/base/nsIClassifiedChannel.idl
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIClassifiedChannel
+ *
+ * A channel may optionally implement this interface if it carries classified
+ * result information of channel classifier. The information contains, for
+ * example, the name of matched table and the name of matched provider.
+ */
+[builtinclass, scriptable, uuid(70cf6091-a1de-4aa8-8224-058f8964be31)]
+interface nsIClassifiedChannel : nsISupports
+{
+ /**
+ * Sets matched info of the classified channel.
+ *
+ * @param aList
+ * Name of the Safe Browsing list that matched (e.g. goog-phish-shavar).
+ * @param aProvider
+ * Name of the Safe Browsing provider that matched (e.g. google)
+ * @param aFullHash
+ * Full hash of URL that matched Safe Browsing list.
+ */
+ void setMatchedInfo(in ACString aList,
+ in ACString aProvider,
+ in ACString aFullHash);
+
+ /**
+ * Name of the list that matched
+ */
+ readonly attribute ACString matchedList;
+
+ /**
+ * Name of provider that matched
+ */
+ readonly attribute ACString matchedProvider;
+
+ /**
+ * Full hash of URL that matched
+ */
+ readonly attribute ACString matchedFullHash;
+
+ /**
+ * Sets matched tracking info of the classified channel.
+ *
+ * @param aLists
+ * Name of the Tracking Protection list that matched (e.g. content-track-digest256).
+ * @param aFullHash
+ * Full hash of URLs that matched Tracking Protection list.
+ */
+ void setMatchedTrackingInfo(in Array<ACString> aLists,
+ in Array<ACString> aFullHashes);
+
+ /**
+ * Name of the lists that matched
+ */
+ readonly attribute Array<ACString> matchedTrackingLists;
+
+ /**
+ * Full hash of URLs that matched
+ */
+ readonly attribute Array<ACString> matchedTrackingFullHashes;
+
+ /**
+ * Returns the classification flags if the channel has been processed by
+ * URL-Classifier features and is considered first-party.
+ */
+ [infallible] readonly attribute unsigned long firstPartyClassificationFlags;
+
+ /**
+ * Returns the classification flags if the channel has been processed by
+ * URL-Classifier features and is considered third-party with the top
+ * window URI.
+ */
+ [infallible] readonly attribute unsigned long thirdPartyClassificationFlags;
+
+ /*
+ * Returns the classification flags if the channel has been processed by
+ * URL-Classifier features. This value is equal to
+ * "firstPartyClassificationFlags || thirdPartyClassificationFlags".
+ *
+ * Note that top-level channels could be classified as well.
+ * In order to identify third-party resources specifically, use
+ * classificationThirdPartyFlags;
+ */
+ [infallible] readonly attribute unsigned long classificationFlags;
+
+ cenum ClassificationFlags : 32 {
+ /**
+ * The resource is on the fingerprinting list.
+ */
+ CLASSIFIED_FINGERPRINTING = 0x0001,
+ CLASSIFIED_FINGERPRINTING_CONTENT = 0x0080,
+
+ /**
+ * The resource is on the cryptomining list.
+ */
+ CLASSIFIED_CRYPTOMINING = 0x0002,
+ CLASSIFIED_CRYPTOMINING_CONTENT = 0x0100,
+
+ /**
+ * The following are about tracking annotation and are available only
+ * if the privacy.trackingprotection.annotate_channels pref.
+ * CLASSIFIED_TRACKING is set if we are not able to identify the
+ * type of classification.
+ */
+ CLASSIFIED_TRACKING = 0x0004,
+ CLASSIFIED_TRACKING_AD = 0x0008,
+ CLASSIFIED_TRACKING_ANALYTICS = 0x0010,
+ CLASSIFIED_TRACKING_SOCIAL = 0x0020,
+ CLASSIFIED_TRACKING_CONTENT = 0x0040,
+
+ /**
+ * The following are about social tracking.
+ */
+ CLASSIFIED_SOCIALTRACKING = 0x0200,
+ CLASSIFIED_SOCIALTRACKING_FACEBOOK = 0x0400,
+ CLASSIFIED_SOCIALTRACKING_LINKEDIN = 0x0800,
+ CLASSIFIED_SOCIALTRACKING_TWITTER = 0x1000,
+
+ /**
+ * The following are about email tracking.
+ */
+ CLASSIFIED_EMAILTRACKING = 0x2000,
+ CLASSIFIED_EMAILTRACKING_CONTENT = 0x4000,
+
+ /**
+ * This is exposed to help to identify tracking classification using the
+ * basic lists.
+ */
+ CLASSIFIED_ANY_BASIC_TRACKING = CLASSIFIED_TRACKING |
+ CLASSIFIED_TRACKING_AD | CLASSIFIED_TRACKING_ANALYTICS |
+ CLASSIFIED_TRACKING_SOCIAL | CLASSIFIED_FINGERPRINTING,
+
+ /**
+ * This is exposed to help to identify tracking classification using the
+ * strict lists.
+ */
+ CLASSIFIED_ANY_STRICT_TRACKING = CLASSIFIED_ANY_BASIC_TRACKING |
+ CLASSIFIED_TRACKING_CONTENT | CLASSIFIED_FINGERPRINTING_CONTENT,
+
+ /**
+ * This is exposed to help to identify social tracking classification
+ * flags.
+ */
+ CLASSIFIED_ANY_SOCIAL_TRACKING = CLASSIFIED_SOCIALTRACKING |
+ CLASSIFIED_SOCIALTRACKING_FACEBOOK |
+ CLASSIFIED_SOCIALTRACKING_LINKEDIN | CLASSIFIED_SOCIALTRACKING_TWITTER,
+ };
+
+ /**
+ * Returns true if the channel has been processed by URL-Classifier features
+ * and is considered third-party with the top window URI, and if it has loaded
+ * a resource that is classified as a tracker.
+ *
+ * This is a helper attribute which returns the same value of
+ * (thirdPartyClassificationFlags & CLASSIFIED_ANY_BASIC_TRACKING) or
+ * (thirdPartyClassificationFlags & CLASSIFIED_ANY_STRICT_TRACKING) or
+ * (thirdPartyClassificationFlags & CLASSIFIED_ANY_SOCIAL_TRACKING)
+ */
+ boolean isThirdPartyTrackingResource();
+
+%{ C++
+ inline bool IsThirdPartyTrackingResource()
+ {
+ bool value = false;
+ if (NS_SUCCEEDED(IsThirdPartyTrackingResource(&value)) && value) {
+ return true;
+ }
+ return false;
+ }
+%}
+
+ /**
+ * Returns true if the channel has loaded a 3rd party resource that is
+ * classified as a social tracker.
+ *
+ * This is a helper attribute which returns the same value of
+ * (classificationFlags & CLASSIFIED_ANY_SOCIAL_TRACKING)
+ *
+ * Note that top-level channels could be marked as tracking
+ * resources. In order to identify third-party social tracking resources
+ * specifically, check the flags manually or add a new helper here.
+ */
+ boolean isThirdPartySocialTrackingResource();
+
+%{ C++
+ inline bool IsThirdPartySocialTrackingResource()
+ {
+ bool value = false;
+ if (NS_SUCCEEDED(IsThirdPartySocialTrackingResource(&value)) && value) {
+ return true;
+ }
+ return false;
+ }
+%}
+};
diff --git a/netwerk/base/nsIContentSniffer.idl b/netwerk/base/nsIContentSniffer.idl
new file mode 100644
index 0000000000..f9052b8e60
--- /dev/null
+++ b/netwerk/base/nsIContentSniffer.idl
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIRequest;
+
+/**
+ * Content sniffer interface. Components implementing this interface can
+ * determine a MIME type from a chunk of bytes.
+ */
+[scriptable, uuid(a5772d1b-fc63-495e-a169-96e8d3311af0)]
+interface nsIContentSniffer : nsISupports
+{
+ /**
+ * Given a chunk of data, determines a MIME type. Information from the given
+ * request may be used in order to make a better decision.
+ *
+ * @param aRequest The request where this data came from. May be null.
+ * @param aData Data to check
+ * @param aLength Length of the data
+ *
+ * @return The content type
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if no MIME type could be determined.
+ *
+ * @note Implementations should consider the request read-only. Especially,
+ * they should not attempt to set the content type property that subclasses of
+ * nsIRequest might offer.
+ */
+ ACString getMIMETypeFromContent(in nsIRequest aRequest,
+ [const,array,size_is(aLength)] in octet aData,
+ in unsigned long aLength);
+};
diff --git a/netwerk/base/nsIDHCPClient.idl b/netwerk/base/nsIDHCPClient.idl
new file mode 100644
index 0000000000..1d986ebd52
--- /dev/null
+++ b/netwerk/base/nsIDHCPClient.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface allows the proxy code to access the DHCP Options in a platform-specific way
+ */
+[scriptable, uuid(aee75dc0-be1a-46b9-9e0c-31a6899c175c)]
+interface nsIDHCPClient : nsISupports
+{
+
+ /**
+ * returns the DHCP Option designated by the option parameter
+ */
+ ACString getOption(in uint8_t option);
+};
diff --git a/netwerk/base/nsIDashboard.idl b/netwerk/base/nsIDashboard.idl
new file mode 100644
index 0000000000..ae80a9e458
--- /dev/null
+++ b/netwerk/base/nsIDashboard.idl
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/* A JavaScript callback function that takes a JSON as its parameter.
+ * The returned JSON contains arrays with data
+ */
+[scriptable, function, uuid(19d7f24f-a95a-4fd9-87e2-d96e9e4b1f6d)]
+interface nsINetDashboardCallback : nsISupports
+{
+ void onDashboardDataAvailable(in jsval data);
+};
+
+/* The dashboard service.
+ * The async API returns JSONs, which hold arrays with the required info.
+ * Only one request of each type may be pending at any time.
+ */
+[scriptable, uuid(c79eb3c6-091a-45a6-8544-5a8d1ab79537)]
+interface nsIDashboard : nsISupports
+{
+ /* Arrays: host, port, tcp, active, socksent, sockreceived
+ * Values: sent, received */
+ void requestSockets(in nsINetDashboardCallback cb);
+
+ /* Arrays: host, port, spdy, ssl
+ * Array of arrays: active, idle */
+ void requestHttpConnections(in nsINetDashboardCallback cb);
+
+ /* Arrays: hostport, encrypted, msgsent, msgreceived, sentsize, receivedsize */
+ void requestWebsocketConnections(in nsINetDashboardCallback cb);
+
+ /* Arrays: hostname, family, hostaddr, expiration */
+ void requestDNSInfo(in nsINetDashboardCallback cb);
+
+ /* aProtocol: a transport layer protocol:
+ * ex: "ssl", "tcp", default is "tcp".
+ * aHost: the host's name
+ * aPort: the port which the connection will open on
+ * aTimeout: the timespan before the connection will be timed out */
+ void requestConnection(in ACString aHost, in unsigned long aPort,
+ in string aProtocol, in unsigned long aTimeout,
+ in nsINetDashboardCallback cb);
+
+ /* When true, the service will log websocket events */
+ attribute boolean enableLogging;
+
+ /* DNS resolver for host name
+ * aHost: host name */
+ void requestDNSLookup(in ACString aHost, in nsINetDashboardCallback cb);
+
+ /* Resolve HTTPS RRs for host name
+ * aHost: host name */
+ void requestDNSHTTPSRRLookup(in ACString aHost,
+ in nsINetDashboardCallback cb);
+
+ /**
+ * Asyncly returns stats regarding the "Race Cache With Network" feature.
+ */
+ void requestRcwnStats(in nsINetDashboardCallback cb);
+
+ AUTF8String getLogPath();
+};
diff --git a/netwerk/base/nsIDashboardEventNotifier.idl b/netwerk/base/nsIDashboardEventNotifier.idl
new file mode 100644
index 0000000000..933caf4371
--- /dev/null
+++ b/netwerk/base/nsIDashboardEventNotifier.idl
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[uuid(24fdfcbe-54cb-4997-8392-3c476126ea3b)]
+interface nsIDashboardEventNotifier : nsISupports
+{
+ /* These methods are called to register a websocket event with the dashboard
+ *
+ * A host is identified by the (aHost, aSerial) pair.
+ * aHost: the host's name: example.com
+ * aSerial: a number that uniquely identifies the websocket
+ *
+ * aEncrypted: if the connection is encrypted
+ * aLength: the length of the message in bytes
+ */
+ void addHost(in ACString aHost, in unsigned long aSerial, in boolean aEncrypted);
+ void removeHost(in ACString aHost, in unsigned long aSerial);
+ void newMsgSent(in ACString aHost, in unsigned long aSerial, in unsigned long aLength);
+ void newMsgReceived(in ACString aHost, in unsigned long aSerial, in unsigned long aLength);
+};
diff --git a/netwerk/base/nsIDownloader.idl b/netwerk/base/nsIDownloader.idl
new file mode 100644
index 0000000000..c887c32c74
--- /dev/null
+++ b/netwerk/base/nsIDownloader.idl
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIFile;
+interface nsIDownloadObserver;
+
+/**
+ * nsIDownloader
+ *
+ * A downloader is a special implementation of a nsIStreamListener that will
+ * make the contents of the stream available as a file. This may utilize the
+ * disk cache as an optimization to avoid an extra copy of the data on disk.
+ * The resulting file is valid from the time the downloader completes until
+ * the last reference to the downloader is released.
+ */
+[scriptable, uuid(fafe41a9-a531-4d6d-89bc-588a6522fb4e)]
+interface nsIDownloader : nsIStreamListener
+{
+ /**
+ * Initialize this downloader
+ *
+ * @param observer
+ * the observer to be notified when the download completes.
+ * @param downloadLocation
+ * the location where the stream contents should be written.
+ * if null, the downloader will select a location and the
+ * resulting file will be deleted (or otherwise made invalid)
+ * when the downloader object is destroyed. if an explicit
+ * download location is specified then the resulting file will
+ * not be deleted, and it will be the callers responsibility
+ * to keep track of the file, etc.
+ */
+ void init(in nsIDownloadObserver observer,
+ in nsIFile downloadLocation);
+};
+
+[scriptable, uuid(44b3153e-a54e-4077-a527-b0325e40924e)]
+interface nsIDownloadObserver : nsISupports
+{
+ /**
+ * Called to signal a download that has completed.
+ */
+ void onDownloadComplete(in nsIDownloader downloader,
+ in nsIRequest request,
+ in nsresult status,
+ in nsIFile result);
+};
diff --git a/netwerk/base/nsIEncodedChannel.idl b/netwerk/base/nsIEncodedChannel.idl
new file mode 100644
index 0000000000..e1db3993c7
--- /dev/null
+++ b/netwerk/base/nsIEncodedChannel.idl
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIUTF8StringEnumerator;
+interface nsIStreamListener;
+interface nsISupports;
+
+/**
+ * A channel interface which allows special handling of encoded content
+ */
+
+[scriptable, uuid(29c29ce6-8ce4-45e6-8d60-36c8fa3e255b)]
+interface nsIEncodedChannel : nsISupports
+{
+ /**
+ * This attribute holds the MIME types corresponding to the content
+ * encodings on the channel. The enumerator returns nsISupportsCString
+ * objects. The first one corresponds to the outermost encoding on the
+ * channel and then we work our way inward. "identity" is skipped and not
+ * represented on the list. Unknown encodings make the enumeration stop.
+ * If you want the actual Content-Encoding value, use
+ * getResponseHeader("Content-Encoding").
+ *
+ * When there is no Content-Encoding header, this property is null.
+ *
+ * Modifying the Content-Encoding header on the channel will cause
+ * this enumerator to have undefined behavior. Don't do it.
+ *
+ * Also note that contentEncodings only exist during or after OnStartRequest.
+ * Calling contentEncodings before OnStartRequest is an error.
+ */
+ readonly attribute nsIUTF8StringEnumerator contentEncodings;
+
+ /**
+ * This attribute controls whether or not content conversion should be
+ * done per the Content-Encoding response header. applyConversion can only
+ * be set before or during OnStartRequest. Calling this during
+ * OnDataAvailable is an error.
+ *
+ * TRUE by default.
+ */
+ attribute boolean applyConversion;
+
+ /**
+ * This attribute indicates the content has been decompressed in
+ * the parent process.
+ */
+ attribute boolean hasContentDecompressed;
+ /**
+ * This function will start converters if they are available.
+ * aNewNextListener will be nullptr if no converter is available.
+ */
+ void doApplyContentConversions(in nsIStreamListener aNextListener,
+ out nsIStreamListener aNewNextListener,
+ in nsISupports aCtxt);
+};
diff --git a/netwerk/base/nsIExternalProtocolHandler.idl b/netwerk/base/nsIExternalProtocolHandler.idl
new file mode 100644
index 0000000000..6f86dbb05a
--- /dev/null
+++ b/netwerk/base/nsIExternalProtocolHandler.idl
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+[scriptable, uuid(0e61f3b2-34d7-4c79-bfdc-4860bc7341b7)]
+interface nsIExternalProtocolHandler: nsIProtocolHandler
+{
+ /**
+ * This method checks if the external handler exists for a given scheme.
+ *
+ * @param scheme external scheme.
+ * @return TRUE if the external handler exists for the input scheme, FALSE otherwise.
+ */
+ boolean externalAppExistsForScheme(in ACString scheme);
+};
diff --git a/netwerk/base/nsIFileStreams.idl b/netwerk/base/nsIFileStreams.idl
new file mode 100644
index 0000000000..026fa41bcf
--- /dev/null
+++ b/netwerk/base/nsIFileStreams.idl
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+#include "nsIOutputStream.idl"
+#include "nsIRandomAccessStream.idl"
+
+interface nsIEventTarget;
+interface nsIFile;
+interface nsIFileMetadataCallback;
+
+%{C++
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescPtr(PRFileDesc);
+
+/**
+ * An input stream that allows you to read from a file.
+ */
+[scriptable, builtinclass, uuid(e3d56a20-c7ec-11d3-8cda-0060b0fc14a3)]
+interface nsIFileInputStream : nsIInputStream
+{
+ /**
+ * @param file file to read from
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDONLY).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in the class)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * If this is set, the file will close automatically when the end of the
+ * file is reached.
+ */
+ const long CLOSE_ON_EOF = 1<<2;
+
+ /**
+ * If this is set, the file will be reopened whenever we reach the start of
+ * the file, either by doing a Seek(0, NS_SEEK_CUR), or by doing a relative
+ * seek that happen to reach the beginning of the file. If the file is
+ * already open and the seek occurs, it will happen naturally. (The file
+ * will only be reopened if it is closed for some reason.)
+ */
+ const long REOPEN_ON_REWIND = 1<<3;
+
+ /**
+ * If this is set, the file will be opened (i.e., a call to
+ * PR_Open done) only when we do an actual operation on the stream,
+ * or more specifically, when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Available
+ * - Read
+ * - ReadLine
+ *
+ * DEFER_OPEN is useful if we use the stream on a background
+ * thread, so that the opening and possible |stat|ing of the file
+ * happens there as well.
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first read. Also, the file is not locked when Init is called,
+ * so it might be deleted before we try to read from it.
+ */
+ const long DEFER_OPEN = 1<<4;
+
+ /**
+ * This flag has no effect and is totally ignored on any platform except
+ * Windows since this is the default behavior on POSIX systems. On Windows
+ * if this flag is set then the stream is opened in a special mode that
+ * allows the OS to delete the file from disk just like POSIX.
+ */
+ const long SHARE_DELETE = 1<<5;
+};
+
+/**
+ * An output stream that lets you stream to a file.
+ */
+[scriptable, builtinclass, uuid(e734cac9-1295-4e6f-9684-3ac4e1f91063)]
+interface nsIFileOutputStream : nsIOutputStream
+{
+ /**
+ * @param file file to write to
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_WRONLY |
+ * PR_CREATE_FILE | PR_TRUNCATE)
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default permissions (0664)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (currently none supported)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * @param length asks the operating system to allocate storage for
+ * this file of at least |length| bytes long, and
+ * set the file length to the corresponding size.
+ * @throws NS_ERROR_FAILURE if the preallocation fails.
+ * @throws NS_ERROR_NOT_INITIALIZED if the file is not opened.
+ */
+ [noscript] void preallocate(in long long length);
+
+ /**
+ * See the same constant in nsIFileInputStream. The deferred open will
+ * be performed when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Write
+ * - Flush
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first write, and if the file is to be created, then it will not
+ * appear on the disk until the first write.
+ */
+ const long DEFER_OPEN = 1<<0;
+};
+
+/**
+ * A stream that allows you to read from a file or stream to a file.
+ */
+[scriptable, builtinclass, uuid(82cf605a-8393-4550-83ab-43cd5578e006)]
+interface nsIFileRandomAccessStream : nsIRandomAccessStream
+{
+ /**
+ * @param file file to read from or stream to
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDWR).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in the class)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * See the same constant in nsIFileInputStream. The deferred open will
+ * be performed when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Available
+ * - Read
+ * - Flush
+ * - Write
+ * - GetSize
+ * - GetLastModified
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first read or write. The file is not locked when Init is called,
+ * so it might be deleted before we try to read from it and if the
+ * file is to be created, then it will not appear on the disk until
+ * the first write.
+ */
+ const long DEFER_OPEN = 1<<0;
+};
+
+/**
+ * An interface that allows you to get some metadata like file size and
+ * file last modified time. These methods and attributes can throw
+ * NS_BASE_STREAM_WOULD_BLOCK in case the informations are not available yet.
+ * If this happens, consider the use of nsIAsyncFileMetadata.
+ *
+ * If using nsIAsyncFileMetadata, you should retrieve any data via this
+ * interface before taking any action that might consume the underlying stream.
+ * For example, once Available(), Read(), or nsIAsyncInputStream::AsyncWait()
+ * are invoked, these methods may return NS_BASE_STREAM_CLOSED. This will
+ * happen when using RemoteLazyInputStream with an underlying file stream, for
+ * example.
+ */
+[scriptable, builtinclass, uuid(07f679e4-9601-4bd1-b510-cd3852edb881)]
+interface nsIFileMetadata : nsISupports
+{
+ /**
+ * File size in bytes.
+ */
+ readonly attribute long long size;
+
+ /**
+ * File last modified time in milliseconds from midnight (00:00:00),
+ * January 1, 1970 Greenwich Mean Time (GMT).
+ */
+ readonly attribute long long lastModified;
+
+ /**
+ * The internal file descriptor. It can be used for memory mapping of the
+ * underlying file. Please use carefully! If this returns
+ * NS_BASE_STREAM_WOULD_BLOCK, consider the use of nsIAsyncFileMetadata.
+ */
+ [noscript] PRFileDescPtr getFileDescriptor();
+};
+
+[scriptable, builtinclass, uuid(de15b80b-29ba-4b7f-9220-a3d75b17ae8c)]
+interface nsIAsyncFileMetadata : nsIFileMetadata
+{
+ /**
+ * Asynchronously wait for the object to be ready.
+ *
+ * @param aCallback The callback will be used when the stream is ready to
+ * return File metadata. Use a nullptr to cancel a
+ * previous operation.
+ *
+ * @param aEventTarget The event target where aCallback will be executed.
+ * If aCallback is passed, aEventTarget cannot be null.
+ */
+ void asyncFileMetadataWait(in nsIFileMetadataCallback aCallback,
+ in nsIEventTarget aEventTarget);
+};
+
+/**
+ * This is a companion interface for
+ * nsIAsyncFileMetadata::asyncFileMetadataWait.
+ */
+[function, scriptable, uuid(d01c7ead-7ba3-4726-b399-618ec8ec7057)]
+interface nsIFileMetadataCallback : nsISupports
+{
+ /**
+ * Called to indicate that the nsIFileMetadata object is ready.
+ */
+ void onFileMetadataReady(in nsIAsyncFileMetadata aObject);
+};
diff --git a/netwerk/base/nsIFileURL.idl b/netwerk/base/nsIFileURL.idl
new file mode 100644
index 0000000000..3c97a2e3af
--- /dev/null
+++ b/netwerk/base/nsIFileURL.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURL.idl"
+
+interface nsIFile;
+interface nsIURIMutator;
+
+/**
+ * nsIFileURL provides access to the underlying nsIFile object corresponding to
+ * an URL. The URL scheme need not be file:, since other local protocols may
+ * map URLs to files (e.g., resource:).
+ */
+[scriptable, builtinclass, uuid(e91ac988-27c2-448b-b1a1-3822e1ef1987)]
+interface nsIFileURL : nsIURL
+{
+ /**
+ * Get the nsIFile corresponding to this URL.
+ *
+ * - Returns a reference to an immutable object. Callers must clone
+ * before attempting to modify the returned nsIFile object. NOTE: this
+ * constraint might not be enforced at runtime, so beware!!
+ */
+ readonly attribute nsIFile file;
+};
+
+[scriptable, builtinclass, uuid(a588b6f2-d2b9-4024-84c7-be3368546b57)]
+interface nsIFileURLMutator : nsISupports
+{
+ /*
+ * - Marks the inner URI implementation as one that supports nsIFileURL.
+ */
+ [must_use, noscript] void markFileURL();
+
+ /*
+ * - Setter clones the nsIFile object (allowing the caller to safely modify
+ * the nsIFile object after setting it on this interface).
+ */
+ [must_use, noscript] void setFile(in nsIFile aFile);
+};
diff --git a/netwerk/base/nsIForcePendingChannel.idl b/netwerk/base/nsIForcePendingChannel.idl
new file mode 100644
index 0000000000..714c83f52d
--- /dev/null
+++ b/netwerk/base/nsIForcePendingChannel.idl
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIForcePending interface exposes a function that enables overwriting of the normal
+ * behavior for the channel's IsPending(), forcing 'true' to be returned.
+ */
+
+[uuid(2ac3e1ca-049f-44c3-a519-f0681f51e9b1)]
+interface nsIForcePendingChannel : nsISupports
+{
+
+ /**
+ * forcePending(true) overrides the normal behavior for the
+ * channel's IsPending(), forcing 'true' to be returned. A call to
+ * forcePending(false) reverts IsPending() back to normal behavior.
+ */
+ void forcePending(in boolean aForcePending);
+};
diff --git a/netwerk/base/nsIFormPOSTActionChannel.idl b/netwerk/base/nsIFormPOSTActionChannel.idl
new file mode 100644
index 0000000000..870886390c
--- /dev/null
+++ b/netwerk/base/nsIFormPOSTActionChannel.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIUploadChannel.idl"
+
+/**
+ * nsIFormPOSTActionChannel
+ *
+ * Channel classes that want to be allowed for HTML form POST action must
+ * implement this interface.
+ */
+[scriptable, uuid(fc826b53-0db8-42b4-aa6a-5dd2cfca52a4)]
+interface nsIFormPOSTActionChannel : nsIUploadChannel
+{
+};
diff --git a/netwerk/base/nsIHttpAuthenticatorCallback.idl b/netwerk/base/nsIHttpAuthenticatorCallback.idl
new file mode 100644
index 0000000000..74fd3c97ab
--- /dev/null
+++ b/netwerk/base/nsIHttpAuthenticatorCallback.idl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[uuid(d989cb03-e446-4086-b9e6-46842cb97bd5)]
+interface nsIHttpAuthenticatorCallback : nsISupports
+{
+ /**
+ * Authentication data for a header is available.
+ *
+ * @param aCreds
+ * Credentials which were obtained asynchonously.
+ * @param aFlags
+ * Flags set by asynchronous call.
+ * @param aResult
+ * Result status of credentials generation
+ * @param aSessionState
+ * Modified session state to be passed to caller
+ * @param aContinuationState
+ * Modified continuation state to be passed to caller
+ */
+ void onCredsGenerated(in ACString aCreds,
+ in unsigned long aFlags,
+ in nsresult aResult,
+ in nsISupports aSessionsState,
+ in nsISupports aContinuationState);
+
+};
diff --git a/netwerk/base/nsIHttpPushListener.idl b/netwerk/base/nsIHttpPushListener.idl
new file mode 100644
index 0000000000..f44605c000
--- /dev/null
+++ b/netwerk/base/nsIHttpPushListener.idl
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIHttpChannel;
+
+/**
+ * nsIHttpPushListener
+ *
+ * Used for triggering when a HTTP/2 push is received.
+ *
+ */
+[scriptable, uuid(0d6ce59c-ad5d-4520-b4d3-09664868f279)]
+interface nsIHttpPushListener : nsISupports
+{
+ /**
+ * When provided as a notificationCallback to an httpChannel, this.onPush()
+ * will be invoked when there is a >= Http2 push to that
+ * channel. The push may be in progress.
+ *
+ * The consumer must start the new channel in the usual way by calling
+ * pushChannel.AsyncOpen with a nsIStreamListener object that
+ * will receive the normal sequence of OnStartRequest(),
+ * 0 to N OnDataAvailable(), and onStopRequest().
+ *
+ * The new channel can be canceled after the AsyncOpen if it is not wanted.
+ *
+ * @param associatedChannel
+ * the monitor channel that was recieved on
+ * @param pushChannel
+ * a channel to the resource which is being pushed
+ */
+ void onPush(in nsIHttpChannel associatedChannel,
+ in nsIHttpChannel pushChannel);
+};
diff --git a/netwerk/base/nsIIOService.idl b/netwerk/base/nsIIOService.idl
new file mode 100644
index 0000000000..5bc3e41181
--- /dev/null
+++ b/netwerk/base/nsIIOService.idl
@@ -0,0 +1,362 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsIProtocolHandler;
+interface nsIChannel;
+interface nsIURI;
+interface nsIFile;
+interface nsIPrincipal;
+interface nsILoadInfo;
+interface nsIWebTransport;
+
+webidl Node;
+
+%{C++
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+namespace dom {
+class ClientInfo;
+class ServiceWorkerDescriptor;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ref] native const_MaybeClientInfoRef(const mozilla::Maybe<mozilla::dom::ClientInfo>);
+[ref] native const_MaybeServiceWorkerDescriptorRef(const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>);
+
+/**
+ * nsIIOService provides a set of network utility functions. This interface
+ * duplicates many of the nsIProtocolHandler methods in a protocol handler
+ * independent way (e.g., NewURI inspects the scheme in order to delegate
+ * creation of the new URI to the appropriate protocol handler). nsIIOService
+ * also provides a set of URL parsing utility functions. These are provided
+ * as a convenience to the programmer and in some cases to improve performance
+ * by eliminating intermediate data structures and interfaces.
+ */
+[scriptable, builtinclass, uuid(4286de5a-b2ea-446f-8f70-e2a461f42694)]
+interface nsIIOService : nsISupports
+{
+ /**
+ * Returns a protocol handler for a given URI scheme.
+ *
+ * @param aScheme the URI scheme
+ * @return reference to corresponding nsIProtocolHandler
+ */
+ nsIProtocolHandler getProtocolHandler(in string aScheme);
+
+ /**
+ * Returns the protocol flags for a given scheme.
+ *
+ * @param aScheme the URI scheme
+ * @return protocol flags for the corresponding protocol
+ */
+ unsigned long getProtocolFlags(in string aScheme);
+
+ /**
+ * Returns the dynamic protocol flags for a given URI.
+ *
+ * @param aURI the URI to get all dynamic flags for
+ * @return protocol flags for that URI
+ */
+ unsigned long getDynamicProtocolFlags(in nsIURI aURI);
+
+ /**
+ * Returns the default port for a given scheme.
+ *
+ * @param aScheme the URI scheme
+ * @return default port for the corresponding protocol
+ */
+ long getDefaultPort(in string aScheme);
+
+ /**
+ * This method constructs a new URI based on the scheme of the URI spec.
+ * QueryInterface can be used on the resulting URI object to obtain a more
+ * specific type of URI.
+ */
+ nsIURI newURI(in AUTF8String aSpec,
+ [optional] in string aOriginCharset,
+ [optional] in nsIURI aBaseURI);
+
+ /**
+ * This method constructs a new URI from a nsIFile.
+ *
+ * @param aFile specifies the file path
+ * @return reference to a new nsIURI object
+ *
+ * Note: in the future, for perf reasons we should allow
+ * callers to specify whether this is a file or directory by
+ * splitting this into newDirURI() and newActualFileURI().
+ */
+ nsIURI newFileURI(in nsIFile aFile);
+
+ /**
+ * Converts an internal URI (e.g. one that has a username and password in
+ * it) into one which we can expose to the user, for example on the URL bar.
+ *
+ * @param aURI The URI to be converted.
+ * @return nsIURI The converted, exposable URI.
+ */
+ nsIURI createExposableURI(in nsIURI aURI);
+
+ /**
+ * Creates a channel for a given URI.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ */
+ nsIChannel newChannelFromURI(in nsIURI aURI,
+ in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType);
+
+ [noscript, nostdcall, notxpcom]
+ nsresult NewChannelFromURIWithClientAndController(in nsIURI aURI,
+ in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in const_MaybeClientInfoRef aLoadingClientInfo,
+ in const_MaybeServiceWorkerDescriptorRef aController,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType,
+ in unsigned long aSandboxFlags,
+ in boolean aSkipCheckForBrokenURLOrZeroSized,
+ out nsIChannel aResult);
+
+ /**
+ * Equivalent to newChannelFromURI(aURI, aLoadingNode, ...)
+ */
+ nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI,
+ in nsILoadInfo aLoadInfo);
+
+ /**
+ * Equivalent to newChannelFromURI(newURI(...))
+ */
+ nsIChannel newChannel(in AUTF8String aSpec,
+ in string aOriginCharset,
+ in nsIURI aBaseURI,
+ in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType);
+
+ /**
+ * Creates a WebTransport.
+ */
+ nsIWebTransport newWebTransport();
+
+ /**
+ * Returns true if networking is in "offline" mode. When in offline mode,
+ * attempts to access the network will fail (although this does not
+ * necessarily correlate with whether there is actually a network
+ * available -- that's hard to detect without causing the dialer to
+ * come up).
+ *
+ * Changing this fires observer notifications ... see below.
+ */
+ attribute boolean offline;
+
+ /**
+ * Returns false if there are no interfaces for a network request
+ */
+ readonly attribute boolean connectivity;
+
+ /**
+ * Checks if a port number is banned. This involves consulting a list of
+ * unsafe ports, corresponding to network services that may be easily
+ * exploitable. If the given port is considered unsafe, then the protocol
+ * handler (corresponding to aScheme) will be asked whether it wishes to
+ * override the IO service's decision to block the port. This gives the
+ * protocol handler ultimate control over its own security policy while
+ * ensuring reasonable, default protection.
+ *
+ * @see nsIProtocolHandler::allowPort
+ */
+ boolean allowPort(in long aPort, in string aScheme);
+
+ /**
+ * Utility to extract the scheme from a URL string, consistently and
+ * according to spec (see RFC 2396).
+ *
+ * NOTE: Most URL parsing is done via nsIURI, and in fact the scheme
+ * can also be extracted from a URL string via nsIURI. This method
+ * is provided purely as an optimization.
+ *
+ * @param aSpec the URL string to parse
+ * @return URL scheme, lowercase
+ *
+ * @throws NS_ERROR_MALFORMED_URI if URL string is not of the right form.
+ */
+ ACString extractScheme(in AUTF8String urlString);
+
+ /**
+ * Checks if a URI host is a local IPv4 or IPv6 address literal.
+ *
+ * @param nsIURI the URI that contains the hostname to check
+ * @return true if the URI hostname is a local IP address
+ */
+ boolean hostnameIsLocalIPAddress(in nsIURI aURI);
+
+ /**
+ * Checks if a URI host is a shared IPv4 address literal.
+ *
+ * @param nsIURI the URI that contains the hostname to check
+ * @return true if the URI hostname is a shared IP address
+ */
+ boolean hostnameIsSharedIPAddress(in nsIURI aURI);
+
+ /**
+ * Checks if a hostname is valid.
+ *
+ * @param AUTF8String hostname is the hostname to validate
+ * @return true if the hostname is valid, else false
+ */
+ boolean isValidHostname(in AUTF8String hostname);
+
+ /**
+ * While this is set, IOService will monitor an nsINetworkLinkService
+ * (if available) and set its offline status to "true" whenever
+ * isLinkUp is false.
+ *
+ * Applications that want to control changes to the IOService's offline
+ * status should set this to false, watch for network:link-status-changed
+ * broadcasts, and change nsIIOService::offline as they see fit. Note
+ * that this means during application startup, IOService may be offline
+ * if there is no link, until application code runs and can turn off
+ * this management.
+ */
+ attribute boolean manageOfflineStatus;
+
+ /**
+ * Creates a channel for a given URI.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aProxyURI
+ * nsIURI to use for proxy resolution. Can be null in which
+ * case aURI is used
+ * @param aProxyFlags flags from nsIProtocolProxyService to use
+ * when resolving proxies for this new channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ */
+ nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI,
+ in nsIURI aProxyURI,
+ in unsigned long aProxyFlags,
+ in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType);
+
+ /**
+ * Return true if socket process is launched.
+ */
+ readonly attribute boolean socketProcessLaunched;
+
+ /**
+ * The pid for socket process.
+ */
+ readonly attribute unsigned long long socketProcessId;
+
+ /**
+ * Register a protocol handler at runtime, given protocol flags and a
+ * default port.
+ *
+ * Statically registered protocol handlers cannot be overridden, and an
+ * error will be returned if that is attempted.
+ *
+ * Runtime registered protocol handlers are never QueryInterface-ed into
+ * `nsIProtocolHandlerWithDynamicFlags`, so that interface will be ignored.
+ *
+ * @param aScheme the scheme handled by the protocol handler.
+ * @param aHandler the protocol handler instance.
+ * @param aProtocolFlags protocol flags for this protocol, see
+ * nsIProtocolHandler for values.
+ * @param aDefaultPort default port for this scheme, or -1.
+ */
+ void registerProtocolHandler(in ACString aScheme,
+ in nsIProtocolHandler aHandler,
+ in unsigned long aProtocolFlags,
+ in long aDefaultPort);
+
+ /**
+ * Unregister a protocol handler which was previously registered using
+ * registerProtocolHandler.
+ *
+ * @param aScheme the scheme to unregister a handler for.
+ */
+ void unregisterProtocolHandler(in ACString aScheme);
+};
+
+%{C++
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_IOSERVICE_GOING_OFFLINE_TOPIC and data NS_IOSERVICE_OFFLINE
+ * when 'offline' has changed from false to true, and we are about
+ * to shut down network services such as DNS. When those
+ * services have been shut down, we send a notification with
+ * topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
+ * NS_IOSERVICE_OFFLINE.
+ *
+ * When 'offline' changes from true to false, then after
+ * network services have been restarted, we send a notification
+ * with topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
+ * NS_IOSERVICE_ONLINE.
+ */
+#define NS_IOSERVICE_GOING_OFFLINE_TOPIC "network:offline-about-to-go-offline"
+#define NS_IOSERVICE_OFFLINE_STATUS_TOPIC "network:offline-status-changed"
+#define NS_IOSERVICE_OFFLINE "offline"
+#define NS_IOSERVICE_ONLINE "online"
+
+%}
+
+[uuid(6633c0bf-d97a-428f-8ece-cb6a655fb95a)]
+interface nsIIOServiceInternal : nsISupports
+{
+ /**
+ * This is an internal method that should only be called from ContentChild
+ * in order to pass the connectivity state from the chrome process to the
+ * content process. It throws if called outside the content process.
+ */
+ void SetConnectivity(in boolean connectivity);
+
+ /**
+ * An internal method to asynchronously run our notifications that happen
+ * when we wake from sleep
+ */
+ void NotifyWakeup();
+};
diff --git a/netwerk/base/nsIIncrementalDownload.idl b/netwerk/base/nsIIncrementalDownload.idl
new file mode 100644
index 0000000000..3ae363c488
--- /dev/null
+++ b/netwerk/base/nsIIncrementalDownload.idl
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIURI;
+interface nsIFile;
+interface nsIRequestObserver;
+
+/**
+ * An incremental download object attempts to fetch a file piecemeal over time
+ * in an effort to minimize network bandwidth usage.
+ *
+ * Canceling a background download does not cause the file on disk to be
+ * deleted.
+ */
+[scriptable, uuid(6687823f-56c4-461d-93a1-7f6cb7dfbfba)]
+interface nsIIncrementalDownload : nsIRequest
+{
+ /**
+ * Initialize the incremental download object. If the destination file
+ * already exists, then only the remaining portion of the file will be
+ * fetched.
+ *
+ * NOTE: The downloader will create the destination file if it does not
+ * already exist. It will create the file with the permissions 0600 if
+ * needed. To affect the permissions of the file, consumers of this
+ * interface may create an empty file at the specified destination prior to
+ * starting the incremental download.
+ *
+ * NOTE: Since this class may create a temporary file at the specified
+ * destination, it is advisable for the consumer of this interface to specify
+ * a file name for the destination that would not tempt the user into
+ * double-clicking it. For example, it might be wise to append a file
+ * extension like ".part" to the end of the destination to protect users from
+ * accidentally running "blah.exe" before it is a complete file.
+ *
+ * @param uri
+ * The URI to fetch.
+ * @param destination
+ * The location where the file is to be stored.
+ * @param chunkSize
+ * The size of the chunks to fetch. A non-positive value results in
+ * the default chunk size being used.
+ * @param intervalInSeconds
+ * The amount of time to wait between fetching chunks. Pass a
+ * negative to use the default interval, or 0 to fetch the remaining
+ * part of the file in one chunk.
+ */
+ void init(in nsIURI uri, in nsIFile destination, in long chunkSize,
+ in long intervalInSeconds);
+
+ /**
+ * The URI being fetched.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The URI being fetched after any redirects have been followed. This
+ * attribute is set just prior to calling OnStartRequest on the observer
+ * passed to the start method.
+ */
+ readonly attribute nsIURI finalURI;
+
+ /**
+ * The file where the download is being written.
+ */
+ readonly attribute nsIFile destination;
+
+ /**
+ * The total number of bytes for the requested file. This attribute is set
+ * just prior to calling OnStartRequest on the observer passed to the start
+ * method.
+ *
+ * This attribute has a value of -1 if the total size is unknown.
+ */
+ readonly attribute long long totalSize;
+
+ /**
+ * The current number of bytes downloaded so far. This attribute is set just
+ * prior to calling OnStartRequest on the observer passed to the start
+ * method.
+ *
+ * This attribute has a value of -1 if the current size is unknown.
+ */
+ readonly attribute long long currentSize;
+
+ /**
+ * Start the incremental download.
+ *
+ * @param observer
+ * An observer to be notified of various events. OnStartRequest is
+ * called when finalURI and totalSize have been determined or when an
+ * error occurs. OnStopRequest is called when the file is completely
+ * downloaded or when an error occurs. If this object implements
+ * nsIProgressEventSink, then its OnProgress method will be called as
+ * data is written to the destination file. If this object implements
+ * nsIInterfaceRequestor, then it will be assigned as the underlying
+ * channel's notification callbacks, which allows it to provide a
+ * nsIAuthPrompt implementation if needed by the channel, for example.
+ * @param ctxt
+ * User defined object forwarded to the observer's methods.
+ */
+ void start(in nsIRequestObserver observer,
+ in nsISupports ctxt);
+};
diff --git a/netwerk/base/nsIIncrementalStreamLoader.idl b/netwerk/base/nsIIncrementalStreamLoader.idl
new file mode 100644
index 0000000000..aef56ed41f
--- /dev/null
+++ b/netwerk/base/nsIIncrementalStreamLoader.idl
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIThreadRetargetableStreamListener.idl"
+
+interface nsIRequest;
+interface nsIIncrementalStreamLoader;
+
+[scriptable, uuid(07c3d2cc-5454-4618-9f4f-cd93de9504a4)]
+interface nsIIncrementalStreamLoaderObserver : nsISupports
+{
+ /**
+ * Called when new data has arrived on the stream.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param dataLength the length of the new data received
+ * @param data the contents of the new data received.
+ *
+ * This method will always be called asynchronously by the
+ * nsIIncrementalStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to not accumulate all or portional of the data in
+ * the internal buffer, the consumedLength shall be set to the value of
+ * the dataLength or less. By default the consumedLength value is assumed 0.
+ * The data and dataLength reflect the non-consumed data and will be
+ * accumulated if consumedLength is not set.
+ *
+ * In comparison with onStreamComplete(), the data buffer cannot be
+ * adopted if this method returns NS_SUCCESS_ADOPTED_DATA.
+ */
+ void onIncrementalData(in nsIIncrementalStreamLoader loader,
+ in nsISupports ctxt,
+ in unsigned long dataLength,
+ [const,array,size_is(dataLength)] in octet data,
+ inout unsigned long consumedLength);
+
+ /**
+ * Called when the entire stream has been loaded.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param status the status of the underlying channel
+ * @param resultLength the length of the data loaded
+ * @param result the data
+ *
+ * This method will always be called asynchronously by the
+ * nsIIncrementalStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to take over responsibility for the
+ * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA
+ * in place of NS_OK as its success code. The loader will then
+ * "forget" about the data and not free() it after
+ * onStreamComplete() returns; observer must call free()
+ * when the data is no longer required.
+ */
+ void onStreamComplete(in nsIIncrementalStreamLoader loader,
+ in nsISupports ctxt,
+ in nsresult status,
+ in unsigned long resultLength,
+ [const,array,size_is(resultLength)] in octet result);
+};
+
+/**
+ * Asynchronously loads a channel into a memory buffer.
+ *
+ * To use this interface, first call init() with a nsIIncrementalStreamLoaderObserver
+ * that will be notified when the data has been loaded. Then call asyncOpen()
+ * on the channel with the nsIIncrementalStreamLoader as the listener. The context
+ * argument in the asyncOpen() call will be passed to the onStreamComplete()
+ * callback.
+ *
+ * XXX define behaviour for sizes >4 GB
+ */
+[scriptable, uuid(a023b060-ba23-431a-b449-2dd63e220554)]
+interface nsIIncrementalStreamLoader : nsIThreadRetargetableStreamListener
+{
+ /**
+ * Initialize this stream loader, and start loading the data.
+ *
+ * @param aObserver
+ * An observer that will be notified when the data is complete.
+ */
+ void init(in nsIIncrementalStreamLoaderObserver aObserver);
+
+ /**
+ * Gets the number of bytes read so far.
+ */
+ readonly attribute unsigned long numBytesRead;
+
+ /**
+ * Gets the request that loaded this file.
+ * null after the request has finished loading.
+ */
+ readonly attribute nsIRequest request;
+};
diff --git a/netwerk/base/nsIInputStreamChannel.idl b/netwerk/base/nsIInputStreamChannel.idl
new file mode 100644
index 0000000000..9172a2b283
--- /dev/null
+++ b/netwerk/base/nsIInputStreamChannel.idl
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIURI;
+
+/**
+ * nsIInputStreamChannel
+ *
+ * This interface provides methods to initialize an input stream channel.
+ * The input stream channel serves as a data pump for an input stream.
+ */
+[scriptable, uuid(ea730238-4bfd-4015-8489-8f264d05b343)]
+interface nsIInputStreamChannel : nsISupports
+{
+ /**
+ * Sets the URI for this channel. This must be called before the
+ * channel is opened, and it may only be called once.
+ */
+ void setURI(in nsIURI aURI);
+
+ /**
+ * Get/set the content stream
+ *
+ * This stream contains the data that will be pushed to the channel's
+ * stream listener. If the stream is non-blocking and supports the
+ * nsIAsyncInputStream interface, then the stream will be read directly.
+ * Otherwise, the stream will be read on a background thread.
+ *
+ * This attribute must be set before the channel is opened, and it may
+ * only be set once.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if the setter is called after the channel
+ * has been opened.
+ */
+ attribute nsIInputStream contentStream;
+
+ /**
+ * Get/set the srcdoc data string. When the input stream channel is
+ * created to load a srcdoc iframe, this is set to hold the value of the
+ * srcdoc attribute.
+ *
+ * This should be the same value used to create contentStream, but this is
+ * not checked.
+ *
+ * Changing the value of this attribute will not otherwise affect the
+ * functionality of the channel or input stream.
+ */
+ attribute AString srcdocData;
+
+ /**
+ * Returns true if srcdocData has been set within the channel.
+ */
+ readonly attribute boolean isSrcdocChannel;
+
+ /**
+ * The base URI to be used for the channel. Used when the base URI cannot
+ * be inferred by other means, for example when this is a srcdoc channel.
+ */
+ attribute nsIURI baseURI;
+};
diff --git a/netwerk/base/nsIInputStreamPump.idl b/netwerk/base/nsIInputStreamPump.idl
new file mode 100644
index 0000000000..2bd1ee1683
--- /dev/null
+++ b/netwerk/base/nsIInputStreamPump.idl
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIInputStream;
+interface nsISerialEventTarget;
+interface nsIStreamListener;
+
+/**
+ * nsIInputStreamPump
+ *
+ * This interface provides a means to configure and use a input stream pump
+ * instance. The input stream pump will asynchronously read from an input
+ * stream, and push data to an nsIStreamListener instance. It utilizes the
+ * current thread's nsIEventTarget in order to make reading from the stream
+ * asynchronous. A different thread can be used if the pump also implements
+ * nsIThreadRetargetableRequest.
+ *
+ * If the given stream supports nsIAsyncInputStream, then the stream pump will
+ * call the stream's AsyncWait method to drive the stream listener. Otherwise,
+ * the stream will be read on a background thread utilizing the stream
+ * transport service. More details are provided below.
+ */
+[scriptable, uuid(400F5468-97E7-4d2b-9C65-A82AECC7AE82)]
+interface nsIInputStreamPump : nsIRequest
+{
+ /**
+ * Initialize the input stream pump.
+ *
+ * @param aStream
+ * contains the data to be read. if the input stream is non-blocking,
+ * then it will be QI'd to nsIAsyncInputStream. if the QI succeeds
+ * then the stream will be read directly. otherwise, it will be read
+ * on a background thread using the stream transport service.
+ * @param aSegmentSize
+ * if the stream transport service is used, then this parameter
+ * specifies the segment size for the stream transport's buffer.
+ * pass 0 to specify the default value.
+ * @param aSegmentCount
+ * if the stream transport service is used, then this parameter
+ * specifies the segment count for the stream transport's buffer.
+ * pass 0 to specify the default value.
+ * @param aCloseWhenDone
+ * if true, the input stream will be closed after it has been read.
+ * @param aMainThreadTarget
+ * a labeled main therad event target.
+ */
+ void init(in nsIInputStream aStream,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount,
+ in boolean aCloseWhenDone,
+ [optional] in nsISerialEventTarget aMainThreadTarget);
+
+ /**
+ * asyncRead causes the input stream to be read in chunks and delivered
+ * asynchronously to the listener via OnDataAvailable.
+ *
+ * @param aListener
+ * receives notifications.
+ * @param aListenerContext
+ * passed to listener methods.
+ */
+ void asyncRead(in nsIStreamListener aListener);
+};
diff --git a/netwerk/base/nsIInterceptionInfo.idl b/netwerk/base/nsIInterceptionInfo.idl
new file mode 100644
index 0000000000..d3c1b030ac
--- /dev/null
+++ b/netwerk/base/nsIInterceptionInfo.idl
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsIPrincipal;
+interface nsIRedirectHistoryEntry;
+
+%{C++
+#include "nsTArray.h"
+%}
+
+[ref] native nsIRedirectHistoryEntryArray(const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>);
+/**
+ * nsIInterceptionInfo is used to record the needed information of the
+ * InterceptedHttpChannel.
+ * This infomration need to be propagated to the new channel which created by
+ * FetchEvent.request or ServiceWorker NavigationPreload.
+ */
+[scriptable, builtinclass, uuid(8b9cd81f-3cd1-4f6a-9086-92a9bbf055f4)]
+interface nsIInterceptionInfo : nsISupports
+{
+ /**
+ * InterceptedHttpChannel's triggering principal
+ */
+ [noscript, notxpcom, nostdcall, binaryname(TriggeringPrincipal)]
+ nsIPrincipal binaryTriggeringPrincipal();
+
+ [noscript, notxpcom, nostdcall, binaryname(SetTriggeringPrincipal)]
+ void binarySetTriggeringPrincipal(in nsIPrincipal aPrincipal);
+
+ /**
+ * InterceptedHttpChannel's content policy type
+ */
+ [noscript, notxpcom, nostdcall, binaryname(ContentPolicyType)]
+ nsContentPolicyType binaryContentPolicyType();
+
+ [noscript, notxpcom, nostdcall, binaryname(ExternalContentPolicyType)]
+ nsContentPolicyType binaryExternalContentPolicyType();
+
+ [noscript, notxpcom, nostdcall, binaryname(SetContentPolicyType)]
+ void binarySetContentPolicyType(in nsContentPolicyType aContentPolicyType);
+
+%{ C++
+ inline ExtContentPolicyType GetExtContentPolicyType()
+ {
+ return static_cast<ExtContentPolicyType>(ExternalContentPolicyType());
+ }
+%}
+
+ /**
+ * The InterceptedHttpChannel's redirect chain
+ */
+ [noscript, notxpcom, nostdcall, binaryname(RedirectChain)]
+ nsIRedirectHistoryEntryArray binaryRedirectChain();
+
+ [noscript, notxpcom, nostdcall, binaryname(SetRedirectChain)]
+ void binarySetRedirectChain(
+ in nsIRedirectHistoryEntryArray aRedirectChain);
+
+ /**
+ * The InterceptedHttpChannel is a third party channel or not.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(FromThirdParty)]
+ bool binaryFromThirdParty();
+
+ [noscript, notxpcom, nostdcall, binaryname(SetFromThirdParty)]
+ void binarySetFromThirdParty(in bool aFromThirdParty);
+};
diff --git a/netwerk/base/nsILoadContextInfo.idl b/netwerk/base/nsILoadContextInfo.idl
new file mode 100644
index 0000000000..bbf2b5d48a
--- /dev/null
+++ b/netwerk/base/nsILoadContextInfo.idl
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+%}
+native OriginAttributesNativePtr(const mozilla::OriginAttributes*);
+
+interface nsILoadContext;
+interface nsIDOMWindow;
+
+/**
+ * Helper interface to carry informatin about the load context
+ * encapsulating origin attributes and IsAnonymous, IsPrivite properties.
+ * It shall be used where nsILoadContext cannot be used or is not
+ * available.
+ */
+
+[scriptable, builtinclass, uuid(555e2f8a-a1f6-41dd-88ca-ed4ed6b98a22)]
+interface nsILoadContextInfo : nsISupports
+{
+ /**
+ * Whether the context is in a Private Browsing mode
+ */
+ readonly attribute boolean isPrivate;
+
+ /**
+ * Whether the load is initiated as anonymous
+ */
+ readonly attribute boolean isAnonymous;
+
+ /**
+ * OriginAttributes hiding all the security context attributes
+ */
+ [implicit_jscontext]
+ readonly attribute jsval originAttributes;
+ [noscript, notxpcom, nostdcall, binaryname(OriginAttributesPtr)]
+ OriginAttributesNativePtr binaryOriginAttributesPtr();
+
+%{C++
+ /**
+ * De-XPCOMed getters
+ */
+ bool IsPrivate()
+ {
+ bool pb;
+ GetIsPrivate(&pb);
+ return pb;
+ }
+
+ bool IsAnonymous()
+ {
+ bool anon;
+ GetIsAnonymous(&anon);
+ return anon;
+ }
+
+ bool Equals(nsILoadContextInfo *aOther)
+ {
+ return IsAnonymous() == aOther->IsAnonymous() &&
+ *OriginAttributesPtr() == *aOther->OriginAttributesPtr();
+ }
+%}
+};
+
+/**
+ * Since OriginAttributes struct limits the implementation of
+ * nsILoadContextInfo (that needs to be thread safe) to C++,
+ * we need a scriptable factory to create instances of that
+ * interface from JS.
+ */
+[scriptable, uuid(c1c7023d-4318-4f99-8307-b5ccf0558793)]
+interface nsILoadContextInfoFactory : nsISupports
+{
+ readonly attribute nsILoadContextInfo default;
+ readonly attribute nsILoadContextInfo private;
+ readonly attribute nsILoadContextInfo anonymous;
+ [implicit_jscontext]
+ nsILoadContextInfo custom(in boolean aAnonymous, in jsval aOriginAttributes);
+ nsILoadContextInfo fromLoadContext(in nsILoadContext aLoadContext, in boolean aAnonymous);
+ nsILoadContextInfo fromWindow(in nsIDOMWindow aWindow, in boolean aAnonymous);
+};
diff --git a/netwerk/base/nsILoadGroup.idl b/netwerk/base/nsILoadGroup.idl
new file mode 100644
index 0000000000..06e51e442b
--- /dev/null
+++ b/netwerk/base/nsILoadGroup.idl
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsISimpleEnumerator;
+interface nsIRequestObserver;
+interface nsIInterfaceRequestor;
+interface nsIRequestContext;
+
+/**
+ * A load group maintains a collection of nsIRequest objects.
+ * This is used in lots of places where groups of requests need to be tracked.
+ * For example, Document::mDocumentLoadGroup is used to track all requests
+ * made for subdocuments in order to track page load progress and allow all
+ * requests made on behalf of the document to be stopped, etc.
+ */
+[builtinclass, scriptable, uuid(f0c87725-7a35-463c-9ceb-2c07f23406cc)]
+interface nsILoadGroup : nsIRequest
+{
+ /**
+ * The group observer is notified when requests are added to and removed
+ * from this load group. The groupObserver is weak referenced.
+ */
+ attribute nsIRequestObserver groupObserver;
+
+ /**
+ * Accesses the default load request for the group. Each time a number
+ * of requests are added to a group, the defaultLoadRequest may be set
+ * to indicate that all of the requests are related to a base request.
+ *
+ * The load group inherits its load flags from the default load request.
+ * If the default load request is NULL, then the group's load flags are
+ * not changed.
+ */
+ attribute nsIRequest defaultLoadRequest;
+
+ /**
+ * Adds a new request to the group. This will cause the default load
+ * flags to be applied to the request. If this is a foreground
+ * request then the groupObserver's onStartRequest will be called.
+ *
+ * If the request is the default load request or if the default load
+ * request is null, then the load group will inherit its load flags from
+ * the request.
+ */
+ void addRequest(in nsIRequest aRequest,
+ in nsISupports aContext);
+
+ /**
+ * Removes a request from the group. If this is a foreground request
+ * then the groupObserver's onStopRequest will be called.
+ *
+ * By the time this call ends, aRequest will have been removed from the
+ * loadgroup, even if this function throws an exception.
+ */
+ void removeRequest(in nsIRequest aRequest,
+ in nsISupports aContext,
+ in nsresult aStatus);
+
+ /**
+ * Returns the requests contained directly in this group.
+ * Enumerator element type: nsIRequest.
+ */
+ readonly attribute nsISimpleEnumerator requests;
+
+ /**
+ * Returns the count of "active" requests (ie. requests without the
+ * LOAD_BACKGROUND bit set).
+ */
+ readonly attribute unsigned long activeCount;
+
+ /**
+ * Notification callbacks for the load group.
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Context for managing things like js/css connection blocking,
+ * and per-tab connection grouping.
+ */
+ readonly attribute unsigned long long requestContextID;
+
+ /**
+ * The set of load flags that will be added to all new requests added to
+ * this group. Any existing requests in the load group are not modified,
+ * so it is expected these flags will be added before requests are added
+ * to the group - typically via nsIDocShell::defaultLoadFlags on a new
+ * docShell.
+ * Note that these flags are *not* added to the default request for the
+ * load group; it is expected the default request will already have these
+ * flags (again, courtesy of setting nsIDocShell::defaultLoadFlags before
+ * the docShell has created the default request.)
+ */
+ attribute nsLoadFlags defaultLoadFlags;
+
+ /**
+ * Returns true if the loadGroup belongs to a discarded context, such as, a
+ * terminated private browsing session.
+ */
+ [infallible]
+ readonly attribute boolean isBrowsingContextDiscarded;
+};
diff --git a/netwerk/base/nsILoadGroupChild.idl b/netwerk/base/nsILoadGroupChild.idl
new file mode 100644
index 0000000000..959913a474
--- /dev/null
+++ b/netwerk/base/nsILoadGroupChild.idl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsILoadGroup;
+
+/**
+ * nsILoadGroupChild provides a hierarchy of load groups so that the
+ * root load group can be used to conceptually tie a series of loading
+ * operations into a logical whole while still leaving them separate
+ * for the purposes of cancellation and status events.
+ */
+
+[builtinclass, scriptable, uuid(02efe8e2-fbbc-4718-a299-b8a09c60bf6b)]
+interface nsILoadGroupChild : nsISupports
+{
+ /**
+ * The parent of this load group. It is stored with
+ * a nsIWeakReference/nsWeakPtr so there is no requirement for the
+ * parentLoadGroup to out live the child, nor will the child keep a
+ * reference count on the parent.
+ */
+ attribute nsILoadGroup parentLoadGroup;
+
+ /**
+ * The nsILoadGroup associated with this nsILoadGroupChild
+ */
+ readonly attribute nsILoadGroup childLoadGroup;
+
+ /**
+ * The rootLoadGroup is the recursive parent of this
+ * load group where parent is defined as parentlLoadGroup if set
+ * or childLoadGroup.loadGroup as a backup. (i.e. parentLoadGroup takes
+ * precedence.) The nsILoadGroup child is the root if neither parent
+ * nor loadgroup attribute is specified.
+ */
+ readonly attribute nsILoadGroup rootLoadGroup;
+};
diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl
new file mode 100644
index 0000000000..ddfcb223e6
--- /dev/null
+++ b/netwerk/base/nsILoadInfo.idl
@@ -0,0 +1,1535 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+#include "nsIScriptSecurityManager.idl"
+#include "nsIInterceptionInfo.idl"
+
+interface nsIChannel;
+interface nsIContentSecurityPolicy;
+interface nsICookieJarSettings;
+interface nsICSPEventListener;
+interface nsINode;
+interface nsIPrincipal;
+interface nsIRedirectHistoryEntry;
+interface nsIURI;
+webidl Document;
+webidl BrowsingContext;
+native LoadContextRef(already_AddRefed<nsISupports>);
+%{C++
+#include "nsTArray.h"
+#include "mozilla/LoadTainting.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsRFPService.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+namespace dom {
+class ClientInfo;
+class ClientSource;
+class PerformanceStorage;
+class ServiceWorkerDescriptor;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ref] native nsIRedirectHistoryEntryArray(const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>);
+native OriginAttributes(mozilla::OriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes);
+[ref] native CStringArrayRef(const nsTArray<nsCString>);
+[ref] native StringArrayRef(const nsTArray<nsString>);
+[ref] native Uint64ArrayRef(const nsTArray<uint64_t>);
+[ref] native PrincipalArrayRef(const nsTArray<nsCOMPtr<nsIPrincipal>>);
+[ref] native const_ClientInfoRef(const mozilla::dom::ClientInfo);
+ native UniqueClientSource(mozilla::UniquePtr<mozilla::dom::ClientSource>);
+ native UniqueClientSourceMove(mozilla::UniquePtr<mozilla::dom::ClientSource>&&);
+[ref] native const_MaybeClientInfoRef(const mozilla::Maybe<mozilla::dom::ClientInfo>);
+[ref] native const_ServiceWorkerDescriptorRef(const mozilla::dom::ServiceWorkerDescriptor);
+[ref] native const_MaybeServiceWorkerDescriptorRef(const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>);
+[ref] native const_MaybeRFPTarget(const mozilla::Maybe<mozilla::RFPTarget>);
+ native RFPTarget(mozilla::RFPTarget);
+[ptr] native PerformanceStoragePtr(mozilla::dom::PerformanceStorage);
+ native LoadTainting(mozilla::LoadTainting);
+ native CSPRef(already_AddRefed<nsIContentSecurityPolicy>);
+
+typedef unsigned long nsSecurityFlags;
+
+/**
+ * The LoadInfo object contains information about a network load, why it
+ * was started, and how we plan on using the resulting response.
+ * If a network request is redirected, the new channel will receive a new
+ * LoadInfo object. The new object will contain mostly the same
+ * information as the pre-redirect one, but updated as appropriate.
+ * For detailed information about what parts of LoadInfo are updated on
+ * redirect, see documentation on individual properties.
+ */
+[scriptable, builtinclass, uuid(ddc65bf9-2f60-41ab-b22a-4f1ae9efcd36)]
+interface nsILoadInfo : nsISupports
+{
+ /**
+ * The following five flags determine the security mode and hence what kind of
+ * security checks should be performed throughout the lifetime of the channel.
+ *
+ * * SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT
+ * * SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+ * * SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT
+ * * SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
+ * * SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT
+ *
+ * Exactly one of these flags are required to be set in order to allow
+ * the channel to perform the correct security checks (SOP, CORS, ...) and
+ * return the correct result principal. If none or more than one of these
+ * flags are set AsyncOpen will fail.
+ */
+
+ /**
+ * Warning: Never use this flag when creating a new channel!
+ * Only use this flag if you have to create a temporary LoadInfo
+ * for performing an explicit nsIContentPolicy check, like e.g.
+ * when loading something from the cache that needs an explicit
+ * nsIContentPolicy check. In all other cases pick one of the
+ * security flags underneath.
+ */
+ const unsigned long SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK = 0;
+
+ /*
+ * Enforce the same origin policy where loads inherit the principal.
+ * See the documentation for principalToInherit, which describes exactly what
+ * principal is inherited.
+ */
+ const unsigned long SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT = (1<<0);
+
+ /*
+ * Enforce the same origin policy and data: loads are blocked.
+ */
+ const unsigned long SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED = (1<<1);
+
+ /**
+ * Allow loads from other origins. Loads which inherit the principal should
+ * see the documentation for principalToInherit, which describes exactly what
+ * principal is inherited.
+ *
+ * Commonly used by plain <img>, <video>, <link rel=stylesheet> etc.
+ */
+ const unsigned long SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT = (1 << 2);
+
+ /**
+ * Allow loads from other origins. Loads from data: will be allowed,
+ * but the resulting resource will get a null principal.
+ * Used in blink/webkit for <iframe>s. Likely also the mode
+ * that should be used by most Chrome code.
+ */
+ const unsigned long SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL = (1<<3);
+
+ /**
+ * Allow loads from any origin, but require CORS for cross-origin loads.
+ * See the documentation for principalToInherit, which describes exactly what
+ * principal is inherited.
+ *
+ * Commonly used by <img crossorigin>, <video crossorigin>,
+ * XHR, fetch(), etc.
+ */
+ const unsigned long SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT = (1<<4);
+
+ /**
+ * Choose cookie policy. The default policy is equivalent to "INCLUDE" for
+ * SEC_REQUIRE_SAME_ORIGIN_* and SEC_ALLOW_CROSS_ORIGIN_* modes, and
+ * equivalent to "SAME_ORIGIN" for SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT mode.
+ *
+ * This means that if you want to perform a CORS load with credentials, pass
+ * SEC_COOKIES_INCLUDE.
+ *
+ * Note that these flags are still subject to the user's cookie policies.
+ * For example, if the user is blocking 3rd party cookies, those cookies
+ * will be blocked no matter which of these flags are set.
+ */
+ const unsigned long SEC_COOKIES_DEFAULT = (0 << 5);
+ const unsigned long SEC_COOKIES_INCLUDE = (1 << 5);
+ const unsigned long SEC_COOKIES_SAME_ORIGIN = (2 << 5);
+ const unsigned long SEC_COOKIES_OMIT = (3 << 5);
+
+ /**
+ * Force inheriting of the principal. See the documentation for
+ * principalToInherit, which describes exactly what principal is inherited.
+ *
+ * Setting this flag will cause GetChannelResultPrincipal to return the
+ * principal to be inherited as the channel principal.
+ *
+ * This will happen independently of the scheme of the URI that the
+ * channel is loading.
+ *
+ * So if the principal that gets inherited is "http://a.com/", and the channel
+ * is loading the URI "http://b.com/whatever", GetChannelResultPrincipal
+ * will return a principal from "http://a.com/".
+ *
+ * This flag can not be used together with SANDBOXED_ORIGIN sandbox flag. If
+ * both are passed to the LoadInfo constructor then this flag will be dropped.
+ * If you need to know whether this flag would have been present but was dropped
+ * due to sandboxing, check for the forceInheritPrincipalDropped flag.
+ */
+ const unsigned long SEC_FORCE_INHERIT_PRINCIPAL = (1<<7);
+
+ /**
+ * Inherit the Principal for about:blank.
+ */
+ const unsigned long SEC_ABOUT_BLANK_INHERITS = (1<<9);
+
+ /**
+ * Allow access to chrome: packages that are content accessible.
+ */
+ const unsigned long SEC_ALLOW_CHROME = (1<<10);
+
+ /**
+ * Disallow access to javascript: uris.
+ */
+ const unsigned long SEC_DISALLOW_SCRIPT = (1<<11);
+
+ /**
+ * Don't follow redirects. Instead the redirect response is returned
+ * as a successful response for the channel.
+ *
+ * Redirects not initiated by a server response, i.e. REDIRECT_INTERNAL and
+ * REDIRECT_STS_UPGRADE, are still followed.
+ *
+ * Note: If this flag is set and the channel response is a redirect, then
+ * the response body might not be available.
+ * This can happen if the redirect was cached.
+ */
+ const unsigned long SEC_DONT_FOLLOW_REDIRECTS = (1<<12);
+
+ /**
+ * Load an error page, it should be one of following : about:neterror,
+ * about:certerror, about:blocked, about:tabcrashed or about:restartrequired.
+ */
+ const unsigned long SEC_LOAD_ERROR_PAGE = (1<<13);
+
+ /**
+ * Force inheriting of the principal, overruling any owner that might be set
+ * on the channel. (Please note that channel.owner is deprecated and will be
+ * removed within Bug 1286838). See the documentation for principalToInherit,
+ * which describes exactly what principal is inherited.
+ *
+ * Setting this flag will cause GetChannelResultPrincipal to return the
+ * principal to be inherited as the channel principal.
+ *
+ * This will happen independently of the scheme of the URI that the
+ * channel is loading.
+ */
+ const unsigned long SEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNER = (1<<14);
+
+ /**
+ * This is the principal of the network request's caller/requester where
+ * the resulting resource will be used. I.e. it is the principal which
+ * will get access to the result of the request. (Where "get access to"
+ * might simply mean "embed" depending on the type of resource that is
+ * loaded).
+ *
+ * For example for an image, it is the principal of the document where
+ * the image is rendered. For a stylesheet it is the principal of the
+ * document where the stylesheet will be applied.
+ *
+ * So if document at http://a.com/page.html loads an image from
+ * http://b.com/pic.jpg, then loadingPrincipal will be
+ * http://a.com/page.html.
+ *
+ * For <iframe> and <frame> loads, the LoadingPrincipal is the
+ * principal of the parent document. For top-level loads, the
+ * LoadingPrincipal is null. For all loads except top-level loads
+ * the LoadingPrincipal is never null.
+ *
+ * If the loadingPrincipal is the system principal, no security checks
+ * will be done at all. There will be no security checks on the initial
+ * load or any subsequent redirects. This means there will be no
+ * nsIContentPolicy checks or any CheckLoadURI checks. Because of
+ * this, never set the loadingPrincipal to the system principal when
+ * the URI to be loaded is controlled by a webpage.
+ * If the loadingPrincipal and triggeringPrincipal are both
+ * content principals, then we will always call into
+ * nsIContentPolicies and CheckLoadURI. The call to nsIContentPolicies
+ * and CheckLoadURI happen even if the URI to be loaded is same-origin
+ * with the loadingPrincipal or triggeringPrincipal.
+ */
+ readonly attribute nsIPrincipal loadingPrincipal;
+
+ /**
+ * A C++-friendly version of triggeringPrincipal.
+ *
+ * This is a bit awkward because we can't use
+ * binaryname(GetLoadingPrincipal).
+ */
+ [noscript, notxpcom, nostdcall]
+ nsIPrincipal virtualGetLoadingPrincipal();
+
+%{C++
+ nsIPrincipal* GetLoadingPrincipal() {
+ return VirtualGetLoadingPrincipal();
+ }
+%}
+
+ /**
+ * This is the principal which caused the network load to start. I.e.
+ * this is the principal which provided the URL to be loaded. This is
+ * often the same as the LoadingPrincipal, but there are a few cases
+ * where that's not true.
+ *
+ * For example for loads into an <iframe>, the LoadingPrincipal is always
+ * the principal of the parent document. However the triggeringPrincipal
+ * is the principal of the document which provided the URL that the
+ * <iframe> is navigating to. This could be the previous document inside
+ * the <iframe> which set document.location. Or a document elsewhere in
+ * the frame tree which contained a <a target="..."> which targetted the
+ * <iframe>.
+ *
+ * If a stylesheet links to a sub-resource, like an @imported stylesheet,
+ * or a background image, then the triggeringPrincipal is the principal
+ * of the stylesheet, while the LoadingPrincipal is the principal of the
+ * document being styled.
+ *
+ * The triggeringPrincipal is never null.
+ *
+ * If the triggeringPrincipal is the system principal, no security checks
+ * will be done at all. There will be no security checks on the initial
+ * load or any subsequent redirects. This means there will be no
+ * nsIContentPolicy checks or any CheckLoadURI checks. Because of
+ * this, never set the triggeringPrincipal to the system principal when
+ * the URI to be loaded is controlled by a webpage.
+ * If the loadingPrincipal and triggeringPrincipal are both
+ * content principals, then we will always call into
+ * nsIContentPolicies and CheckLoadURI. The call to nsIContentPolicies
+ * and CheckLoadURI happen even if the URI to be loaded is same-origin
+ * with the loadingPrincipal or triggeringPrincipal.
+ */
+ readonly attribute nsIPrincipal triggeringPrincipal;
+
+ /**
+ * A C++-friendly version of triggeringPrincipal.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(TriggeringPrincipal)]
+ nsIPrincipal binaryTriggeringPrincipal();
+
+ /**
+ * The remote type of the process which caused the network load to start. I.e.
+ * this is the remote type of the process which provided the URL to be loaded.
+ *
+ * For subresource loads, this should be the same as the process which will
+ * handle the response, however for document loads this may both be different
+ * than the final process, as well as different from the process which starts
+ * the navigation.
+ *
+ * This field is intentionally not perfectly preserved over IPC, and will be
+ * reset to the remote type of the sending process when sent from a content
+ * process to the parent process.
+ */
+ attribute AUTF8String triggeringRemoteType;
+
+ /**
+ * For non-document loads the principalToInherit is always null. For
+ * loads of type TYPE_DOCUMENT or TYPE_SUBDOCUMENT the principalToInherit
+ * might be null. If it's non null, then this is the principal that is
+ * inherited if a principal needs to be inherited. If the principalToInherit
+ * is null but the inherit flag is set, then the triggeringPrincipal is
+ * the principal that is inherited.
+ */
+ attribute nsIPrincipal principalToInherit;
+
+ /**
+ * A C++-friendly version of principalToInherit.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(PrincipalToInherit)]
+ nsIPrincipal binaryPrincipalToInherit();
+
+ /**
+ * Finds the correct principal to inherit for the given channel, based on
+ * the values of PrincipalToInherit and TriggeringPrincipal.
+ */
+ [noscript, notxpcom, nostdcall]
+ nsIPrincipal FindPrincipalToInherit(in nsIChannel aChannel);
+
+ /**
+ * This is the ownerDocument of the LoadingNode. Unless the LoadingNode
+ * is a Document, in which case the LoadingDocument is the same as the
+ * LoadingNode.
+ *
+ * For top-level loads, and for loads originating from workers, the
+ * LoadingDocument is null. When the LoadingDocument is not null, the
+ * LoadingPrincipal is set to the principal of the LoadingDocument.
+ */
+ readonly attribute Document loadingDocument;
+
+ /**
+ * A C++-friendly version of loadingDocument (loadingNode).
+ * This is the Node where the resulting resource will be used. I.e. it is
+ * the Node which will get access to the result of the request. (Where
+ * "get access to" might simply mean "embed" depending on the type of
+ * resource that is loaded).
+ *
+ * For example for an <img>/<video> it is the image/video element. For
+ * document loads inside <iframe> and <frame>s, the LoadingNode is the
+ * <iframe>/<frame> element. For an XMLHttpRequest, it is the Document
+ * which contained the JS which initiated the XHR. For a stylesheet, it
+ * is the Document that contains <link rel=stylesheet>.
+ *
+ * For loads triggered by the HTML pre-parser, the LoadingNode is the
+ * Document which is currently being parsed.
+ *
+ * For top-level loads, and for loads originating from workers, the
+ * LoadingNode is null. If the LoadingNode is non-null, then the
+ * LoadingPrincipal is the principal of the LoadingNode.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(LoadingNode)]
+ nsINode binaryLoadingNode();
+
+ /**
+ * A C++ friendly version of the loadingContext for toplevel loads.
+ * Most likely you want to query the ownerDocument or LoadingNode
+ * and not this context only available for TYPE_DOCUMENT loads.
+ * Please note that except for loads of TYPE_DOCUMENT, this
+ * ContextForTopLevelLoad will always return null.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(ContextForTopLevelLoad)]
+ LoadContextRef binaryContextForTopLevelLoad();
+
+ /**
+ * For all loads except loads of TYPE_DOCUMENT, the loadingContext
+ * simply returns the loadingNode. For loads of TYPE_DOCUMENT this
+ * will return the context available for top-level loads which
+ * do not have a loadingNode.
+ */
+ [binaryname(LoadingContextXPCOM)]
+ readonly attribute nsISupports loadingContext;
+
+ /**
+ * A C++ friendly version of the loadingContext.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(GetLoadingContext)]
+ LoadContextRef binaryGetLoadingContext();
+
+ /**
+ * The securityFlags of that channel.
+ */
+ readonly attribute nsSecurityFlags securityFlags;
+
+%{C++
+ inline nsSecurityFlags GetSecurityFlags()
+ {
+ nsSecurityFlags result;
+ mozilla::DebugOnly<nsresult> rv = GetSecurityFlags(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+%}
+
+ /**
+ * The sandboxFlags of that channel.
+ */
+ [infallible] readonly attribute unsigned long sandboxFlags;
+
+ /**
+ * The TriggingSandboxFlags are the SandboxFlags of the entity
+ * responsible for causing the load to occur.
+ */
+ [infallible] attribute unsigned long triggeringSandboxFlags;
+
+
+ /**
+ * The window id and storage access status of the window of the
+ * context that triggered the load. This is used to allow self-initiated
+ * same-origin navigations to propogate their "has storage access" bit
+ * to the next Document.
+ */
+ [infallible] attribute unsigned long long triggeringWindowId;
+ [infallible] attribute boolean triggeringStorageAccess;
+
+ /**
+ * Allows to query only the security mode bits from above.
+ */
+ [infallible] readonly attribute unsigned long securityMode;
+
+ /**
+ * This flag is used for any browsing context where we should not sniff
+ * the content type. E.g if an iframe has the XCTO nosniff header, then
+ * that flag is set to true so we skip content sniffing for that browsing
+ * context.
+ */
+ [infallible] attribute boolean skipContentSniffing;
+
+ /**
+ * (default) If this flag is set, it has not yet been determined if the
+ * HTTPS-Only mode will upgrade the request.
+ */
+ const unsigned long HTTPS_ONLY_UNINITIALIZED = (1 << 0);
+
+ /**
+ * Indicates that this is the first time the request gets upgraded, and thus
+ * the HTTPS-Only StreamListener hasn't been registered yet. Even though there
+ * might be multiple channels per request that have to be upgraded (e.g.,
+ * because of redirects), the StreamListener only has to be attached to one
+ * channel.
+ */
+ const unsigned long HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED = (1 << 1);
+
+ /**
+ * Indicates that the request will get upgraded, and the HTTPS-Only
+ * StreamListener got registered.
+ */
+ const unsigned long HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED = (1 << 2);
+
+ /**
+ * This flag can be manually set if the HTTPS-Only mode should exempt the
+ * request and not upgrade it. (e.g in the case of OCSP.
+ */
+ const unsigned long HTTPS_ONLY_EXEMPT = (1 << 3);
+
+ /**
+ * This flag can only ever be set on top-level loads. It indicates
+ * that the top-level https connection succeeded. This flag is mostly
+ * used to counter time-outs which allows to cancel the channel
+ * if the https load has not started.
+ */
+ const unsigned long HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS = (1 << 4);
+
+ /**
+ * This flag can only ever be set on downloads. It indicates
+ * that the download https connection succeeded. This flag is mostly
+ * used to counter time-outs which allows to cancel the channel
+ * if the https load has not started.
+ */
+ const unsigned long HTTPS_ONLY_DOWNLOAD_IN_PROGRESS = (1 << 5);
+
+ /**
+ * This flag indicates that the request should not be logged to the
+ * console.
+ */
+ const unsigned long HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE = (1 << 6);
+
+ /**
+ * This flag indicates that the request was upgraded by https-first mode.
+ */
+ const unsigned long HTTPS_ONLY_UPGRADED_HTTPS_FIRST = (1 << 7);
+
+ /**
+ * This flag indicates that the request should not be blocked by ORB.
+ */
+ const unsigned long HTTPS_ONLY_BYPASS_ORB = (1 << 8);
+
+ /**
+ * This flag indicates that HTTPS_ONLY_EXEMPT should be
+ * set the next time HTTPS-Only exemptions are checked
+ * and HTTPS-First is enabled.
+ */
+ const unsigned long HTTPS_FIRST_EXEMPT_NEXT_LOAD = (1 << 9);
+
+ /**
+ * Upgrade state of HTTPS-Only Mode. The flag HTTPS_ONLY_EXEMPT can get
+ * set on requests that should be excempt from an upgrade.
+ */
+ [infallible] attribute unsigned long httpsOnlyStatus;
+
+ /**
+ * Reflects whetehr this is an HTTP Strict Transport Security host
+ */
+[infallible] attribute boolean hstsStatus;
+
+ /**
+ * Returns true if at the time of the loadinfo construction the document
+ * that triggered this load has the bit hasValidTransientUserGestureActivation
+ * set or the load was triggered from External. (Mostly this bool is used
+ * in the context of Sec-Fetch-User.)
+ */
+ [infallible] attribute boolean hasValidUserGestureActivation;
+
+ /**
+ * We disallow the SystemPrincipal to initiate requests to
+ * the public web. This flag is to allow exceptions.
+ */
+ [infallible] attribute boolean allowDeprecatedSystemRequests;
+
+ /**
+ * Only ever returns true if the loadinfo is of TYPE_SCRIPT and
+ * the script was created by the HTML parser.
+ */
+ [infallible] attribute boolean parserCreatedScript;
+
+ /**
+ * True if this request is known to have been triggered by a user
+ * manually requesting the URI to be saved.
+ */
+ [infallible] attribute boolean isUserTriggeredSave;
+
+ /**
+ * True if this request is from DevTools.
+ */
+ [infallible] attribute boolean isInDevToolsContext;
+
+ /**
+ * True if this request is embedded in a context that can't be third-party
+ * (i.e. an iframe embedded in a cross-origin parent window). If this is
+ * false, then this request may be third-party if it's a third-party to
+ * loadingPrincipal.
+ */
+ [infallible] attribute boolean isInThirdPartyContext;
+
+ /**
+ * True if this request is a third party in respect to the top-level window.
+ *
+ * Note that this doesn't consider the parent window. I.e. It will still
+ * return false even in the case that the parent is cross-origin but the
+ * top-level is same-origin.
+ *
+ * This value would be set during opening the channel in parent and propagate
+ * to the channel in the content.
+ */
+ [infallible] attribute boolean isThirdPartyContextToTopWindow;
+
+ /**
+ * See the SEC_COOKIES_* flags above. This attribute will never return
+ * SEC_COOKIES_DEFAULT, but will instead return what the policy resolves to.
+ * I.e. SEC_COOKIES_SAME_ORIGIN for CORS mode, and SEC_COOKIES_INCLUDE
+ * otherwise.
+ */
+ [infallible] readonly attribute unsigned long cookiePolicy;
+
+ /**
+ * The cookie jar settings inherited from the top-level document's loadInfo.
+ * It cannot be null.
+ */
+ attribute nsICookieJarSettings cookieJarSettings;
+
+ cenum StoragePermissionState : 8 {
+ NoStoragePermission = 0,
+ HasStoragePermission = 1,
+ StoragePermissionAllowListed = 2,
+ };
+
+ /**
+ * The result of the storage permission check of the loading document. This
+ * value would be set during opening the channel.
+ */
+ [infallible] attribute nsILoadInfo_StoragePermissionState
+ storagePermission;
+
+ /**
+ * Get the granular overrides of fingerprinting protections associated to the
+ * channel, the value will override the default fingerprinting protection
+ * settings. This field will only get populated if these is one that comes
+ * from the local granular overrides pref or WebCompat. Otherwise, a value of
+ * Nothing() indicates no granular overrides are present for this channel.
+ *
+ * The RFPTarget defined in the RFPTargets.inc.
+ */
+ [noscript, nostdcall, notxpcom]
+ const_MaybeRFPTarget GetOverriddenFingerprintingSettings();
+
+ /**
+ * Set the granular overrides of fingerprinting protections for the channel.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetOverriddenFingerprintingSettings(in RFPTarget aTargets);
+
+ /**
+ * True if the load was triggered by a meta refresh.
+ */
+ [infallible] attribute boolean isMetaRefresh;
+
+ /**
+ * If forceInheritPrincipal is true, the data coming from the channel should
+ * inherit its principal, even when the data is loaded over http:// or another
+ * protocol that would normally use a URI-based principal.
+ *
+ * See the documentation for principalToInherit, which describes exactly what
+ * principal is inherited.
+ *
+ * This attribute will never be true when loadingSandboxed is true.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipal;
+
+ /**
+ * If forceInheritPrincipalOverruleOwner is true, the data coming from the
+ * channel should inherit the principal, even when the data is loaded over
+ * http:// or another protocol that would normally use a URI-based principal
+ * and even if the channel's .owner is not null. This last is the difference
+ * between forceInheritPrincipalOverruleOwner and forceInheritPrincipal: the
+ * latter does _not_ overrule the .owner setting.
+ *
+ * See the documentation for principalToInherit, which describes exactly what
+ * principal is inherited.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipalOverruleOwner;
+
+ /**
+ * If loadingSandboxed is true, the data coming from the channel is
+ * being loaded sandboxed, so it should have a nonce origin and
+ * hence should use a NullPrincipal.
+ */
+ [infallible] readonly attribute boolean loadingSandboxed;
+
+ /**
+ * If aboutBlankInherits is true, then about:blank should inherit
+ * the principal.
+ */
+ [infallible] readonly attribute boolean aboutBlankInherits;
+
+ /**
+ * If allowChrome is true, then use nsIScriptSecurityManager::ALLOW_CHROME
+ * when calling CheckLoadURIWithPrincipal().
+ */
+ [infallible] readonly attribute boolean allowChrome;
+
+ /**
+ * If disallowScript is true, then use nsIScriptSecurityManager::DISALLOW_SCRIPT
+ * when calling CheckLoadURIWithPrincipal().
+ */
+ [infallible] readonly attribute boolean disallowScript;
+
+%{C++
+ uint32_t CheckLoadURIFlags() {
+ uint32_t flags = nsIScriptSecurityManager::STANDARD;
+ if (GetAllowChrome()) {
+ flags |= nsIScriptSecurityManager::ALLOW_CHROME;
+ }
+ if (GetDisallowScript()) {
+ flags |= nsIScriptSecurityManager::DISALLOW_SCRIPT;
+ }
+ return flags;
+ }
+%}
+
+ /**
+ * Returns true if SEC_DONT_FOLLOW_REDIRECTS is set.
+ */
+ [infallible] readonly attribute boolean dontFollowRedirects;
+
+ /**
+ * Returns true if SEC_LOAD_ERROR_PAGE is set.
+ */
+ [infallible] readonly attribute boolean loadErrorPage;
+
+ /**
+ * True if the load was initiated by a form request.
+ */
+ [infallible] attribute boolean isFormSubmission;
+
+ /**
+ * The external contentPolicyType of the channel, used for security checks
+ * like Mixed Content Blocking and Content Security Policy.
+ *
+ * Specifically, content policy types with _INTERNAL_ in their name will
+ * never get returned from this attribute.
+ */
+ readonly attribute nsContentPolicyType externalContentPolicyType;
+
+ /**
+ * CSP uses this parameter to send or not CSP violation events.
+ * Default value: true.
+ */
+ [infallible] attribute boolean sendCSPViolationEvents;
+
+%{ C++
+ inline ExtContentPolicyType GetExternalContentPolicyType()
+ {
+ nsContentPolicyType result;
+ mozilla::DebugOnly<nsresult> rv = GetExternalContentPolicyType(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return static_cast<ExtContentPolicyType>(result);
+ }
+
+%}
+
+
+ /**
+ * The internal contentPolicyType of the channel, used for constructing
+ * RequestContext values when creating a fetch event for an intercepted
+ * channel.
+ *
+ * This should not be used for the purposes of security checks, since
+ * the content policy implementations cannot be expected to deal with
+ * _INTERNAL_ values. Please use the contentPolicyType attribute above
+ * for that purpose.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(InternalContentPolicyType)]
+ nsContentPolicyType binaryInternalContentPolicyType();
+
+ readonly attribute nsContentPolicyType internalContentPolicyType;
+
+ /**
+ * Returns true if document or any of the documents ancestors
+ * up to the toplevel document make use of the CSP directive
+ * 'block-all-mixed-content'.
+ *
+ * Warning: If the loadingDocument is null, then the
+ * blockAllMixedContent is false.
+ */
+ [infallible] readonly attribute boolean blockAllMixedContent;
+
+ /**
+ * Returns true if document or any of the documents ancestors
+ * up to the toplevel document make use of the CSP directive
+ * 'upgrade-insecure-requests'.
+ *
+ * Warning: If the loadingDocument is null, then the
+ * upgradeInsecureRequests is false.
+ */
+ [infallible] readonly attribute boolean upgradeInsecureRequests;
+
+ /**
+ * Returns true if the the page is https and the content is upgradable from http
+ * requires 'security.mixed_content.upgrade_display_content' pref to be true.
+ * Currently this only upgrades display content but might be expanded to other loads.
+ * This is very similar in implementation to upgradeInsecureRequests but browser set.
+ */
+ [infallible] readonly attribute boolean browserUpgradeInsecureRequests;
+
+ /**
+ * Returns true if the display content was or will get upgraded from http to https.
+ * Requires 'security.mixed_content.upgrade_display_content' pref to be true.
+ * Flag is set purely to collect telemetry.
+ */
+ [infallible] attribute boolean browserDidUpgradeInsecureRequests;
+
+ /**
+ * Returns true if the the page is https and the content is upgradable from http
+ * requires 'security.mixed_content.upgrade_display_content' pref to be false.
+ * See browserUpgradeInsecureRequests for more details, this only happens
+ * when *not* upgrading purely for telemetry.
+ */
+ [infallible] readonly attribute boolean browserWouldUpgradeInsecureRequests;
+
+ /**
+ * If true, toplevel data: URI navigation is allowed
+ */
+ [infallible] attribute boolean forceAllowDataURI;
+
+ /**
+ * If true, insecure redirects to a data: URI are allowed.
+ */
+ [infallible] attribute boolean allowInsecureRedirectToDataURI;
+
+ /**
+ * If true, the content policy security check is excluded from web requests.
+ */
+ [infallible] attribute boolean skipContentPolicyCheckForWebRequest;
+
+ /**
+ * If true, this is the load of a frame's original src attribute
+ */
+ [infallible] attribute boolean originalFrameSrcLoad;
+
+ /**
+ * The SEC_FORCE_INHERIT_PRINCIPAL flag may be dropped when a load info
+ * object is created. Specifically, it will be dropped if the SANDBOXED_ORIGIN
+ * sandbox flag is also present. This flag is set if SEC_FORCE_INHERIT_PRINCIPAL
+ * was dropped.
+ */
+ [infallible] readonly attribute boolean forceInheritPrincipalDropped;
+
+ /**
+ * This is the inner window ID of the window in which the element being
+ * loaded lives.
+ *
+ * Note that this window ID can be 0 if the window is not
+ * available.
+ */
+ [infallible] readonly attribute unsigned long long innerWindowID;
+
+ /**
+ * The BrowsingContext performing the load for this nsILoadInfo object.
+ */
+ [infallible] readonly attribute unsigned long long browsingContextID;
+ [infallible] readonly attribute BrowsingContext browsingContext;
+
+ /**
+ * The BrowsingContext which the worker is associated.
+ *
+ * Note that this could be 0 if the load is not triggered in a WorkerScope.
+ * This value is only set and used in the parent process for some sitautions
+ * the channel is created in the parent process for Workers. Such as fetch().
+ * In content process, it is always 0.
+ * This value would not be propagated through IPC.
+ */
+ [infallible] attribute unsigned long long workerAssociatedBrowsingContextID;
+ [infallible] readonly attribute BrowsingContext workerAssociatedBrowsingContext;
+
+ /**
+ * Only when the element being loaded is <frame src="foo.html">
+ * (or, more generally, if the element QIs to nsFrameLoaderOwner),
+ * the frameBrowsingContext is the browsing context containing the
+ * foo.html document.
+ *
+ * Note: For other cases, frameBrowsingContextID is 0.
+ */
+ [infallible] readonly attribute unsigned long long frameBrowsingContextID;
+ [infallible] readonly attribute BrowsingContext frameBrowsingContext;
+
+ /**
+ * If the element being loaded is a nsFrameLoaderOwner,
+ * `targetBrowsingContext` is the Browsing Context which will contain the
+ * loading document (see `frameBrowsingContext`). Otherwise, it is the
+ * Browsing Context performing the load (see `browsingContext`).
+ */
+ [infallible] readonly attribute unsigned long long targetBrowsingContextID;
+ [infallible] readonly attribute BrowsingContext targetBrowsingContext;
+
+ /**
+ * Resets the PrincipalToInherit to a freshly created NullPrincipal
+ * which inherits the origin attributes from the loadInfo.
+ *
+ * WARNING: Please only use that function if you know exactly what
+ * you are doing!!!
+ */
+ void resetPrincipalToInheritToNullPrincipal();
+
+ /**
+ * Customized OriginAttributes within LoadInfo to allow overwriting of the
+ * default originAttributes from the loadingPrincipal.
+ *
+ * In chrome side, originAttributes.privateBrowsingId will always be 0 even if
+ * the usePrivateBrowsing is true, because chrome docshell won't set
+ * privateBrowsingId on origin attributes (See bug 1278664). This is to make
+ * sure nsILoadInfo and nsILoadContext have the same origin attributes.
+ */
+ [implicit_jscontext, binaryname(ScriptableOriginAttributes)]
+ attribute jsval originAttributes;
+
+ [noscript, nostdcall, binaryname(GetOriginAttributes)]
+ OriginAttributes binaryGetOriginAttributes();
+
+ [noscript, nostdcall, binaryname(SetOriginAttributes)]
+ void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs);
+
+%{ C++
+ inline mozilla::OriginAttributes GetOriginAttributes()
+ {
+ mozilla::OriginAttributes result;
+ mozilla::DebugOnly<nsresult> rv = GetOriginAttributes(&result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return result;
+ }
+%}
+
+ /**
+ * Whenever a channel is evaluated by the ContentSecurityManager
+ * the first time, we set this flag to true to indicate that
+ * subsequent calls of AsyncOpen() do not have to enforce all
+ * security checks again. E.g., after a redirect there is no
+ * need to set up CORS again. We need this separate flag
+ * because the redirectChain might also contain internal
+ * redirects which might pollute the redirectChain so we can't
+ * rely on the size of the redirectChain-array to query whether
+ * a channel got redirected or not.
+ *
+ * Please note, once the flag is set to true it must remain true
+ * throughout the lifetime of the channel. Trying to set it
+ * to anything else than true will be discarded.
+ *
+ */
+ [infallible] attribute boolean initialSecurityCheckDone;
+
+ /**
+ * Returns true if the load was triggered from an external application
+ * (e.g. Thunderbird). Please note that this flag will only ever be true
+ * if the load is of TYPE_DOCUMENT.
+ */
+ [infallible] attribute boolean loadTriggeredFromExternal;
+
+ /**
+ * True if the tainting has been set by the service worker.
+ */
+ [noscript, infallible] readonly attribute boolean serviceWorkerTaintingSynthesized;
+
+ /**
+ * Whenever a channel gets redirected, append the redirect history entry of
+ * the channel which contains principal referrer and remote address [before
+ * the channels got redirected] to the loadinfo, so that at every point this
+ * array provides us information about all the redirects this channel went
+ * through.
+ * @param channelToDeriveFrom the channel being redirected
+ * @param aIsInternalRedirect should be true if the channel is going
+ * through an internal redirect, otherwise false.
+ */
+ void appendRedirectHistoryEntry(in nsIChannel channelToDeriveFrom,
+ in boolean isInternalRedirect);
+
+ /**
+ * An array of nsIRedirectHistoryEntry which stores redirects associated
+ * with this channel. This array is filled whether or not the channel has
+ * ever been opened. The last element of the array is associated with the
+ * most recent redirect. Please note, that this array *includes* internal
+ * redirects.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval redirectChainIncludingInternalRedirects;
+
+ /**
+ * A C++-friendly version of redirectChain.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(RedirectChainIncludingInternalRedirects)]
+ nsIRedirectHistoryEntryArray binaryRedirectChainIncludingInternalRedirects();
+
+ /**
+ * Same as RedirectChain but does *not* include internal redirects.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval redirectChain;
+
+ /**
+ * A C++-friendly version of redirectChain.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(RedirectChain)]
+ nsIRedirectHistoryEntryArray binaryRedirectChain();
+
+ /**
+ * This array is only filled out when we are in the parent process and we are
+ * creating a loadInfo object or deserializing LoadInfoArgs into LoadInfo,
+ * as we ever only need in the parent process.
+ *
+ * The array is meant to be a list of principals of the documents that the
+ * browsing context, corresponding to this loadInfo object, is "nested through" in
+ * the sense of
+ * <https://html.spec.whatwg.org/multipage/browsers.html#browsing-context-nested-through>.
+ * Note that the array does not include the principal corresponding to the frame
+ * loading this request. The closest ancestor is at index zero and the top level
+ * ancestor is at the last index.
+ *
+ * If this is a toplevel content browsing context (i.e. toplevel document in spec
+ * terms), the list is empty.
+ *
+ * Otherwise the array is a list for the document we're nested through (again in
+ * the spec sense), with the principal of that document prepended. The
+ * ancestorPrincipals[0] entry for an iframe load will be the principal of the
+ * iframe element's owner document. The ancestorPrincipals[0] entry for an image
+ * loaded in an iframe will be the principal of the iframe element's owner
+ * document. This matches the ordering specified for Location.ancestorOrigins.
+ *
+ * Please note that this array has the same lifetime as the loadInfo object - use
+ * with caution!
+ */
+ [noscript, notxpcom, nostdcall]
+ PrincipalArrayRef AncestorPrincipals();
+
+ /**
+ * An array of BrowsingContext IDs which correspond to nsILoadInfo::AncestorPrincipals
+ * above. AncestorBrowsingContextIDs[0] is the BrowsingContext ID of the frame
+ * associated with the principal at ancestorPrincipals[0], and so forth.
+ *
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall]
+ Uint64ArrayRef AncestorBrowsingContextIDs();
+
+ /**
+ * Sets the list of unsafe headers according to CORS spec, as well as
+ * potentially forces a preflight.
+ * Note that you do not need to set the Content-Type header. That will be
+ * automatically detected as needed.
+ *
+ * Only call this function when using the SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT mode.
+ */
+ [noscript, notxpcom, nostdcall]
+ void setCorsPreflightInfo(in CStringArrayRef unsafeHeaders,
+ in boolean forcePreflight);
+
+ /**
+ * A C++-friendly getter for the list of cors-unsafe headers.
+ * Please note that this array has the same lifetime as the
+ * loadInfo object - use with caution!
+ */
+ [noscript, notxpcom, nostdcall, binaryname(CorsUnsafeHeaders)]
+ CStringArrayRef corsUnsafeHeaders();
+
+ /**
+ * Returns value set through setCorsPreflightInfo.
+ */
+ [infallible] readonly attribute boolean forcePreflight;
+
+ /**
+ * A C++ friendly getter for the forcePreflight flag.
+ */
+ [infallible] readonly attribute boolean isPreflight;
+
+ /**
+ * Constants reflecting the channel tainting. These are mainly defined here
+ * for script. Internal C++ code should use the enum defined in LoadTainting.h.
+ * See LoadTainting.h for documentation.
+ */
+ const unsigned long TAINTING_BASIC = 0;
+ const unsigned long TAINTING_CORS = 1;
+ const unsigned long TAINTING_OPAQUE = 2;
+
+ /**
+ * Determine the associated channel's current tainting. Note, this can
+ * change due to a service worker intercept, so it should be checked after
+ * OnStartRequest() fires.
+ */
+ readonly attribute unsigned long tainting;
+
+ /**
+ * Note a new tainting level and possibly increase the current tainting
+ * to match. If the tainting level is already greater than the given
+ * value, then there is no effect. It is not possible to reduce the tainting
+ * level on an existing channel/loadinfo.
+ */
+ void maybeIncreaseTainting(in unsigned long aTainting);
+
+ /**
+ * Various helper code to provide more convenient C++ access to the tainting
+ * attribute and maybeIncreaseTainting().
+ */
+%{C++
+ static_assert(TAINTING_BASIC == static_cast<uint32_t>(mozilla::LoadTainting::Basic),
+ "basic tainting enums should match");
+ static_assert(TAINTING_CORS == static_cast<uint32_t>(mozilla::LoadTainting::CORS),
+ "cors tainting enums should match");
+ static_assert(TAINTING_OPAQUE == static_cast<uint32_t>(mozilla::LoadTainting::Opaque),
+ "opaque tainting enums should match");
+
+ mozilla::LoadTainting GetTainting()
+ {
+ uint32_t tainting = TAINTING_BASIC;
+ MOZ_ALWAYS_SUCCEEDS(GetTainting(&tainting));
+ return static_cast<mozilla::LoadTainting>(tainting);
+ }
+
+ void MaybeIncreaseTainting(mozilla::LoadTainting aTainting)
+ {
+ uint32_t tainting = static_cast<uint32_t>(aTainting);
+ MOZ_ALWAYS_SUCCEEDS(MaybeIncreaseTainting(tainting));
+ }
+%}
+
+ /**
+ * Returns true if this load is for top level document.
+ * Note that the load for a sub-frame's document will return false here.
+ */
+ [infallible] readonly attribute boolean isTopLevelLoad;
+
+ /**
+ * If this is non-null, this property represents two things: (1) the
+ * URI to be used for the principal if the channel with this loadinfo
+ * gets a principal based on URI and (2) the URI to use for a document
+ * created from the channel with this loadinfo.
+ */
+ attribute nsIURI resultPrincipalURI;
+
+ /**
+ * This is the URI used to create the most recent channel in the load's
+ * redirect chain, if it's different from channel's `originalURI`.
+ * This is always null for loads not handled by DocumentLoadListener. If
+ * non-null, channelCreationOriginalURI will be used instead of channel's
+ * originalURI to re-create the channel in the final content process selected
+ * to perform the load.
+ */
+ attribute nsIURI channelCreationOriginalURI;
+
+ /**
+ * Returns a unique nsID used to construct the null principal for the
+ * resulting resource if the SANDBOXED_ORIGIN flag is set. This is used by
+ * GetChannelResultPrincipal() to ensure that the same null principal is
+ * returned every time.
+ */
+ [noscript, nostdcall, notxpcom]
+ nsIDRef GetSandboxedNullPrincipalID();
+
+ /**
+ * Generates a new nsID to be returned by a future call to
+ * `GetSandboxedNullPrincipalID()`.
+ */
+ [noscript, nostdcall, notxpcom]
+ void ResetSandboxedNullPrincipalID();
+
+ /**
+ * Return the top-level principal, which is the principal of the top-level
+ * window.
+ */
+ [notxpcom, nostdcall] readonly attribute nsIPrincipal topLevelPrincipal;
+
+ /**
+ * Note which client (i.e. global) initiated this network request. All
+ * nsGlobalWindow and WorkerPrivate can be converted to a ClientInfo to
+ * be set here. While this is being added to support service worker
+ * FetchEvent, it can also be used to communicate other information about
+ * the source global context in the future.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetClientInfo(in const_ClientInfoRef aClientInfo);
+
+ /**
+ * Get the ClientInfo for the global that initiated the network request,
+ * if it has been set.
+ */
+ [noscript, nostdcall, notxpcom]
+ const_MaybeClientInfoRef GetClientInfo();
+
+ /**
+ * Give a pre-allocated ClientSource to the channel LoadInfo. This is
+ * intended to be used by docshell when loading windows without an
+ * initial about:blank document. The docshell will allocate the ClientSource
+ * to represent the client that will be created as a result of the navigation
+ * network request. If the channel succeeds and remains same-origin, then
+ * the result nsGlobalWindow will take ownership of the reserved ClientSource.
+ *
+ * This method is also called when a cross-origin redirect occurs. A new
+ * ClientSource with a different UUID must be created in this case.
+ *
+ * This method automatically calls SetReservedClientInfo() with the
+ * ClientSource::Info().
+ */
+ [noscript, nostdcall, notxpcom]
+ void GiveReservedClientSource(in UniqueClientSourceMove aClientSource);
+
+ /**
+ * This method takes ownership of the reserved ClientSource previously
+ * provided in GiveReservedClientSource(). It may return nullptr if the
+ * nsILoadInfo does not own a ClientSource object.
+ */
+ [noscript, nostdcall, notxpcom]
+ UniqueClientSource TakeReservedClientSource();
+
+ /**
+ * Note the reserved client that be created if this non-subresource
+ * network request succeeds. Depending on the type of client this
+ * may be called directly or indirectly via GiveReservedClientSource().
+ * For example, web workers do not call give their ClientSource to
+ * the nsILoadInfo, but must still call this method to indicate the
+ * reserved client for their main script load.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetReservedClientInfo(in const_ClientInfoRef aClientInfo);
+
+ /**
+ * This will clear any existing reserved or initial client and override
+ * it with the given reserved client. This is similar to calling
+ * TakeReservedClientSource() and then GiveReservedClientSource() with
+ * a new client as ClientChannelHelper does. This method is needed,
+ * though, to perform this operation in the parent process where
+ * the LoadInfo does not have direct access to a ClientSource.
+ *
+ * If in doubt, do not call this method. Its really only needed for
+ * a specific redirect case where the child has created a new client on
+ * redirect and we need to override the parent side's reserved client
+ * to match.
+ */
+ [noscript, nostdcall, notxpcom]
+ void OverrideReservedClientInfoInParent(in const_ClientInfoRef aClientInfo);
+
+ /**
+ * Return the reserved ClientInfo for this load, if one has been set.
+ */
+ [noscript, nostdcall, notxpcom]
+ const_MaybeClientInfoRef GetReservedClientInfo();
+
+ /**
+ * Note that this non-subresource network request will result in
+ * re-using an existing "initial" active client. This mainly only
+ * happens when an initial about:blank document is replaced with
+ * a real load in a window. In these cases we need to track this
+ * initial client so that we may report its existence in a FetchEvent.
+ *
+ * Note, an nsILoadInfo may only have a reserved client or an
+ * initial client. It should never have both.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetInitialClientInfo(in const_ClientInfoRef aClientInfo);
+
+ /**
+ * Return the initial ClientInfo for this load, if one has been set.
+ */
+ [noscript, nostdcall, notxpcom]
+ const_MaybeClientInfoRef GetInitialClientInfo();
+
+ /**
+ * Note that this network request should be controlled by a service worker.
+ * For non-subresource requests this may be set during the load when
+ * the first service worker interception occurs. For subresource requests
+ * it may be set by the source client if its already controlled by a
+ * service worker.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetController(in const_ServiceWorkerDescriptorRef aServiceWorker);
+
+ /**
+ * Clear the service worker controller for this channel. This should only
+ * be used for window navigation redirects. By default we want to keep
+ * the controller in all other cases.
+ */
+ [noscript, nostdcall, notxpcom]
+ void ClearController();
+
+ /**
+ * Get the service worker controlling this network request, if one has
+ * been set.
+ */
+ [noscript, nostdcall, notxpcom]
+ const_MaybeServiceWorkerDescriptorRef GetController();
+
+ /**
+ * Set a custom performance storage. This is meant to be executed only for
+ * workers. If a PerformanceStorage is not set, the loadingDocument->Window
+ * Performance object will be returned instead.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SetPerformanceStorage(in PerformanceStoragePtr aPerformanceStorage);
+
+ /**
+ * Get the custom PerformanceStorage if set by SetPerformanceStorage.
+ * Otherwise the loadingDocument->Window Performance object will be returned
+ * instead if all the following conditions are met:
+ * - the triggeringPrincipal is the same as the loadingDocument's principal.
+ * - if the external content policy type is TYPE_SUBDOCUMENT then loading
+ * wasn't caused by processing the attributes of the browsing context
+ * container.
+ */
+ [noscript, nostdcall, notxpcom]
+ PerformanceStoragePtr GetPerformanceStorage();
+
+ /**
+ * Returns the CSP (or Preload CSP for preloads) which should be enforced
+ * when fetching the resource this loadinfo belongs to.
+ *
+ * a) Non-navigations:
+ * For non-navigation loads, GetCsp() returns what the spec refers to as the
+ * "request's client's global object's CSP list". In practice, if this is the
+ * loadinfo of a subresource load (e.g an image load), then GetCsp() or
+ * GetPreloadCSP() returns the CSP of the document which embeds the image.
+ * The returned CSP includes any policy delivered through the HTTP header or
+ * also through the meta tag (modulo the difference for preloads, e.g. image
+ * preloads have to query GetPreloadCsp() because at the time of preloading
+ * we are not entirely sure if the Meta CSP will be applied to the document
+ * in the end or not). Please note that GetCSPToInherit() called on a
+ * loadinfo for any non-navigation always returns null.
+ *
+ * b) Navigations:
+ * * Top-level loads:
+ * For top-level loads (navigations) GetCsp() will return null, unless
+ * the navigation is started by a WebExtension, in which case it will
+ * return the CSP of the webextension, if any.
+ * If you need to query the CSP that potentially should apply to the
+ * new top-level load, you have to query GetCspToInherit(), which is
+ * the CSP of the request's client's global object, just like GetCsp()
+ * is for non-navigation requests.
+ *
+ * * Iframe-loads:
+ * For iframe-loads (navigations) GetCsp() will return the CSP of the
+ * parent document, unless the navigation is started by a WebExtension,
+ * in which case it will return the CSP of the webextension, if any.
+ *
+ * If you need to query the CSP that should potentially be inherited
+ * into the new document, you have to query GetCSPToInherit().
+ *
+ * TODO Bug 1557114:
+ * After evaluating what CSP to use for frame navigations we should
+ * update the above documentation to match the outcome of Bug 1557114.
+ */
+ [notxpcom,nostdcall] CSPRef GetCsp();
+ [notxpcom,nostdcall] CSPRef GetPreloadCsp();
+ [notxpcom,nostdcall] CSPRef GetCspToInherit();
+
+ /**
+ * The service worker and fetch specifications require returning the
+ * exact tainting level of the Response passed to FetchEvent.respondWith().
+ * This method allows us to override the tainting level in that case.
+ *
+ * NOTE: This should not be used outside of service worker code! Use
+ * nsILoadInfo::MaybeIncreaseTainting() instead.
+ */
+ [noscript, nostdcall, notxpcom]
+ void SynthesizeServiceWorkerTainting(in LoadTainting aTainting);
+
+ /**
+ * The top-level document has been user-interacted.
+ */
+ [infallible] attribute boolean documentHasUserInteracted;
+
+ /**
+ * During a top-level document channel redirect from tracking to
+ * non-tracking resources, our anti-tracking heuristic, grants the storage
+ * access permission for a short amount of seconds (See
+ * privacy.restrict3rdpartystorage.expiration_redirect pref).
+ * We use this flag to remember this decision even if this channel is part
+ * of a chain of redirects.
+ */
+ [infallible] attribute boolean allowListFutureDocumentsCreatedFromThisRedirectChain;
+
+ /**
+ * Indicates that we need to check if we should apply the anti-tracking
+ * heuristic after the channel has been classified.
+ */
+ [infallible] attribute boolean needForCheckingAntiTrackingHeuristic;
+
+ /**
+ * A snapshot of the nonce at load start time which is used for CSP
+ * checks and only set for:
+ * * TYPE_SCRIPT and
+ * * TYPE_STYLESHEET
+ */
+ attribute AString cspNonce;
+
+ // Subresource Integrity (SRI) metadata.
+ attribute AString integrityMetadata;
+
+ /**
+ * List of possible reasons the request associated with this load info
+ * may have been blocked, set by various content blocking checkers.
+ */
+ const uint32_t BLOCKING_REASON_NONE = 0;
+ const uint32_t BLOCKING_REASON_CORSDISABLED = 1001;
+ const uint32_t BLOCKING_REASON_CORSDIDNOTSUCCEED = 1002;
+ const uint32_t BLOCKING_REASON_CORSREQUESTNOTHTTP = 1003;
+ const uint32_t BLOCKING_REASON_CORSMULTIPLEALLOWORIGINNOTALLOWED = 1004;
+ const uint32_t BLOCKING_REASON_CORSMISSINGALLOWORIGIN = 1005;
+ const uint32_t BLOCKING_REASON_CORSNOTSUPPORTINGCREDENTIALS = 1006;
+ const uint32_t BLOCKING_REASON_CORSALLOWORIGINNOTMATCHINGORIGIN = 1007;
+ const uint32_t BLOCKING_REASON_CORSMISSINGALLOWCREDENTIALS = 1008;
+ const uint32_t BLOCKING_REASON_CORSORIGINHEADERNOTADDED = 1009;
+ const uint32_t BLOCKING_REASON_CORSEXTERNALREDIRECTNOTALLOWED = 1010;
+ const uint32_t BLOCKING_REASON_CORSPREFLIGHTDIDNOTSUCCEED = 1011;
+ const uint32_t BLOCKING_REASON_CORSINVALIDALLOWMETHOD = 1012;
+ const uint32_t BLOCKING_REASON_CORSMETHODNOTFOUND = 1013;
+ const uint32_t BLOCKING_REASON_CORSINVALIDALLOWHEADER = 1014;
+ const uint32_t BLOCKING_REASON_CORSMISSINGALLOWHEADERFROMPREFLIGHT = 1015;
+ const uint32_t BLOCKING_REASON_CLASSIFY_MALWARE_URI = 2001;
+ const uint32_t BLOCKING_REASON_CLASSIFY_PHISHING_URI = 2002;
+ const uint32_t BLOCKING_REASON_CLASSIFY_UNWANTED_URI = 2003;
+ const uint32_t BLOCKING_REASON_CLASSIFY_TRACKING_URI = 2004;
+ const uint32_t BLOCKING_REASON_CLASSIFY_BLOCKED_URI = 2005;
+ const uint32_t BLOCKING_REASON_CLASSIFY_HARMFUL_URI = 2006;
+ const uint32_t BLOCKING_REASON_CLASSIFY_CRYPTOMINING_URI = 2007;
+ const uint32_t BLOCKING_REASON_CLASSIFY_FINGERPRINTING_URI = 2008;
+ const uint32_t BLOCKING_REASON_CLASSIFY_SOCIALTRACKING_URI = 2009;
+ const uint32_t BLOCKING_REASON_CLASSIFY_EMAILTRACKING_URI = 2010;
+ const uint32_t BLOCKING_REASON_MIXED_BLOCKED = 3001;
+ // The general reason comes from nsCSPContext::permitsInternal(),
+ // which is way too generic to distinguish an exact reason.
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_GENERAL = 4000;
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_NO_DATA_PROTOCOL = 4001;
+ // removed 4002
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_CONTENT_BLOCKED = 4003;
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_DATA_DOCUMENT = 4004;
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_WEB_BROWSER = 4005;
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_PRELOAD = 4006;
+ // The reason used when SEC_REQUIRE_SAME_ORIGIN_* is set and not satisifed.
+ const uint32_t BLOCKING_REASON_NOT_SAME_ORIGIN = 5000;
+ // The reason used when an extension cancels the request via the WebRequest api.
+ const uint32_t BLOCKING_REASON_EXTENSION_WEBREQUEST = 6000;
+ // The reason used when a request is cancelled via WebDriver BiDi network interception.
+ const uint32_t BLOCKING_REASON_WEBDRIVER_BIDI = 7000;
+
+ /**
+ * If the request associated with this load info was blocked by some of
+ * our content or load blockers, the reason can be found here.
+ * Note that setting this attribute has NO EFFECT on blocking the request.
+ * This attribute is only informative!
+ *
+ * By default the value is '0' - NONE.
+ * Each write rewrites the last value.
+ * Can be accessed only on a single thread.
+ */
+ [infallible] attribute unsigned long requestBlockingReason;
+
+ /**
+ * The object in charged to receive CSP violation events. It can be null.
+ * This attribute will be merged into the CSP object eventually.
+ * See bug 1500908.
+ */
+ attribute nsICSPEventListener cspEventListener;
+
+ /**
+ * This attribute will be true if this is a load triggered by
+ * https://html.spec.whatwg.org/multipage/iframe-embed-object.html#process-the-iframe-attributes
+ * or https://html.spec.whatwg.org/multipage/obsolete.html#process-the-frame-attributes
+ */
+ [infallible] readonly attribute boolean isFromProcessingFrameAttributes;
+
+ cenum CrossOriginOpenerPolicy : 8 {
+ OPENER_POLICY_UNSAFE_NONE = 0,
+ OPENER_POLICY_SAME_ORIGIN = 1,
+ OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS = 2,
+ OPENER_POLICY_EMBEDDER_POLICY_REQUIRE_CORP_FLAG = 0x10,
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP =
+ OPENER_POLICY_SAME_ORIGIN |
+ OPENER_POLICY_EMBEDDER_POLICY_REQUIRE_CORP_FLAG
+ };
+
+ cenum CrossOriginEmbedderPolicy : 8 {
+ EMBEDDER_POLICY_NULL = 0,
+ EMBEDDER_POLICY_REQUIRE_CORP = 1,
+ EMBEDDER_POLICY_CREDENTIALLESS = 2,
+ };
+
+ /**
+ * This attribute is the loading context's cross origin embedder policy.
+ * The value is initialized with corresponding WindowContext which get by
+ * innerWindowIID in the nsILoadInfo.
+ * It also could be set by workers when fetch is called under
+ * the workers' scope.
+ */
+ [infallible] attribute nsILoadInfo_CrossOriginEmbedderPolicy
+ loadingEmbedderPolicy;
+
+ /**
+ * This attribute will be true if the top level document has COEP:
+ * credentialless enabled in Origin Trial.
+ */
+ [infallible] attribute boolean isOriginTrialCoepCredentiallessEnabledForTopLevel;
+ /**
+ * This attribute will be true if this is a load triggered by a media
+ * element.
+ */
+ [infallible] attribute boolean isMediaRequest;
+
+ /**
+ * This attribute will be true if this is a load triggered by a media
+ * element and it's an initial request.
+ */
+ [infallible] attribute boolean isMediaInitialRequest;
+
+ /**
+ * This attribute will be true if the fetch request is from object or embed
+ * elements
+ */
+ [infallible] attribute boolean isFromObjectOrEmbed;
+
+ /**
+ * This attribute will be true if the URL is known to be possibly broken and
+ * CheckForBrokenChromeURL and RecordZeroLengthEvent should be skipped.
+ */
+ [infallible] readonly attribute boolean shouldSkipCheckForBrokenURLOrZeroSized;
+
+ /**
+ * If this is non-null, this property holds the URI as it was before query
+ * stripping was performed.
+ */
+ attribute nsIURI unstrippedURI;
+
+ /**
+ * Propagated information from InterceptedHttpChannel
+ * It should be null when the channel is not created from FetchEvent.request
+ * or ServiceWorker NavigationPreload.
+ * nsIFetchEventInfo is C++ only, so it is not an attribute.
+ */
+ [noscript, notxpcom, nostdcall, binaryname(InterceptionInfo)]
+ nsIInterceptionInfo binaryInterceptionInfo();
+
+ [noscript, notxpcom, nostdcall, binaryname(SetInterceptionInfo)]
+ void binarySetInterceptionInfo(in nsIInterceptionInfo info);
+
+ /**
+ * Whether nsICookieInjector has injected a cookie for this request to
+ * handle a cookie banner. This is only done for top-level requests.
+ */
+ [infallible] attribute boolean hasInjectedCookieForCookieBannerHandling;
+
+ /**
+ * Whether the load has gone through the URL bar, where the fixup had to add * the protocol scheme.
+ */
+ [infallible] attribute boolean wasSchemelessInput;
+};
diff --git a/netwerk/base/nsIMIMEInputStream.idl b/netwerk/base/nsIMIMEInputStream.idl
new file mode 100644
index 0000000000..1842cdb40c
--- /dev/null
+++ b/netwerk/base/nsIMIMEInputStream.idl
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIHttpHeaderVisitor.idl"
+#include "nsIInputStream.idl"
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+[scriptable, builtinclass, uuid(dcbce63c-1dd1-11b2-b94d-91f6d49a3161)]
+interface nsIMIMEInputStream : nsIInputStream
+{
+ /**
+ * Adds an additional header to the stream on the form "name: value". May
+ * not be called once the stream has been started to be read.
+ * @param name name of the header
+ * @param value value of the header
+ */
+ void addHeader(in string name, in string value);
+
+ /**
+ * Visits all headers which have been added via addHeader. Calling
+ * addHeader while visiting request headers has undefined behavior.
+ *
+ * @param aVisitor
+ * The header visitor instance.
+ */
+ void visitHeaders(in nsIHttpHeaderVisitor visitor);
+
+ /**
+ * Sets data-stream. May not be called once the stream has been started
+ * to be read.
+ * The cursor of the new stream should be located at the beginning of the
+ * stream if the implementation of the nsIMIMEInputStream also is used as
+ * an nsISeekableStream.
+ * @param stream stream containing the data for the stream
+ */
+ void setData(in nsIInputStream stream);
+
+ /**
+ * Get the wrapped data stream
+ */
+ readonly attribute nsIInputStream data;
+};
diff --git a/netwerk/base/nsIMultiPartChannel.idl b/netwerk/base/nsIMultiPartChannel.idl
new file mode 100644
index 0000000000..3e4bf58812
--- /dev/null
+++ b/netwerk/base/nsIMultiPartChannel.idl
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * An interface to access the the base channel
+ * associated with a MultiPartChannel.
+ */
+
+[scriptable, builtinclass, uuid(4fefb490-5567-11e5-a837-0800200c9a66)]
+interface nsIMultiPartChannel : nsISupports
+{
+ /**
+ * readonly attribute to access the underlying channel
+ */
+ readonly attribute nsIChannel baseChannel;
+
+ /**
+ * Attribute guaranteed to be different for different parts of
+ * the same multipart document.
+ */
+ readonly attribute uint32_t partID;
+
+ [noscript] readonly attribute boolean isFirstPart;
+
+ /**
+ * Set to true when onStopRequest is received from the base channel.
+ * The listener can check this from its onStopRequest to determine
+ * whether more data can be expected.
+ */
+ readonly attribute boolean isLastPart;
+};
+
+/**
+ * An interface that listeners can implement to receive a notification
+ * when the last part of the multi-part channel has finished, and the
+ * final OnStopRequest has been sent.
+ */
+[scriptable, uuid(b084959a-4fb9-41a5-88a0-d0f045ce75cf)]
+interface nsIMultiPartChannelListener : nsISupports
+{
+ /**
+ * Sent when all parts have finished and sent OnStopRequest.
+ */
+ void onAfterLastPart(in nsresult status);
+};
diff --git a/netwerk/base/nsINestedURI.idl b/netwerk/base/nsINestedURI.idl
new file mode 100644
index 0000000000..df124348af
--- /dev/null
+++ b/netwerk/base/nsINestedURI.idl
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsINestedURI is an interface that must be implemented by any nsIURI
+ * implementation which has an "inner" URI that it actually gets data
+ * from.
+ *
+ * For example, if URIs for the scheme "sanitize" have the structure:
+ *
+ * sanitize:http://example.com
+ *
+ * and opening a channel on such a sanitize: URI gets the data from
+ * http://example.com, sanitizes it, and returns it, then the sanitize: URI
+ * should implement nsINestedURI and return the http://example.com URI as its
+ * inner URI.
+ */
+[scriptable, builtinclass, uuid(6de2c874-796c-46bf-b57f-0d7bd7d6cab0)]
+interface nsINestedURI : nsISupports
+{
+ /**
+ * The inner URI for this nested URI. This must not return null if the
+ * getter succeeds; URIs that have no inner must not QI to this interface.
+ * Dynamically changing whether there is an inner URI is not allowed.
+ *
+ * Modifying the returned URI must not in any way modify the nested URI; this
+ * means the returned URI must be either immutable or a clone.
+ */
+ readonly attribute nsIURI innerURI;
+
+ /**
+ * The innermost URI for this nested URI. This must not return null if the
+ * getter succeeds. This is equivalent to repeatedly calling innerURI while
+ * the returned URI QIs to nsINestedURI.
+ *
+ * Modifying the returned URI must not in any way modify the nested URI; this
+ * means the returned URI must be either immutable or a clone.
+ */
+ readonly attribute nsIURI innermostURI;
+};
+
+[scriptable, builtinclass, uuid(ca3d6c03-4eee-4271-a97a-d16c0a0b2c5c)]
+interface nsINestedURIMutator : nsISupports
+{
+ /*
+ * - Creates a new URI with the given innerURI.
+ */
+ [must_use, noscript] void init(in nsIURI innerURI);
+};
+
+[scriptable, builtinclass, uuid(c6357a3b-c2bb-4b4b-9278-513377398a38)]
+interface nsINestedAboutURIMutator : nsISupports
+{
+ /*
+ * - Creates a new URI with the given innerURI and base.
+ */
+ [must_use, noscript] void initWithBase(in nsIURI innerURI, in nsIURI baseURI);
+};
+
+[scriptable, builtinclass, uuid(3bd44535-08ea-478f-99b9-85fa1084e820)]
+interface nsIJSURIMutator : nsISupports
+{
+ /*
+ * - Inits the URI by setting the base URI
+ * - It is the caller's responsibility to also call SetSpec afterwards,
+ * otherwise we will return an incomplete URI (with only a base)
+ */
+ [must_use, noscript] void setBase(in nsIURI aBaseURI);
+};
diff --git a/netwerk/base/nsINetAddr.idl b/netwerk/base/nsINetAddr.idl
new file mode 100644
index 0000000000..bbbcd28c0e
--- /dev/null
+++ b/netwerk/base/nsINetAddr.idl
@@ -0,0 +1,88 @@
+/* vim: et ts=4 sw=4 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+
+/**
+ * nsINetAddr
+ *
+ * This interface represents a native NetAddr struct in a readonly
+ * interface.
+ */
+[scriptable, uuid(652B9EC5-D159-45D7-9127-50BB559486CD)]
+interface nsINetAddr : nsISupports
+{
+ /**
+ * @return the address family of the network address, which corresponds to
+ * one of the FAMILY_ constants.
+ */
+ readonly attribute unsigned short family;
+
+ /**
+ * @return Either the IP address (FAMILY_INET, FAMILY_INET6) or the path
+ * (FAMILY_LOCAL) in string form. IP addresses are in the format produced by
+ * mozilla::net::NetAddr::ToStringBuffer.
+ *
+ * Note: Paths for FAMILY_LOCAL may have length limitations which are
+ * implementation dependent and not documented as part of this interface.
+ */
+ readonly attribute AUTF8String address;
+
+ /**
+ * @return the port number for a FAMILY_INET or FAMILY_INET6 address.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET or
+ * FAMILY_INET6.
+ */
+ readonly attribute unsigned short port;
+
+ /**
+ * @return the flow label for a FAMILY_INET6 address.
+ *
+ * @see http://www.ietf.org/rfc/rfc3697.txt
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute unsigned long flow;
+
+ /**
+ * @return the address scope of a FAMILY_INET6 address.
+ *
+ * @see http://tools.ietf.org/html/rfc4007
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute unsigned long scope;
+
+ /**
+ * @return whether a FAMILY_INET6 address is mapped from FAMILY_INET.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if the address family is not FAMILY_INET6
+ */
+ readonly attribute boolean isV4Mapped;
+
+ /**
+ * Network address families. These correspond to all the network address
+ * families supported by the NetAddr struct.
+ */
+ const unsigned long FAMILY_INET = 1;
+ const unsigned long FAMILY_INET6 = 2;
+ const unsigned long FAMILY_LOCAL = 3;
+
+ /**
+ * @return the underlying NetAddr struct.
+ */
+ [noscript] NetAddr getNetAddr();
+};
diff --git a/netwerk/base/nsINetUtil.idl b/netwerk/base/nsINetUtil.idl
new file mode 100644
index 0000000000..67e6c54f2b
--- /dev/null
+++ b/netwerk/base/nsINetUtil.idl
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIPrefBranch;
+
+/**
+ * nsINetUtil provides various network-related utility methods.
+ */
+[scriptable, uuid(fe2625ec-b884-4df1-b39c-9e830e47aa94)]
+interface nsINetUtil : nsISupports
+{
+ /**
+ * Parse a Content-Type header value in strict mode. This is a more
+ * conservative parser that reject things that violate RFC 7231 section
+ * 3.1.1.1. This is typically useful for parsing Content-Type header values
+ * that are used for HTTP requests, and those that are used to make security
+ * decisions.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aHadCharset whether a charset was explicitly specified.
+ * @return the MIME type specified in the header, in lower-case.
+ */
+ AUTF8String parseRequestContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out boolean aHadCharset);
+
+ /**
+ * Parse a Content-Type header value in relaxed mode. This is a more
+ * permissive parser that ignores things that go against RFC 7231 section
+ * 3.1.1.1. This is typically useful for parsing Content-Type header values
+ * received from web servers where we want to make a best effort attempt
+ * at extracting a useful MIME type and charset.
+ *
+ * NOTE: DO NOT USE THIS if you're going to make security decisions
+ * based on the result.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aHadCharset whether a charset was explicitly specified.
+ * @return the MIME type specified in the header, in lower-case.
+ */
+ AUTF8String parseResponseContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out boolean aHadCharset);
+
+ /**
+ * Test whether the given URI's handler has the given protocol flags.
+ *
+ * @param aURI the URI in question
+ * @param aFlags the flags we're testing for.
+ *
+ * @return whether the protocol handler for aURI has all the flags
+ * in aFlags.
+ */
+ boolean protocolHasFlags(in nsIURI aURI, in unsigned long aFlag);
+
+ /**
+ * Test whether the protocol handler for this URI or that for any of
+ * its inner URIs has the given protocol flags. This will QI aURI to
+ * nsINestedURI and walk the nested URI chain.
+ *
+ * @param aURI the URI in question
+ * @param aFlags the flags we're testing for.
+ *
+ * @return whether any of the protocol handlers involved have all the flags
+ * in aFlags.
+ */
+ boolean URIChainHasFlags(in nsIURI aURI, in unsigned long aFlags);
+
+ /** Escape every character with its %XX-escaped equivalent */
+ const unsigned long ESCAPE_ALL = 0;
+
+ /** Leave alphanumeric characters intact and %XX-escape all others */
+ const unsigned long ESCAPE_XALPHAS = 1;
+
+ /** Leave alphanumeric characters intact, convert spaces to '+',
+ %XX-escape all others */
+ const unsigned long ESCAPE_XPALPHAS = 2;
+
+ /** Leave alphanumeric characters and forward slashes intact,
+ %XX-escape all others */
+ const unsigned long ESCAPE_URL_PATH = 4;
+
+ /** Additional encoding for Apple's NSURL compatibility.
+ Like XALPHAS, but leave '%' to avoid double encoding, '+', and '/'.
+ %XX-escape all others */
+ const unsigned long ESCAPE_URL_APPLE_EXTRA = 8;
+
+ /**
+ * escape a string with %00-style escaping
+ */
+ ACString escapeString(in ACString aString, in unsigned long aEscapeType);
+
+ /** %XX-escape URL scheme */
+ const unsigned long ESCAPE_URL_SCHEME = 1;
+
+ /** %XX-escape username in the URL */
+ const unsigned long ESCAPE_URL_USERNAME = 1 << 1;
+
+ /** %XX-escape password in the URL */
+ const unsigned long ESCAPE_URL_PASSWORD = 1 << 2;
+
+ /** %XX-escape URL host */
+ const unsigned long ESCAPE_URL_HOST = 1 << 3;
+
+ /** %XX-escape URL directory */
+ const unsigned long ESCAPE_URL_DIRECTORY = 1 << 4;
+
+ /** %XX-escape file basename in the URL */
+ const unsigned long ESCAPE_URL_FILE_BASENAME = 1 << 5;
+
+ /** %XX-escape file extension in the URL */
+ const unsigned long ESCAPE_URL_FILE_EXTENSION = 1 << 6;
+
+ /** %XX-escape URL parameters */
+ const unsigned long ESCAPE_URL_PARAM = 1 << 7;
+
+ /** %XX-escape URL query */
+ const unsigned long ESCAPE_URL_QUERY = 1 << 8;
+
+ /** %XX-escape URL ref */
+ const unsigned long ESCAPE_URL_REF = 1 << 9;
+
+ /** %XX-escape URL path - same as escaping directory, basename and extension */
+ const unsigned long ESCAPE_URL_FILEPATH =
+ ESCAPE_URL_DIRECTORY | ESCAPE_URL_FILE_BASENAME | ESCAPE_URL_FILE_EXTENSION;
+
+ /** %XX-escape scheme, username, password, host, path, params, query and ref */
+ const unsigned long ESCAPE_URL_MINIMAL =
+ ESCAPE_URL_SCHEME | ESCAPE_URL_USERNAME | ESCAPE_URL_PASSWORD |
+ ESCAPE_URL_HOST | ESCAPE_URL_FILEPATH | ESCAPE_URL_PARAM |
+ ESCAPE_URL_QUERY | ESCAPE_URL_REF;
+
+ /** Force %XX-escaping of already escaped sequences */
+ const unsigned long ESCAPE_URL_FORCED = 1 << 10;
+
+ /** Skip non-ascii octets, %XX-escape all others */
+ const unsigned long ESCAPE_URL_ONLY_ASCII = 1 << 11;
+
+ /**
+ * Skip graphic octets (0x20-0x7E) when escaping
+ * Skips all ASCII octets (0x00-0x7F) when unescaping
+ */
+ const unsigned long ESCAPE_URL_ONLY_NONASCII = 1 << 12;
+
+ /** Force %XX-escape of colon */
+ const unsigned long ESCAPE_URL_COLON = 1 << 14;
+
+ /** Skip C0 and DEL from unescaping */
+ const unsigned long ESCAPE_URL_SKIP_CONTROL = 1 << 15;
+
+ /** %XX-escape external protocol handler URL */
+ const unsigned long ESCAPE_URL_EXT_HANDLER = 1 << 17;
+
+ /**
+ * %XX-Escape invalid chars in a URL segment.
+ *
+ * @param aStr the URL to be escaped
+ * @param aFlags the URL segment type flags
+ *
+ * @return the escaped string (the string itself if escaping did not happen)
+ *
+ */
+ ACString escapeURL(in ACString aStr, in unsigned long aFlags);
+
+ /**
+ * Expands URL escape sequences
+ *
+ * @param aStr the URL to be unescaped
+ * @param aFlags only ESCAPE_URL_ONLY_NONASCII and ESCAPE_URL_SKIP_CONTROL
+ * are recognized. If |aFlags| is 0 all escape sequences are
+ * unescaped
+ * @return unescaped string
+ */
+ ACString unescapeString(in AUTF8String aStr, in unsigned long aFlags);
+
+ /**
+ * Extract the charset parameter location and value from a content-type
+ * header.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aCharsetStart index of the start of the charset parameter
+ * (the ';' separating it from what came before) in aTypeHeader.
+ * If this function returns false, this argument will still be
+ * set, to the index of the location where a new charset should
+ * be inserted.
+ * @param [out] aCharsetEnd index of the end of the charset parameter (the
+ * ';' separating it from what comes after, or the end
+ * of the string) in aTypeHeader. If this function returns
+ * false, this argument will still be set, to the index of the
+ * location where a new charset should be inserted.
+ *
+ * @return whether a charset parameter was found. This can be false even in
+ * cases when parseContentType would claim to have a charset, if the type
+ * that won out does not have a charset parameter specified.
+ */
+ boolean extractCharsetFromContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out long aCharsetStart,
+ out long aCharsetEnd);
+
+ /**
+ * This is test-only. Send an IPC message to let socket process send a
+ * telemetry.
+ */
+ void socketProcessTelemetryPing();
+
+ /**
+ * This is a void method that is C++ implemented and always
+ * returns NS_ERROR_NOT_IMPLEMENTED. To be used for testing.
+ */
+ void notImplemented();
+};
diff --git a/netwerk/base/nsINetworkConnectivityService.idl b/netwerk/base/nsINetworkConnectivityService.idl
new file mode 100644
index 0000000000..482eaf45ee
--- /dev/null
+++ b/netwerk/base/nsINetworkConnectivityService.idl
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(2693457e-3ba5-4455-991f-5350946adb12)]
+interface nsINetworkConnectivityService : nsISupports
+{
+ /**
+ * Each tested feature may be in one of 3 states:
+ * UNKNOWN, if a check hasn't been performed.
+ * OK, if the feature was successfully tested
+ * NOT_AVAILABLE, if the feature is blocked by the network.
+ * Note that the endpoints are guaranteed to support the features.
+ */
+ cenum ConnectivityState: 32 {
+ UNKNOWN = 0,
+ OK = 1,
+ NOT_AVAILABLE = 2
+ };
+
+ /* If DNS v4/v6 queries actually work on the current network */
+ [infallible]
+ readonly attribute nsINetworkConnectivityService_ConnectivityState DNSv4;
+ [infallible]
+ readonly attribute nsINetworkConnectivityService_ConnectivityState DNSv6;
+
+ /* If connecting to IPv4/v6 works on the current network */
+ [infallible]
+ readonly attribute nsINetworkConnectivityService_ConnectivityState IPv4;
+ [infallible]
+ readonly attribute nsINetworkConnectivityService_ConnectivityState IPv6;
+
+ /* If a NAT64 gateway was detected on the current network */
+ [infallible]
+ readonly attribute nsINetworkConnectivityService_ConnectivityState NAT64;
+
+ /* Starts the DNS request to check for DNS v4/v6 availability */
+ void recheckDNS();
+
+ /* Starts HTTP requests over IPv4 and IPv6, and checks if they work */
+ void recheckIPConnectivity();
+};
diff --git a/netwerk/base/nsINetworkInfoService.idl b/netwerk/base/nsINetworkInfoService.idl
new file mode 100644
index 0000000000..9349abe13f
--- /dev/null
+++ b/netwerk/base/nsINetworkInfoService.idl
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Listener for getting list of addresses.
+ */
+[scriptable, uuid(c4bdaac1-3ab1-4fdb-9a16-17cbed794603)]
+interface nsIListNetworkAddressesListener : nsISupports
+{
+ /**
+ * Callback function that gets called by nsINetworkInfoService.listNetworkAddresses.
+ * Each address in the array is a string IP address in canonical form,
+ * e.g. 192.168.1.10, or an IPV6 address in string form.
+ */
+ void onListedNetworkAddresses(in Array<ACString> aAddressArray);
+ void onListNetworkAddressesFailed();
+};
+
+/**
+ * Listener for getting hostname.
+ */
+[scriptable, uuid(3ebdcb62-2df4-4042-8864-3fa81abd4693)]
+interface nsIGetHostnameListener : nsISupports
+{
+ void onGotHostname(in AUTF8String aHostname);
+ void onGetHostnameFailed();
+};
+
+/**
+ * Service information
+ */
+[scriptable, uuid(55fc8dae-4a58-4e0f-a49b-901cbabae809)]
+interface nsINetworkInfoService : nsISupports
+{
+ /**
+ * Obtain a list of local machine network addresses. The listener object's
+ * onListedNetworkAddresses will be called with the obtained addresses.
+ * On failure, the listener object's onListNetworkAddressesFailed() will be called.
+ */
+ void listNetworkAddresses(in nsIListNetworkAddressesListener aListener);
+
+ /**
+ * Obtain the hostname of the local machine. The listener object's
+ * onGotHostname will be called with the obtained hostname.
+ * On failure, the listener object's onGetHostnameFailed() will be called.
+ */
+ void getHostname(in nsIGetHostnameListener aListener);
+};
+
+%{ C++
+#define NETWORKINFOSERVICE_CONTRACT_ID \
+ "@mozilla.org/network-info-service;1"
+%}
diff --git a/netwerk/base/nsINetworkInterceptController.idl b/netwerk/base/nsINetworkInterceptController.idl
new file mode 100644
index 0000000000..155daa5cd5
--- /dev/null
+++ b/netwerk/base/nsINetworkInterceptController.idl
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsICacheInfoChannel;
+interface nsIChannel;
+interface nsIConsoleReportCollector;
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIURI;
+
+%{C++
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsILoadInfo.h"
+namespace mozilla {
+class TimeStamp;
+
+namespace dom {
+class ChannelInfo;
+}
+}
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+
+[ptr] native ChannelInfo(mozilla::dom::ChannelInfo);
+
+/**
+ * Interface allowing the nsIInterceptedChannel to callback when it is
+ * done reading from the body stream.
+ */
+[scriptable, uuid(51039eb6-bea0-40c7-b523-ccab56cc4fde)]
+interface nsIInterceptedBodyCallback : nsISupports
+{
+ void bodyComplete(in nsresult aRv);
+};
+
+/**
+ * Interface to allow implementors of nsINetworkInterceptController to control the behaviour
+ * of intercepted channels without tying implementation details of the interception to
+ * the actual channel. nsIInterceptedChannel is expected to be implemented by objects
+ * which do not implement nsIChannel.
+ */
+
+[scriptable, uuid(f4b82975-6a86-4cc4-87fe-9a1fd430c86d)]
+interface nsIInterceptedChannel : nsISupports
+{
+ /**
+ * Instruct a channel that has been intercepted to continue with the original
+ * network request.
+ *
+ * For our mitigations, we support this reset triggering "bypass" mode which
+ * results in the resulting client not being controlled.
+ */
+ void resetInterception(in boolean bypass);
+
+ /**
+ * Set the status and reason for the forthcoming synthesized response.
+ * Multiple calls overwrite existing values.
+ */
+ void synthesizeStatus(in uint16_t status, in ACString reason);
+
+ /**
+ * Attach a header name/value pair to the forthcoming synthesized response.
+ * Overwrites any existing header value.
+ */
+ void synthesizeHeader(in ACString name, in ACString value);
+
+ /**
+ * Instruct a channel that has been intercepted that a response is
+ * starting to be synthesized. No further header modification is allowed
+ * after this point. There are a few parameters:
+ * - A body stream may be optionally passed. If nullptr, then an
+ * empty body is assumed.
+ * - A callback may be optionally passed. It will be invoked
+ * when the body is complete. For a nullptr body this may be
+ * synchronously on the current thread. Otherwise it will be invoked
+ * asynchronously on the current thread.
+ * - A cacheInfoChannel may be optionally passed. If the body stream is
+ * from alternative data cache, this cacheInfoChannel provides needed
+ * cache information.
+ * - The caller may optionally pass a spec for a URL that this response
+ * originates from; an empty string will cause the original
+ * intercepted request's URL to be used instead.
+ * - The responseRedirected flag is false will cause the channel do an
+ * internal redirect when the original intercepted reauest's URL is
+ * different from the response's URL. The flag is true will cause the
+ * chaanel do a non-internal redirect when the URLs are different.
+ */
+ void startSynthesizedResponse(in nsIInputStream body,
+ in nsIInterceptedBodyCallback callback,
+ in nsICacheInfoChannel channel,
+ in ACString finalURLSpec,
+ in bool responseRedirected);
+
+ /**
+ * Instruct a channel that has been intercepted that response synthesis
+ * has completed and all outstanding resources can be closed.
+ */
+ void finishSynthesizedResponse();
+
+ /**
+ * Cancel the pending intercepted request.
+ * @return NS_ERROR_FAILURE if the response has already been synthesized or
+ * the original request has been instructed to continue.
+ */
+ void cancelInterception(in nsresult status);
+
+ /**
+ * The underlying channel object that was intercepted.
+ */
+ readonly attribute nsIChannel channel;
+
+ /**
+ * The URL of the underlying channel object, corrected for a potential
+ * secure upgrade.
+ */
+ readonly attribute nsIURI secureUpgradedChannelURI;
+
+ /**
+ * This method allows to override the channel info for the channel.
+ */
+ [noscript]
+ void setChannelInfo(in ChannelInfo channelInfo);
+
+ /**
+ * Get the internal load type from the underlying channel.
+ */
+ [noscript]
+ readonly attribute nsContentPolicyType internalContentPolicyType;
+
+ [noscript]
+ readonly attribute nsIConsoleReportCollector consoleReportCollector;
+
+ [noscript]
+ void SetFetchHandlerStart(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetFetchHandlerFinish(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetRemoteWorkerLaunchStart(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetRemoteWorkerLaunchEnd(in TimeStamp aTimeStamp);
+
+ /**
+ * This method indicates if the ServiceWorker Interception is reset to
+ * network or not.
+ */
+ [noscript]
+ bool GetIsReset();
+
+%{C++
+ already_AddRefed<nsIConsoleReportCollector>
+ GetConsoleReportCollector()
+ {
+ nsCOMPtr<nsIConsoleReportCollector> reporter;
+ GetConsoleReportCollector(getter_AddRefs(reporter));
+ return reporter.forget();
+ }
+
+ void
+ GetSubresourceTimeStampKey(nsIChannel* aChannel, nsACString& aKey)
+ {
+ if (!nsContentUtils::IsNonSubresourceRequest(aChannel)) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ switch(loadInfo->InternalContentPolicyType()) {
+ case nsIContentPolicy::TYPE_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE: {
+ aKey = "subresource-script"_ns;
+ break;
+ }
+ case nsIContentPolicy::TYPE_IMAGE:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON: {
+ aKey = "subresource-image"_ns;
+ break;
+ }
+ case nsIContentPolicy::TYPE_STYLESHEET:
+ case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
+ case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD: {
+ aKey = "subresource-stylesheet"_ns;
+ break;
+ }
+ default: {
+ aKey = "subresource-other"_ns;
+ break;
+ }
+ }
+ }
+ }
+
+ bool
+ IsReset()
+ {
+ bool result;
+ GetIsReset(&result);
+ return result;
+ }
+%}
+
+ /**
+ * Allow the ServiceWorkerManager to set an RAII-style object on the
+ * intercepted channel that should be released once the channel is
+ * torn down.
+ */
+ [noscript]
+ void setReleaseHandle(in nsISupports aHandle);
+};
+
+/**
+ * Interface to allow consumers to attach themselves to a channel's
+ * notification callbacks/loadgroup and determine if a given channel
+ * request should be intercepted before any network request is initiated.
+ */
+
+[scriptable, uuid(70d2b4fe-a552-48cd-8d93-1d8437a56b53)]
+interface nsINetworkInterceptController : nsISupports
+{
+ /**
+ * Returns true if a channel should avoid initiating any network
+ * requests until specifically instructed to do so.
+ *
+ * @param aURI The URI to be loaded. Note, this may differ from
+ * the channel's current URL in some cases.
+ * @param aChannel The channel that may be intercepted. It will
+ * be in the state prior to calling OnStartRequest().
+ */
+ bool shouldPrepareForIntercept(in nsIURI aURI, in nsIChannel aChannel);
+
+ /**
+ * Notification when a given intercepted channel is prepared to accept a synthesized
+ * response via the provided stream.
+ *
+ * @param aChannel the controlling interface for a channel that has been intercepted
+ */
+ void channelIntercepted(in nsIInterceptedChannel aChannel);
+};
diff --git a/netwerk/base/nsINetworkLinkService.idl b/netwerk/base/nsINetworkLinkService.idl
new file mode 100644
index 0000000000..29c7fab836
--- /dev/null
+++ b/netwerk/base/nsINetworkLinkService.idl
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsINetAddr;
+
+%{ C++
+#include "nsTArrayForwardDeclare.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+
+/**
+ * Network link status monitoring service.
+ */
+[scriptable, uuid(103e5293-77b3-4b70-af59-6e9e4a1f994a)]
+interface nsINetworkLinkService : nsISupports
+{
+ /* Link type constants */
+ const unsigned long LINK_TYPE_UNKNOWN = 0;
+ const unsigned long LINK_TYPE_ETHERNET = 1;
+ const unsigned long LINK_TYPE_USB = 2;
+ const unsigned long LINK_TYPE_WIFI = 3;
+ const unsigned long LINK_TYPE_WIMAX = 4;
+ const unsigned long LINK_TYPE_MOBILE = 9;
+
+ /**
+ * This is set to true when the system is believed to have a usable
+ * network connection.
+ *
+ * The link is only up when network connections can be established. For
+ * example, the link is down during DHCP configuration (unless there
+ * is another usable interface already configured).
+ *
+ * If the link status is not currently known, we generally assume that
+ * it is up.
+ */
+ readonly attribute boolean isLinkUp;
+
+ /**
+ * This is set to true when we believe that isLinkUp is accurate.
+ */
+ readonly attribute boolean linkStatusKnown;
+
+ /**
+ * The type of network connection.
+ */
+ readonly attribute unsigned long linkType;
+
+ /**
+ * A string uniquely identifying the current active network interfaces.
+ * Empty when there are no active network interfaces.
+ */
+ readonly attribute ACString networkID;
+
+ /**
+ * The list of DNS suffixes for the currently active network interfaces.
+ */
+ readonly attribute Array<ACString> dnsSuffixList;
+
+ /**
+ * The IPs of the DNS resolvers currently used by the platform.
+ */
+ [noscript] readonly attribute Array<NetAddr> nativeResolvers;
+
+ /**
+ * Same as previous - returns the IPs of DNS resolvers but this time as
+ * XPCOM objects usable by extensions.
+ */
+ readonly attribute Array<nsINetAddr> resolvers;
+
+ const unsigned long NONE_DETECTED = 0;
+ const unsigned long VPN_DETECTED = 1 << 0;
+ const unsigned long PROXY_DETECTED = 1 << 1;
+ const unsigned long NRPT_DETECTED = 1 << 2;
+
+ /**
+ * A bitfield that encodes the platform attributes we detected which
+ * indicate that we should only use DNS, not TRR.
+ */
+ readonly attribute unsigned long platformDNSIndications;
+};
+
+%{C++
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_NETWORK_LINK_TOPIC whenever one of isLinkUp or linkStatusKnown
+ * changes. We pass one of the NS_NETWORK_LINK_DATA_ constants below
+ * as the aData parameter of the notification.
+ */
+#define NS_NETWORK_LINK_TOPIC "network:link-status-changed"
+
+/**
+ * isLinkUp is now true, linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_UP "up"
+/**
+ * isLinkUp is now false, linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_DOWN "down"
+/**
+ * isLinkUp is still true, but the network setup is modified.
+ * linkStatusKnown is true.
+ */
+#define NS_NETWORK_LINK_DATA_CHANGED "changed"
+/**
+ * linkStatusKnown is now false.
+ */
+#define NS_NETWORK_LINK_DATA_UNKNOWN "unknown"
+
+/**
+ * network ID has changed.
+ */
+#define NS_NETWORK_ID_CHANGED_TOPIC "network:networkid-changed"
+
+/**
+ * DNS suffix list has updated.
+ */
+#define NS_DNS_SUFFIX_LIST_UPDATED_TOPIC "network:dns-suffix-list-updated"
+
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_NETWORK_LINK_TYPE_TOPIC whenever the network connection type
+ * changes. We pass one of the valid connection type constants
+ * below as the aData parameter of the notification.
+ */
+#define NS_NETWORK_LINK_TYPE_TOPIC "network:link-type-changed"
+
+/** We were unable to determine the network connection type */
+#define NS_NETWORK_LINK_TYPE_UNKNOWN "unknown"
+
+/** A standard wired ethernet connection */
+#define NS_NETWORK_LINK_TYPE_ETHERNET "ethernet"
+
+/** A connection via a USB port */
+#define NS_NETWORK_LINK_TYPE_USB "usb"
+
+/** A connection via a WiFi access point (IEEE802.11) */
+#define NS_NETWORK_LINK_TYPE_WIFI "wifi"
+
+/** A connection via WiMax (IEEE802.16) */
+#define NS_NETWORK_LINK_TYPE_WIMAX "wimax"
+
+/** A mobile connection (e.g. 2G, 3G, etc) */
+#define NS_NETWORK_LINK_TYPE_MOBILE "mobile"
+%}
diff --git a/netwerk/base/nsINetworkPredictor.idl b/netwerk/base/nsINetworkPredictor.idl
new file mode 100644
index 0000000000..07f88cba8d
--- /dev/null
+++ b/netwerk/base/nsINetworkPredictor.idl
@@ -0,0 +1,194 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsINetworkPredictorVerifier;
+
+webidl Document;
+
+typedef unsigned long PredictorPredictReason;
+typedef unsigned long PredictorLearnReason;
+
+%{C++
+namespace mozilla {
+
+class OriginAttributes;
+
+}
+%}
+
+[ref] native OriginAttributes(const mozilla::OriginAttributes);
+
+/**
+ * nsINetworkPredictor - learn about pages users visit, and allow us to take
+ * predictive actions upon future visits.
+ * NOTE: nsINetworkPredictor should only
+ * be used on the main thread.
+ */
+[scriptable, builtinclass, uuid(acc88e7c-3f39-42c7-ac31-6377c2c3d73e)]
+interface nsINetworkPredictor : nsISupports
+{
+ /**
+ * Prediction reasons
+ *
+ * PREDICT_LINK - we are being asked to take predictive action because
+ * the user is hovering over a link.
+ *
+ * PREDICT_LOAD - we are being asked to take predictive action because
+ * the user has initiated a pageload.
+ *
+ * PREDICT_STARTUP - we are being asked to take predictive action
+ * because the browser is starting up.
+ */
+ const PredictorPredictReason PREDICT_LINK = 0;
+ const PredictorPredictReason PREDICT_LOAD = 1;
+ const PredictorPredictReason PREDICT_STARTUP = 2;
+
+ /**
+ * Start taking predictive actions
+ *
+ * Calling this will cause the predictor to (possibly) start
+ * taking actions such as DNS prefetch and/or TCP preconnect based on
+ * (1) the host name that we are given, and (2) the reason we are being
+ * asked to take actions.
+ *
+ * @param targetURI - The URI we are being asked to take actions based on.
+ * @param sourceURI - The URI that is currently loaded. This is so we can
+ * avoid doing predictive actions for link hover on an HTTPS page (for
+ * example).
+ * @param reason - The reason we are being asked to take actions. Can be
+ * any of the PREDICT_* values above.
+ * In the case of PREDICT_LINK, targetURI should be the URI of the link
+ * that is being hovered over, and sourceURI should be the URI of the page
+ * on which the link appears.
+ * In the case of PREDICT_LOAD, targetURI should be the URI of the page that
+ * is being loaded and sourceURI should be null.
+ * In the case of PREDICT_STARTUP, both targetURI and sourceURI should be
+ * null.
+ * @param originAttributes - The originAttributes of the page load we are
+ * predicting about.
+ * @param verifier - An nsINetworkPredictorVerifier used in testing to ensure
+ * we're predicting the way we expect to. Not necessary (or desired) for
+ * normal operation.
+ */
+ [implicit_jscontext]
+ void predict(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorPredictReason reason,
+ in jsval originAttributes,
+ in nsINetworkPredictorVerifier verifier);
+
+ [notxpcom]
+ nsresult predictNative(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorPredictReason reason,
+ in OriginAttributes originAttributes,
+ in nsINetworkPredictorVerifier verifier);
+
+
+ /*
+ * Reasons we are learning something
+ *
+ * LEARN_LOAD_TOPLEVEL - we are learning about the toplevel resource of a
+ * pageload (NOTE: this should ONLY be used by tests)
+ *
+ * LEARN_LOAD_SUBRESOURCE - we are learning a subresource from a pageload
+ *
+ * LEARN_LOAD_REDIRECT - we are learning about the re-direct of a URI
+ *
+ * LEARN_STARTUP - we are learning about a page loaded during startup
+ */
+ const PredictorLearnReason LEARN_LOAD_TOPLEVEL = 0;
+ const PredictorLearnReason LEARN_LOAD_SUBRESOURCE = 1;
+ const PredictorLearnReason LEARN_LOAD_REDIRECT = 2;
+ const PredictorLearnReason LEARN_STARTUP = 3;
+
+ /**
+ * Add to our compendium of knowledge
+ *
+ * This adds to our prediction database to make things (hopefully)
+ * smarter next time we predict something.
+ *
+ * @param targetURI - The URI that was loaded that we are keeping track of.
+ * @param sourceURI - The URI that caused targetURI to be loaded (for page
+ * loads). This means the DOCUMENT URI.
+ * @param reason - The reason we are learning this bit of knowledge.
+ * Reason can be any of the LEARN_* values.
+ * In the case of LEARN_LOAD_SUBRESOURCE, targetURI should be the URI of a
+ * subresource of a page, and sourceURI should be the top-level URI.
+ * In the case of LEARN_LOAD_REDIRECT, targetURI is the NEW URI of a
+ * top-level resource that was redirected to, and sourceURI is the
+ * ORIGINAL URI of said top-level resource.
+ * In the case of LEARN_STARTUP, targetURI should be the URI of a page
+ * that was loaded immediately after browser startup, and sourceURI should
+ * be null.
+ * @param originAttributes - The originAttributes for the page load that we
+ * are learning about.
+ */
+ [implicit_jscontext]
+ void learn(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorLearnReason reason,
+ in jsval originAttributes);
+
+ [notxpcom]
+ nsresult learnNative(in nsIURI targetURI,
+ in nsIURI sourceURI,
+ in PredictorLearnReason reason,
+ in OriginAttributes originAttributes);
+
+ /**
+ * Clear out all our learned knowledge
+ *
+ * This removes everything from our database so that any predictions begun
+ * after this completes will start from a blank slate.
+ */
+ void reset();
+};
+
+%{C++
+// Wrapper functions to make use of the predictor easier and less invasive
+class nsIChannel;
+
+class nsILoadContext;
+class nsILoadGroup;
+class nsINetworkPredictorVerifier;
+
+namespace mozilla {
+
+class OriginAttributes;
+
+namespace net {
+
+nsresult PredictorPredict(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorPredictReason reason,
+ const OriginAttributes& originAttributes,
+ nsINetworkPredictorVerifier *verifier);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ const OriginAttributes& originAttributes);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ nsILoadGroup *loadGroup);
+
+nsresult PredictorLearn(nsIURI *targetURI,
+ nsIURI *sourceURI,
+ PredictorLearnReason reason,
+ dom::Document *document);
+
+nsresult PredictorLearnRedirect(nsIURI *targetURI,
+ nsIChannel *channel,
+ const OriginAttributes& originAttributes);
+
+} // mozilla::net
+} // mozilla
+%}
diff --git a/netwerk/base/nsINetworkPredictorVerifier.idl b/netwerk/base/nsINetworkPredictorVerifier.idl
new file mode 100644
index 0000000000..b00aecc076
--- /dev/null
+++ b/netwerk/base/nsINetworkPredictorVerifier.idl
@@ -0,0 +1,40 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * nsINetworkPredictorVerifier - used for testing the network predictor to
+ * ensure it does what we expect it to do.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+[scriptable, uuid(2e43bb32-dabf-4494-9f90-2b3195b1c73d)]
+interface nsINetworkPredictorVerifier : nsISupports
+{
+ /**
+ * Callback for when we do a predictive prefetch
+ *
+ * @param uri - The URI that was prefetched
+ * @param status - The request status code returned by the
+ * prefetch attempt e.g. 200 (OK):w
+ */
+ void onPredictPrefetch(in nsIURI uri, in uint32_t status);
+
+ /**
+ * Callback for when we do a predictive preconnect
+ *
+ * @param uri - The URI that was preconnected to
+ */
+ void onPredictPreconnect(in nsIURI uri);
+
+ /**
+ * Callback for when we do a predictive DNS lookup
+ *
+ * @param uri - The URI that was looked up
+ */
+ void onPredictDNS(in nsIURI uri);
+};
diff --git a/netwerk/base/nsINullChannel.idl b/netwerk/base/nsINullChannel.idl
new file mode 100644
index 0000000000..6c03a2743b
--- /dev/null
+++ b/netwerk/base/nsINullChannel.idl
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface is only used in order to mark the fact that
+ * an object isn't a complete implementation of its interfaces.
+ * For example, a consumer can QI NullHttpChannel to nsINullChannel,
+ * to determine if the object is just a dummy implementation of nsIHttpChannel.
+ */
+[scriptable, builtinclass, uuid(4610b901-df41-4bb4-bd3f-fd4d6b6d8d68)]
+interface nsINullChannel : nsISupports
+{
+};
diff --git a/netwerk/base/nsIOService.cpp b/netwerk/base/nsIOService.cpp
new file mode 100644
index 0000000000..8450a59b4a
--- /dev/null
+++ b/netwerk/base/nsIOService.cpp
@@ -0,0 +1,2194 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 cindent et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsIOService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIFileProtocolHandler.h"
+#include "nscore.h"
+#include "nsIURI.h"
+#include "prprf.h"
+#include "netCore.h"
+#include "nsIObserverService.h"
+#include "nsXPCOM.h"
+#include "nsIProxiedProtocolHandler.h"
+#include "nsIProxyInfo.h"
+#include "nsDNSService2.h"
+#include "nsEscape.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsSimpleNestedURI.h"
+#include "nsSocketTransport2.h"
+#include "nsTArray.h"
+#include "nsIConsoleService.h"
+#include "nsIUploadChannel2.h"
+#include "nsXULAppAPI.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsICancelable.h"
+#include "nsINetworkLinkService.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsURLHelper.h"
+#include "nsIProtocolProxyService2.h"
+#include "MainThreadUtils.h"
+#include "nsINode.h"
+#include "nsIWebTransport.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "WebTransportSessionProxy.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/net/CaptivePortalService.h"
+#include "mozilla/net/NetworkConnectivityService.h"
+#include "mozilla/net/SocketProcessHost.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/net/SSLTokensCache.h"
+#include "mozilla/Unused.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "nsNSSComponent.h"
+#include "ssl.h"
+#include "StaticComponents.h"
+
+namespace mozilla {
+namespace net {
+
+using mozilla::Maybe;
+using mozilla::dom::ClientInfo;
+using mozilla::dom::ServiceWorkerDescriptor;
+
+#define PORT_PREF_PREFIX "network.security.ports."
+#define PORT_PREF(x) PORT_PREF_PREFIX x
+#define MANAGE_OFFLINE_STATUS_PREF "network.manage-offline-status"
+
+// Nb: these have been misnomers since bug 715770 removed the buffer cache.
+// "network.segment.count" and "network.segment.size" would be better names,
+// but the old names are still used to preserve backward compatibility.
+#define NECKO_BUFFER_CACHE_COUNT_PREF "network.buffer.cache.count"
+#define NECKO_BUFFER_CACHE_SIZE_PREF "network.buffer.cache.size"
+#define NETWORK_CAPTIVE_PORTAL_PREF "network.captive-portal-service.enabled"
+#define WEBRTC_PREF_PREFIX "media.peerconnection."
+#define NETWORK_DNS_PREF "network.dns."
+#define FORCE_EXTERNAL_PREF_PREFIX "network.protocol-handler.external."
+
+nsIOService* gIOService;
+static bool gHasWarnedUploadChannel2;
+static bool gCaptivePortalEnabled = false;
+static LazyLogModule gIOServiceLog("nsIOService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gIOServiceLog, LogLevel::Debug, args)
+
+// A general port blacklist. Connections to these ports will not be allowed
+// unless the protocol overrides.
+//
+// This list is to be kept in sync with "bad ports" as defined in the
+// WHATWG Fetch standard at <https://fetch.spec.whatwg.org/#port-blocking>
+
+int16_t gBadPortList[] = {
+ 1, // tcpmux
+ 7, // echo
+ 9, // discard
+ 11, // systat
+ 13, // daytime
+ 15, // netstat
+ 17, // qotd
+ 19, // chargen
+ 20, // ftp-data
+ 21, // ftp
+ 22, // ssh
+ 23, // telnet
+ 25, // smtp
+ 37, // time
+ 42, // name
+ 43, // nicname
+ 53, // domain
+ 69, // tftp
+ 77, // priv-rjs
+ 79, // finger
+ 87, // ttylink
+ 95, // supdup
+ 101, // hostriame
+ 102, // iso-tsap
+ 103, // gppitnp
+ 104, // acr-nema
+ 109, // pop2
+ 110, // pop3
+ 111, // sunrpc
+ 113, // auth
+ 115, // sftp
+ 117, // uucp-path
+ 119, // nntp
+ 123, // ntp
+ 135, // loc-srv / epmap
+ 137, // netbios
+ 139, // netbios
+ 143, // imap2
+ 161, // snmp
+ 179, // bgp
+ 389, // ldap
+ 427, // afp (alternate)
+ 465, // smtp (alternate)
+ 512, // print / exec
+ 513, // login
+ 514, // shell
+ 515, // printer
+ 526, // tempo
+ 530, // courier
+ 531, // chat
+ 532, // netnews
+ 540, // uucp
+ 548, // afp
+ 554, // rtsp
+ 556, // remotefs
+ 563, // nntp+ssl
+ 587, // smtp (outgoing)
+ 601, // syslog-conn
+ 636, // ldap+ssl
+ 989, // ftps-data
+ 990, // ftps
+ 993, // imap+ssl
+ 995, // pop3+ssl
+ 1719, // h323gatestat
+ 1720, // h323hostcall
+ 1723, // pptp
+ 2049, // nfs
+ 3659, // apple-sasl
+ 4045, // lockd
+ 5060, // sip
+ 5061, // sips
+ 6000, // x11
+ 6566, // sane-port
+ 6665, // irc (alternate)
+ 6666, // irc (alternate)
+ 6667, // irc (default)
+ 6668, // irc (alternate)
+ 6669, // irc (alternate)
+ 6697, // irc+tls
+ 10080, // amanda
+ 0, // Sentinel value: This MUST be zero
+};
+
+static const char kProfileChangeNetTeardownTopic[] =
+ "profile-change-net-teardown";
+static const char kProfileChangeNetRestoreTopic[] =
+ "profile-change-net-restore";
+static const char kProfileDoChange[] = "profile-do-change";
+
+// Necko buffer defaults
+uint32_t nsIOService::gDefaultSegmentSize = 4096;
+uint32_t nsIOService::gDefaultSegmentCount = 24;
+
+uint32_t nsIOService::sSocketProcessCrashedCount = 0;
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsIOService::nsIOService()
+ : mLastOfflineStateChange(PR_IntervalNow()),
+ mLastConnectivityChange(PR_IntervalNow()),
+ mLastNetworkLinkChange(PR_IntervalNow()) {}
+
+static const char* gCallbackPrefs[] = {
+ PORT_PREF_PREFIX,
+ MANAGE_OFFLINE_STATUS_PREF,
+ NECKO_BUFFER_CACHE_COUNT_PREF,
+ NECKO_BUFFER_CACHE_SIZE_PREF,
+ NETWORK_CAPTIVE_PORTAL_PREF,
+ FORCE_EXTERNAL_PREF_PREFIX,
+ nullptr,
+};
+
+static const char* gCallbackPrefsForSocketProcess[] = {
+ WEBRTC_PREF_PREFIX,
+ NETWORK_DNS_PREF,
+ "network.send_ODA_to_content_directly",
+ "network.trr.",
+ "doh-rollout.",
+ "network.dns.disableIPv6",
+ "network.offline-mirrors-connectivity",
+ "network.disable-localhost-when-offline",
+ "network.proxy.parse_pac_on_socket_process",
+ "network.proxy.allow_hijacking_localhost",
+ "network.connectivity-service.",
+ "network.captive-portal-service.testMode",
+ nullptr,
+};
+
+static const char* gCallbackSecurityPrefs[] = {
+ // Note the prefs listed below should be in sync with the code in
+ // HandleTLSPrefChange().
+ "security.tls.version.min",
+ "security.tls.version.max",
+ "security.tls.version.enable-deprecated",
+ "security.tls.hello_downgrade_check",
+ "security.ssl.require_safe_negotiation",
+ "security.ssl.enable_false_start",
+ "security.ssl.enable_alpn",
+ "security.tls.enable_0rtt_data",
+ "security.ssl.disable_session_identifiers",
+ "security.tls.enable_post_handshake_auth",
+ "security.tls.enable_delegated_credentials",
+ // Note the prefs listed below should be in sync with the code in
+ // SetValidationOptionsCommon().
+ "security.ssl.enable_ocsp_stapling",
+ "security.ssl.enable_ocsp_must_staple",
+ "security.pki.certificate_transparency.mode",
+ nullptr,
+};
+
+nsresult nsIOService::Init() {
+ SSLTokensCache::Init();
+
+ InitializeCaptivePortalService();
+
+ // setup our bad port list stuff
+ for (int i = 0; gBadPortList[i]; i++) {
+ // We can't be accessed by another thread yet
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ mRestrictedPortList.AppendElement(gBadPortList[i]);
+ MOZ_POP_THREAD_SAFETY
+ }
+
+ // Further modifications to the port list come from prefs
+ Preferences::RegisterPrefixCallbacks(nsIOService::PrefsChanged,
+ gCallbackPrefs, this);
+ PrefsChanged();
+
+ mSocketProcessTopicBlockedList.Insert(
+ nsLiteralCString(NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID));
+ mSocketProcessTopicBlockedList.Insert(
+ nsLiteralCString(NS_XPCOM_SHUTDOWN_OBSERVER_ID));
+ mSocketProcessTopicBlockedList.Insert("xpcom-shutdown-threads"_ns);
+ mSocketProcessTopicBlockedList.Insert("profile-do-change"_ns);
+ mSocketProcessTopicBlockedList.Insert("network:socket-process-crashed"_ns);
+
+ // Register for profile change notifications
+ mObserverService = services::GetObserverService();
+ AddObserver(this, kProfileChangeNetTeardownTopic, true);
+ AddObserver(this, kProfileChangeNetRestoreTopic, true);
+ AddObserver(this, kProfileDoChange, true);
+ AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
+ AddObserver(this, NS_NETWORK_ID_CHANGED_TOPIC, true);
+ AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
+
+ // Register observers for sending notifications to nsSocketTransportService
+ if (XRE_IsParentProcess()) {
+ AddObserver(this, "profile-initial-state", true);
+ AddObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC, true);
+ }
+
+ if (IsSocketProcessChild()) {
+ Preferences::RegisterCallbacks(nsIOService::OnTLSPrefChange,
+ gCallbackSecurityPrefs, this);
+ }
+
+ gIOService = this;
+
+ InitializeNetworkLinkService();
+ InitializeProtocolProxyService();
+
+ SetOffline(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::AddObserver(nsIObserver* aObserver, const char* aTopic,
+ bool aOwnsWeak) {
+ if (!mObserverService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Register for the origional observer.
+ nsresult rv = mObserverService->AddObserver(aObserver, aTopic, aOwnsWeak);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ return NS_OK;
+ }
+
+ nsAutoCString topic(aTopic);
+ // This happens when AddObserver() is called by nsIOService::Init(). We don't
+ // want to add nsIOService again.
+ if (SameCOMIdentity(aObserver, static_cast<nsIObserver*>(this))) {
+ mIOServiceTopicList.Insert(topic);
+ return NS_OK;
+ }
+
+ if (!UseSocketProcess()) {
+ return NS_OK;
+ }
+
+ if (mSocketProcessTopicBlockedList.Contains(topic)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Avoid registering duplicate topics.
+ if (mObserverTopicForSocketProcess.Contains(topic)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mObserverTopicForSocketProcess.Insert(topic);
+
+ // Avoid registering duplicate topics.
+ if (mIOServiceTopicList.Contains(topic)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mObserverService->AddObserver(this, aTopic, true);
+}
+
+NS_IMETHODIMP
+nsIOService::RemoveObserver(nsIObserver* aObserver, const char* aTopic) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIOService::EnumerateObservers(const char* aTopic,
+ nsISimpleEnumerator** anEnumerator) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsIOService::NotifyObservers(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aSomeData) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsIOService::~nsIOService() {
+ if (gIOService) {
+ MOZ_ASSERT(gIOService == this);
+ gIOService = nullptr;
+ }
+}
+
+// static
+void nsIOService::OnTLSPrefChange(const char* aPref, void* aSelf) {
+ MOZ_ASSERT(IsSocketProcessChild());
+
+ if (!EnsureNSSInitializedChromeOrContent()) {
+ LOG(("NSS not initialized."));
+ return;
+ }
+
+ nsAutoCString pref(aPref);
+ // The preferences listed in gCallbackSecurityPrefs need to be in sync with
+ // the code in HandleTLSPrefChange() and SetValidationOptionsCommon().
+ if (HandleTLSPrefChange(pref)) {
+ LOG(("HandleTLSPrefChange done"));
+ } else if (pref.EqualsLiteral("security.ssl.enable_ocsp_stapling") ||
+ pref.EqualsLiteral("security.ssl.enable_ocsp_must_staple") ||
+ pref.EqualsLiteral("security.pki.certificate_transparency.mode")) {
+ SetValidationOptionsCommon();
+ }
+}
+
+nsresult nsIOService::InitializeCaptivePortalService() {
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ // We only initalize a captive portal service in the main process
+ return NS_OK;
+ }
+
+ mCaptivePortalService = do_GetService(NS_CAPTIVEPORTAL_CID);
+ if (mCaptivePortalService) {
+ return static_cast<CaptivePortalService*>(mCaptivePortalService.get())
+ ->Initialize();
+ }
+
+ // Instantiate and initialize the service
+ RefPtr<NetworkConnectivityService> ncs =
+ NetworkConnectivityService::GetSingleton();
+
+ return NS_OK;
+}
+
+nsresult nsIOService::InitializeSocketTransportService() {
+ nsresult rv = NS_OK;
+
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ LOG(
+ ("nsIOService aborting InitializeSocketTransportService because of app "
+ "shutdown"));
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ if (!mSocketTransportService) {
+ mSocketTransportService =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to get socket transport service");
+ }
+ }
+
+ if (mSocketTransportService) {
+ rv = mSocketTransportService->Init();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service init failed");
+ mSocketTransportService->SetOffline(false);
+ }
+
+ return rv;
+}
+
+nsresult nsIOService::InitializeNetworkLinkService() {
+ nsresult rv = NS_OK;
+
+ if (mNetworkLinkServiceInitialized) return rv;
+
+ if (!NS_IsMainThread()) {
+ NS_WARNING("Network link service should be created on main thread");
+ return NS_ERROR_FAILURE;
+ }
+
+ // go into managed mode if we can, and chrome process
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mNetworkLinkService = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+
+ if (mNetworkLinkService) {
+ mNetworkLinkServiceInitialized = true;
+ }
+
+ // After initializing the networkLinkService, query the connectivity state
+ OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
+
+ return rv;
+}
+
+nsresult nsIOService::InitializeProtocolProxyService() {
+ nsresult rv = NS_OK;
+
+ if (XRE_IsParentProcess()) {
+ // for early-initialization
+ Unused << do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ }
+
+ return rv;
+}
+
+already_AddRefed<nsIOService> nsIOService::GetInstance() {
+ if (!gIOService) {
+ RefPtr<nsIOService> ios = new nsIOService();
+ if (NS_SUCCEEDED(ios->Init())) {
+ MOZ_ASSERT(gIOService == ios.get());
+ return ios.forget();
+ }
+ }
+ return do_AddRef(gIOService);
+}
+
+class SocketProcessListenerProxy : public SocketProcessHost::Listener {
+ public:
+ SocketProcessListenerProxy() = default;
+ void OnProcessLaunchComplete(SocketProcessHost* aHost, bool aSucceeded) {
+ if (!gIOService) {
+ return;
+ }
+
+ gIOService->OnProcessLaunchComplete(aHost, aSucceeded);
+ }
+
+ void OnProcessUnexpectedShutdown(SocketProcessHost* aHost) {
+ if (!gIOService) {
+ return;
+ }
+
+ gIOService->OnProcessUnexpectedShutdown(aHost);
+ }
+};
+
+// static
+bool nsIOService::TooManySocketProcessCrash() {
+ return sSocketProcessCrashedCount >=
+ StaticPrefs::network_max_socket_process_failed_count();
+}
+
+// static
+void nsIOService::IncreaseSocketProcessCrashCount() {
+ MOZ_ASSERT(IsNeckoChild());
+ sSocketProcessCrashedCount++;
+}
+
+nsresult nsIOService::LaunchSocketProcess() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ return NS_OK;
+ }
+
+ // We shouldn't launch socket prcess when shutdown begins.
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ return NS_OK;
+ }
+
+ if (mSocketProcess) {
+ return NS_OK;
+ }
+
+ if (PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS")) {
+ LOG(("nsIOService skipping LaunchSocketProcess because of the env"));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_process_enabled()) {
+ LOG(("nsIOService skipping LaunchSocketProcess because of the pref"));
+ return NS_OK;
+ }
+
+ Preferences::RegisterPrefixCallbacks(
+ nsIOService::NotifySocketProcessPrefsChanged,
+ gCallbackPrefsForSocketProcess, this);
+
+ // The subprocess is launched asynchronously, so we wait for a callback to
+ // acquire the IPDL actor.
+ mSocketProcess = new SocketProcessHost(new SocketProcessListenerProxy());
+ LOG(("nsIOService::LaunchSocketProcess"));
+ if (!mSocketProcess->Launch()) {
+ NS_WARNING("Failed to launch socket process!!");
+ DestroySocketProcess();
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void nsIOService::DestroySocketProcess() {
+ LOG(("nsIOService::DestroySocketProcess"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default || !mSocketProcess) {
+ return;
+ }
+
+ Preferences::UnregisterPrefixCallbacks(
+ nsIOService::NotifySocketProcessPrefsChanged,
+ gCallbackPrefsForSocketProcess, this);
+
+ mSocketProcess->Shutdown();
+ mSocketProcess = nullptr;
+}
+
+bool nsIOService::SocketProcessReady() {
+ return mSocketProcess && mSocketProcess->IsConnected();
+}
+
+static bool sUseSocketProcess = false;
+static bool sUseSocketProcessChecked = false;
+
+// static
+bool nsIOService::UseSocketProcess(bool aCheckAgain) {
+ if (sUseSocketProcessChecked && !aCheckAgain) {
+ return sUseSocketProcess;
+ }
+
+ sUseSocketProcessChecked = true;
+ sUseSocketProcess = false;
+
+ if (PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS")) {
+ return sUseSocketProcess;
+ }
+
+ if (TooManySocketProcessCrash()) {
+ LOG(("TooManySocketProcessCrash"));
+ return sUseSocketProcess;
+ }
+
+ if (PR_GetEnv("MOZ_FORCE_USE_SOCKET_PROCESS")) {
+ sUseSocketProcess = true;
+ return sUseSocketProcess;
+ }
+
+ if (StaticPrefs::network_process_enabled()) {
+ sUseSocketProcess =
+ StaticPrefs::network_http_network_access_on_socket_process_enabled();
+ }
+ return sUseSocketProcess;
+}
+
+// static
+void nsIOService::NotifySocketProcessPrefsChanged(const char* aName,
+ void* aSelf) {
+ static_cast<nsIOService*>(aSelf)->NotifySocketProcessPrefsChanged(aName);
+}
+
+void nsIOService::NotifySocketProcessPrefsChanged(const char* aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (!StaticPrefs::network_process_enabled()) {
+ return;
+ }
+
+ dom::Pref pref(nsCString(aName), /* isLocked */ false,
+ /* isSanitized */ false, Nothing(), Nothing());
+
+ Preferences::GetPreference(&pref, GeckoProcessType_Socket,
+ /* remoteType */ ""_ns);
+ auto sendPrefUpdate = [pref]() {
+ Unused << gIOService->mSocketProcess->GetActor()->SendPreferenceUpdate(
+ pref);
+ };
+ CallOrWaitForSocketProcess(sendPrefUpdate);
+}
+
+void nsIOService::OnProcessLaunchComplete(SocketProcessHost* aHost,
+ bool aSucceeded) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("nsIOService::OnProcessLaunchComplete aSucceeded=%d\n", aSucceeded));
+
+ mSocketProcessLaunchComplete = aSucceeded;
+
+ if (mShutdown || !SocketProcessReady() || !aSucceeded) {
+ mPendingEvents.Clear();
+ return;
+ }
+
+ if (!mPendingEvents.IsEmpty()) {
+ nsTArray<std::function<void()>> pendingEvents = std::move(mPendingEvents);
+ for (auto& func : pendingEvents) {
+ func();
+ }
+ }
+}
+
+void nsIOService::CallOrWaitForSocketProcess(
+ const std::function<void()>& aFunc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (IsSocketProcessLaunchComplete() && SocketProcessReady()) {
+ aFunc();
+ } else {
+ mPendingEvents.AppendElement(aFunc); // infallible
+ LaunchSocketProcess();
+ }
+}
+
+int32_t nsIOService::SocketProcessPid() {
+ if (!mSocketProcess) {
+ return 0;
+ }
+ if (SocketProcessParent* actor = mSocketProcess->GetActor()) {
+ return (int32_t)actor->OtherPid();
+ }
+ return 0;
+}
+
+bool nsIOService::IsSocketProcessLaunchComplete() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mSocketProcessLaunchComplete;
+}
+
+void nsIOService::OnProcessUnexpectedShutdown(SocketProcessHost* aHost) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("nsIOService::OnProcessUnexpectedShutdown\n"));
+ DestroySocketProcess();
+ mPendingEvents.Clear();
+
+ // Nothing to do if socket process was not used before.
+ if (!UseSocketProcess()) {
+ return;
+ }
+
+ sSocketProcessCrashedCount++;
+ if (TooManySocketProcessCrash()) {
+ sUseSocketProcessChecked = false;
+ DNSServiceWrapper::SwitchToBackupDNSService();
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ (void)observerService->NotifyObservers(
+ nullptr, "network:socket-process-crashed", nullptr);
+ }
+
+ // UseSocketProcess() could return false if we have too many crashes, so we
+ // should call it again.
+ if (UseSocketProcess()) {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
+ NewRunnableMethod("nsIOService::LaunchSocketProcess", this,
+ &nsIOService::LaunchSocketProcess)));
+ }
+}
+
+RefPtr<MemoryReportingProcess> nsIOService::GetSocketProcessMemoryReporter() {
+ // Check the prefs here again, since we don't want to create
+ // SocketProcessMemoryReporter for some tests.
+ if (!StaticPrefs::network_process_enabled() || !SocketProcessReady()) {
+ return nullptr;
+ }
+
+ return new SocketProcessMemoryReporter();
+}
+
+NS_IMETHODIMP
+nsIOService::SocketProcessTelemetryPing() {
+ CallOrWaitForSocketProcess([]() {
+ Unused << gIOService->mSocketProcess->GetActor()
+ ->SendSocketProcessTelemetryPing();
+ });
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsIOService, nsIIOService, nsINetUtil, nsISpeculativeConnect,
+ nsIObserver, nsIIOServiceInternal, nsISupportsWeakReference,
+ nsIObserverService)
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsIOService::RecheckCaptivePortal() {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ if (!mCaptivePortalService) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIRunnable> task = NewRunnableMethod(
+ "nsIOService::RecheckCaptivePortal", mCaptivePortalService,
+ &nsICaptivePortalService::RecheckCaptivePortal);
+ return NS_DispatchToMainThread(task);
+}
+
+nsresult nsIOService::RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan) {
+ nsresult rv;
+
+ if (!mCaptivePortalService) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = newChan->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ NetAddr addr;
+ // If the redirect wasn't to an IP literal, so there's probably no need
+ // to trigger the captive portal detection right now. It can wait.
+ if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrLocal()) {
+ RecheckCaptivePortal();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsIOService::AsyncOnChannelRedirect(
+ nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsAsyncRedirectVerifyHelper* helper) {
+ // If a redirect to a local network address occurs, then chances are we
+ // are in a captive portal, so we trigger a recheck.
+ RecheckCaptivePortalIfLocalRedirect(newChan);
+
+ // This is silly. I wish there was a simpler way to get at the global
+ // reference of the contentSecurityManager. But it lives in the XPCOM
+ // service registry.
+ nsCOMPtr<nsIChannelEventSink> sink =
+ do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
+ if (sink) {
+ nsresult rv =
+ helper->DelegateOnChannelRedirect(sink, oldChan, newChan, flags);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Finally, our category
+ nsCOMArray<nsIChannelEventSink> entries;
+ mChannelEventSinks.GetEntries(entries);
+ int32_t len = entries.Count();
+ for (int32_t i = 0; i < len; ++i) {
+ nsresult rv =
+ helper->DelegateOnChannelRedirect(entries[i], oldChan, newChan, flags);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(oldChan));
+
+ // Collect the redirection from HTTP(S) only.
+ if (httpChan) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIURI> newURI;
+ newChan->GetURI(getter_AddRefs(newURI));
+ MOZ_ASSERT(newURI);
+
+ nsAutoCString scheme;
+ newURI->GetScheme(scheme);
+ MOZ_ASSERT(!scheme.IsEmpty());
+
+ Telemetry::AccumulateCategoricalKeyed(
+ scheme,
+ oldChan->IsDocument()
+ ? Telemetry::LABELS_NETWORK_HTTP_REDIRECT_TO_SCHEME::topLevel
+ : Telemetry::LABELS_NETWORK_HTTP_REDIRECT_TO_SCHEME::subresource);
+ }
+
+ return NS_OK;
+}
+
+bool nsIOService::UsesExternalProtocolHandler(const nsACString& aScheme) {
+ if (aScheme == "file"_ns || aScheme == "chrome"_ns ||
+ aScheme == "resource"_ns) {
+ // Don't allow file:, chrome: or resource: URIs to be handled with
+ // nsExternalProtocolHandler, since internally we rely on being able to
+ // use and read from these URIs.
+ return false;
+ }
+
+ if (aScheme == "place"_ns || aScheme == "fake-favicon-uri"_ns ||
+ aScheme == "favicon"_ns || aScheme == "moz-nullprincipal"_ns) {
+ // Force place: fake-favicon-uri: favicon: and moz-nullprincipal: URIs to be
+ // handled with nsExternalProtocolHandler, and not with a dynamically
+ // registered handler.
+ return true;
+ }
+
+ // If prefs configure the URI to be handled externally, do so.
+ for (const auto& scheme : mForceExternalSchemes) {
+ if (aScheme == scheme) {
+ return true;
+ }
+ }
+ return false;
+}
+
+ProtocolHandlerInfo nsIOService::LookupProtocolHandler(
+ const nsACString& aScheme) {
+ // Look-ups are ASCII-case-insensitive, so lower-case the string before
+ // continuing.
+ nsAutoCString scheme(aScheme);
+ ToLowerCase(scheme);
+
+ // NOTE: If we could get rid of mForceExternalSchemes (or prevent them from
+ // disabling static protocols), we could avoid locking mLock until we need to
+ // check `mRuntimeProtocolHandlers.
+ AutoReadLock lock(mLock);
+ if (!UsesExternalProtocolHandler(scheme)) {
+ // Try the static protocol handler first - they cannot be overridden by
+ // dynamic protocols.
+ if (const xpcom::StaticProtocolHandler* handler =
+ xpcom::StaticProtocolHandler::Lookup(scheme)) {
+ return ProtocolHandlerInfo(*handler);
+ }
+ if (auto handler = mRuntimeProtocolHandlers.Lookup(scheme)) {
+ return ProtocolHandlerInfo(handler.Data());
+ }
+ }
+ return ProtocolHandlerInfo(xpcom::StaticProtocolHandler::Default());
+}
+
+NS_IMETHODIMP
+nsIOService::GetProtocolHandler(const char* scheme,
+ nsIProtocolHandler** result) {
+ AssertIsOnMainThread();
+ NS_ENSURE_ARG_POINTER(scheme);
+
+ *result = LookupProtocolHandler(nsDependentCString(scheme)).Handler().take();
+ return *result ? NS_OK : NS_ERROR_UNKNOWN_PROTOCOL;
+}
+
+NS_IMETHODIMP
+nsIOService::ExtractScheme(const nsACString& inURI, nsACString& scheme) {
+ return net_ExtractURLScheme(inURI, scheme);
+}
+
+NS_IMETHODIMP
+nsIOService::HostnameIsLocalIPAddress(nsIURI* aURI, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
+ NS_ENSURE_ARG_POINTER(innerURI);
+
+ nsAutoCString host;
+ nsresult rv = innerURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aResult = false;
+
+ NetAddr addr;
+ if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrLocal()) {
+ *aResult = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::HostnameIsSharedIPAddress(nsIURI* aURI, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
+ NS_ENSURE_ARG_POINTER(innerURI);
+
+ nsAutoCString host;
+ nsresult rv = innerURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aResult = false;
+
+ NetAddr addr;
+ if (NS_SUCCEEDED(addr.InitFromString(host)) && addr.IsIPAddrShared()) {
+ *aResult = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::IsValidHostname(const nsACString& inHostname, bool* aResult) {
+ *aResult = net_IsValidHostName(inHostname);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetProtocolFlags(const char* scheme, uint32_t* flags) {
+ NS_ENSURE_ARG_POINTER(scheme);
+
+ *flags =
+ LookupProtocolHandler(nsDependentCString(scheme)).StaticProtocolFlags();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetDynamicProtocolFlags(nsIURI* uri, uint32_t* flags) {
+ AssertIsOnMainThread();
+ NS_ENSURE_ARG(uri);
+
+ nsAutoCString scheme;
+ nsresult rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return LookupProtocolHandler(scheme).DynamicProtocolFlags(uri, flags);
+}
+
+NS_IMETHODIMP
+nsIOService::GetDefaultPort(const char* scheme, int32_t* defaultPort) {
+ NS_ENSURE_ARG_POINTER(scheme);
+
+ *defaultPort =
+ LookupProtocolHandler(nsDependentCString(scheme)).DefaultPort();
+ return NS_OK;
+}
+
+nsresult nsIOService::NewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** result) {
+ return NS_NewURI(result, aSpec, aCharset, aBaseURI);
+}
+
+NS_IMETHODIMP
+nsIOService::NewFileURI(nsIFile* file, nsIURI** result) {
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(file);
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+
+ rv = GetProtocolHandler("file", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler(do_QueryInterface(handler, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ return fileHandler->NewFileURI(file, result);
+}
+
+// static
+already_AddRefed<nsIURI> nsIOService::CreateExposableURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI, "Must have a URI");
+ nsCOMPtr<nsIURI> uri = aURI;
+ bool hasUserPass;
+ if (NS_SUCCEEDED(aURI->GetHasUserPass(&hasUserPass)) && hasUserPass) {
+ DebugOnly<nsresult> rv = NS_MutateURI(uri).SetUserPass(""_ns).Finalize(uri);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && uri, "Mutating URI should never fail");
+ }
+ return uri.forget();
+}
+
+NS_IMETHODIMP
+nsIOService::CreateExposableURI(nsIURI* aURI, nsIURI** _result) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(_result);
+ nsCOMPtr<nsIURI> exposableURI = CreateExposableURI(aURI);
+ exposableURI.forget(_result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURI(nsIURI* aURI, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsIChannel** result) {
+ return NewChannelFromURIWithProxyFlags(aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadingNode, aLoadingPrincipal,
+ aTriggeringPrincipal, aSecurityFlags,
+ aContentPolicyType, result);
+}
+nsresult nsIOService::NewChannelFromURIWithClientAndController(
+ nsIURI* aURI, nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ const Maybe<ClientInfo>& aLoadingClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController, uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags,
+ bool aSkipCheckForBrokenURLOrZeroSized, nsIChannel** aResult) {
+ return NewChannelFromURIWithProxyFlagsInternal(
+ aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal, aLoadingClientInfo,
+ aController, aSecurityFlags, aContentPolicyType, aSandboxFlags,
+ aSkipCheckForBrokenURLOrZeroSized, aResult);
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURIWithLoadInfo(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ return NewChannelFromURIWithProxyFlagsInternal(aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadInfo, result);
+}
+
+nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal(
+ nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
+ nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ const Maybe<ClientInfo>& aLoadingClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController, uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, uint32_t aSandboxFlags,
+ bool aSkipCheckForBrokenURLOrZeroSized, nsIChannel** result) {
+ nsCOMPtr<nsILoadInfo> loadInfo = new LoadInfo(
+ aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags,
+ aContentPolicyType, aLoadingClientInfo, aController, aSandboxFlags,
+ aSkipCheckForBrokenURLOrZeroSized);
+ return NewChannelFromURIWithProxyFlagsInternal(aURI, aProxyURI, aProxyFlags,
+ loadInfo, result);
+}
+
+nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal(
+ nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
+ nsILoadInfo* aLoadInfo, nsIChannel** result) {
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aURI);
+ // all channel creations must provide a valid loadinfo
+ MOZ_ASSERT(aLoadInfo, "can not create channel without aLoadInfo");
+ NS_ENSURE_ARG_POINTER(aLoadInfo);
+
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler);
+ if (pph) {
+ rv = pph->NewProxiedChannel(aURI, nullptr, aProxyFlags, aProxyURI,
+ aLoadInfo, getter_AddRefs(channel));
+ } else {
+ rv = handler->NewChannel(aURI, aLoadInfo, getter_AddRefs(channel));
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ // Make sure that all the individual protocolhandlers attach a loadInfo.
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ if (aLoadInfo != loadInfo) {
+ MOZ_ASSERT(false, "newly created channel must have a loadinfo attached");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If we're sandboxed, make sure to clear any owner the channel
+ // might already have.
+ if (loadInfo->GetLoadingSandboxed()) {
+ channel->SetOwner(nullptr);
+ }
+
+ // Some extensions override the http protocol handler and provide their own
+ // implementation. The channels returned from that implementation doesn't
+ // seem to always implement the nsIUploadChannel2 interface, presumably
+ // because it's a new interface.
+ // Eventually we should remove this and simply require that http channels
+ // implement the new interface.
+ // See bug 529041
+ if (!gHasWarnedUploadChannel2 && scheme.EqualsLiteral("http")) {
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(channel);
+ if (!uploadChannel2) {
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ consoleService->LogStringMessage(
+ u"Http channel implementation "
+ "doesn't support nsIUploadChannel2. An extension has "
+ "supplied a non-functional http protocol handler. This will "
+ "break behavior and in future releases not work at all.");
+ }
+ gHasWarnedUploadChannel2 = true;
+ }
+ }
+
+ channel.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannelFromURIWithProxyFlags(
+ nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
+ nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal, uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, nsIChannel** result) {
+ return NewChannelFromURIWithProxyFlagsInternal(
+ aURI, aProxyURI, aProxyFlags, aLoadingNode, aLoadingPrincipal,
+ aTriggeringPrincipal, Maybe<ClientInfo>(),
+ Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, aContentPolicyType, 0,
+ /* aSkipCheckForBrokenURLOrZeroSized = */ false, result);
+}
+
+NS_IMETHODIMP
+nsIOService::NewChannel(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ uint32_t aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsIChannel** result) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ rv = NewURI(aSpec, aCharset, aBaseURI, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ return NewChannelFromURI(uri, aLoadingNode, aLoadingPrincipal,
+ aTriggeringPrincipal, aSecurityFlags,
+ aContentPolicyType, result);
+}
+
+NS_IMETHODIMP
+nsIOService::NewWebTransport(nsIWebTransport** result) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIWebTransport> webTransport = new WebTransportSessionProxy();
+
+ webTransport.forget(result);
+ return NS_OK;
+}
+
+bool nsIOService::IsLinkUp() {
+ InitializeNetworkLinkService();
+
+ if (!mNetworkLinkService) {
+ // We cannot decide, assume the link is up
+ return true;
+ }
+
+ bool isLinkUp;
+ nsresult rv;
+ rv = mNetworkLinkService->GetIsLinkUp(&isLinkUp);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ return isLinkUp;
+}
+
+NS_IMETHODIMP
+nsIOService::GetOffline(bool* offline) {
+ if (StaticPrefs::network_offline_mirrors_connectivity()) {
+ *offline = mOffline || !mConnectivity;
+ } else {
+ *offline = mOffline;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::SetOffline(bool offline) { return SetOfflineInternal(offline); }
+
+nsresult nsIOService::SetOfflineInternal(bool offline,
+ bool notifySocketProcess) {
+ LOG(("nsIOService::SetOffline offline=%d\n", offline));
+ // When someone wants to go online (!offline) after we got XPCOM shutdown
+ // throw ERROR_NOT_AVAILABLE to prevent return to online state.
+ if ((mShutdown || mOfflineForProfileChange) && !offline) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // SetOffline() may re-enter while it's shutting down services.
+ // If that happens, save the most recent value and it will be
+ // processed when the first SetOffline() call is done bringing
+ // down the service.
+ mSetOfflineValue = offline;
+ if (mSettingOffline) {
+ return NS_OK;
+ }
+
+ mSettingOffline = true;
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+
+ NS_ASSERTION(observerService, "The observer service should not be null");
+
+ if (XRE_IsParentProcess()) {
+ if (observerService) {
+ (void)observerService->NotifyObservers(nullptr,
+ NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC,
+ offline ? u"true" : u"false");
+ }
+ if (SocketProcessReady() && notifySocketProcess) {
+ Unused << mSocketProcess->GetActor()->SendSetOffline(offline);
+ }
+ }
+
+ nsIIOService* subject = static_cast<nsIIOService*>(this);
+ while (mSetOfflineValue != mOffline) {
+ offline = mSetOfflineValue;
+
+ if (offline && !mOffline) {
+ mOffline = true; // indicate we're trying to shutdown
+
+ // don't care if notifications fail
+ if (observerService) {
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_GOING_OFFLINE_TOPIC,
+ u"" NS_IOSERVICE_OFFLINE);
+ }
+
+ if (mSocketTransportService) mSocketTransportService->SetOffline(true);
+
+ mLastOfflineStateChange = PR_IntervalNow();
+ if (observerService) {
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ u"" NS_IOSERVICE_OFFLINE);
+ }
+ } else if (!offline && mOffline) {
+ // go online
+ InitializeSocketTransportService();
+ mOffline = false; // indicate success only AFTER we've
+ // brought up the services
+
+ mLastOfflineStateChange = PR_IntervalNow();
+ // don't care if notification fails
+ // Only send the ONLINE notification if there is connectivity
+ if (observerService && mConnectivity) {
+ observerService->NotifyObservers(subject,
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ (u"" NS_IOSERVICE_ONLINE));
+ }
+ }
+ }
+
+ // Don't notify here, as the above notifications (if used) suffice.
+ if ((mShutdown || mOfflineForProfileChange) && mOffline) {
+ if (mSocketTransportService) {
+ DebugOnly<nsresult> rv = mSocketTransportService->Shutdown(mShutdown);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "socket transport service shutdown failed");
+ }
+ }
+
+ mSettingOffline = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetConnectivity(bool* aConnectivity) {
+ *aConnectivity = mConnectivity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::SetConnectivity(bool aConnectivity) {
+ LOG(("nsIOService::SetConnectivity aConnectivity=%d\n", aConnectivity));
+ // This should only be called from ContentChild to pass the connectivity
+ // value from the chrome process to the content process.
+ if (XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return SetConnectivityInternal(aConnectivity);
+}
+
+nsresult nsIOService::SetConnectivityInternal(bool aConnectivity) {
+ LOG(("nsIOService::SetConnectivityInternal aConnectivity=%d\n",
+ aConnectivity));
+ if (mConnectivity == aConnectivity) {
+ // Nothing to do here.
+ return NS_OK;
+ }
+ mConnectivity = aConnectivity;
+
+ // This is used for PR_Connect PR_Close telemetry so it is important that
+ // we have statistic about network change event even if we are offline.
+ mLastConnectivityChange = PR_IntervalNow();
+
+ if (mCaptivePortalService) {
+ if (aConnectivity && gCaptivePortalEnabled) {
+ // This will also trigger a captive portal check for the new network
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Start();
+ } else {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (!observerService) {
+ return NS_OK;
+ }
+ // This notification sends the connectivity to the child processes
+ if (XRE_IsParentProcess()) {
+ observerService->NotifyObservers(nullptr,
+ NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC,
+ aConnectivity ? u"true" : u"false");
+ if (SocketProcessReady()) {
+ Unused << mSocketProcess->GetActor()->SendSetConnectivity(aConnectivity);
+ }
+ }
+
+ if (mOffline) {
+ // We don't need to send any notifications if we're offline
+ return NS_OK;
+ }
+
+ if (aConnectivity) {
+ // If we were previously offline due to connectivity=false,
+ // send the ONLINE notification
+ observerService->NotifyObservers(static_cast<nsIIOService*>(this),
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ (u"" NS_IOSERVICE_ONLINE));
+ } else {
+ // If we were previously online and lost connectivity
+ // send the OFFLINE notification
+ observerService->NotifyObservers(static_cast<nsIIOService*>(this),
+ NS_IOSERVICE_GOING_OFFLINE_TOPIC,
+ u"" NS_IOSERVICE_OFFLINE);
+ observerService->NotifyObservers(static_cast<nsIIOService*>(this),
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ u"" NS_IOSERVICE_OFFLINE);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::AllowPort(int32_t inPort, const char* scheme, bool* _retval) {
+ int32_t port = inPort;
+ if (port == -1) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ if (port <= 0 || port > std::numeric_limits<uint16_t>::max()) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ nsTArray<int32_t> restrictedPortList;
+ {
+ AutoReadLock lock(mLock);
+ restrictedPortList.Assign(mRestrictedPortList);
+ }
+ // first check to see if the port is in our blacklist:
+ int32_t badPortListCnt = restrictedPortList.Length();
+ for (int i = 0; i < badPortListCnt; i++) {
+ if (port == restrictedPortList[i]) {
+ *_retval = false;
+
+ // check to see if the protocol wants to override
+ if (!scheme) return NS_OK;
+
+ // We don't support get protocol handler off main thread.
+ if (!NS_IsMainThread()) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ // let the protocol handler decide
+ return handler->AllowPort(port, scheme, _retval);
+ }
+ }
+
+ *_retval = true;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// static
+void nsIOService::PrefsChanged(const char* pref, void* self) {
+ static_cast<nsIOService*>(self)->PrefsChanged(pref);
+}
+
+void nsIOService::PrefsChanged(const char* pref) {
+ // Look for extra ports to block
+ if (!pref || strcmp(pref, PORT_PREF("banned")) == 0) {
+ ParsePortList(PORT_PREF("banned"), false);
+ }
+
+ // ...as well as previous blocks to remove.
+ if (!pref || strcmp(pref, PORT_PREF("banned.override")) == 0) {
+ ParsePortList(PORT_PREF("banned.override"), true);
+ }
+
+ if (!pref || strcmp(pref, MANAGE_OFFLINE_STATUS_PREF) == 0) {
+ bool manage;
+ if (mNetworkLinkServiceInitialized &&
+ NS_SUCCEEDED(
+ Preferences::GetBool(MANAGE_OFFLINE_STATUS_PREF, &manage))) {
+ LOG(("nsIOService::PrefsChanged ManageOfflineStatus manage=%d\n",
+ manage));
+ SetManageOfflineStatus(manage);
+ }
+ }
+
+ if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_COUNT_PREF) == 0) {
+ int32_t count;
+ if (NS_SUCCEEDED(
+ Preferences::GetInt(NECKO_BUFFER_CACHE_COUNT_PREF, &count))) {
+ /* check for bogus values and default if we find such a value */
+ if (count > 0) gDefaultSegmentCount = count;
+ }
+ }
+
+ if (!pref || strcmp(pref, NECKO_BUFFER_CACHE_SIZE_PREF) == 0) {
+ int32_t size;
+ if (NS_SUCCEEDED(
+ Preferences::GetInt(NECKO_BUFFER_CACHE_SIZE_PREF, &size))) {
+ /* check for bogus values and default if we find such a value
+ * the upper limit here is arbitrary. having a 1mb segment size
+ * is pretty crazy. if you remove this, consider adding some
+ * integer rollover test.
+ */
+ if (size > 0 && size < 1024 * 1024) gDefaultSegmentSize = size;
+ }
+ NS_WARNING_ASSERTION(!(size & (size - 1)),
+ "network segment size is not a power of 2!");
+ }
+
+ if (!pref || strcmp(pref, NETWORK_CAPTIVE_PORTAL_PREF) == 0) {
+ nsresult rv = Preferences::GetBool(NETWORK_CAPTIVE_PORTAL_PREF,
+ &gCaptivePortalEnabled);
+ if (NS_SUCCEEDED(rv) && mCaptivePortalService) {
+ if (gCaptivePortalEnabled) {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())
+ ->Start();
+ } else {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ }
+ }
+ }
+
+ if (!pref || strncmp(pref, FORCE_EXTERNAL_PREF_PREFIX,
+ strlen(FORCE_EXTERNAL_PREF_PREFIX)) == 0) {
+ nsTArray<nsCString> prefs;
+ if (nsIPrefBranch* prefRootBranch = Preferences::GetRootBranch()) {
+ prefRootBranch->GetChildList(FORCE_EXTERNAL_PREF_PREFIX, prefs);
+ }
+ nsTArray<nsCString> forceExternalSchemes;
+ for (const auto& pref : prefs) {
+ if (Preferences::GetBool(pref.get(), false)) {
+ forceExternalSchemes.AppendElement(
+ Substring(pref, strlen(FORCE_EXTERNAL_PREF_PREFIX)));
+ }
+ }
+ AutoWriteLock lock(mLock);
+ mForceExternalSchemes = std::move(forceExternalSchemes);
+ }
+}
+
+void nsIOService::ParsePortList(const char* pref, bool remove) {
+ nsAutoCString portList;
+ nsTArray<int32_t> restrictedPortList;
+ {
+ AutoWriteLock lock(mLock);
+ restrictedPortList.Assign(std::move(mRestrictedPortList));
+ }
+ // Get a pref string and chop it up into a list of ports.
+ Preferences::GetCString(pref, portList);
+ if (!portList.IsVoid()) {
+ nsTArray<nsCString> portListArray;
+ ParseString(portList, ',', portListArray);
+ uint32_t index;
+ for (index = 0; index < portListArray.Length(); index++) {
+ portListArray[index].StripWhitespace();
+ int32_t portBegin, portEnd;
+
+ if (PR_sscanf(portListArray[index].get(), "%d-%d", &portBegin,
+ &portEnd) == 2) {
+ if ((portBegin < 65536) && (portEnd < 65536)) {
+ int32_t curPort;
+ if (remove) {
+ for (curPort = portBegin; curPort <= portEnd; curPort++) {
+ restrictedPortList.RemoveElement(curPort);
+ }
+ } else {
+ for (curPort = portBegin; curPort <= portEnd; curPort++) {
+ restrictedPortList.AppendElement(curPort);
+ }
+ }
+ }
+ } else {
+ nsresult aErrorCode;
+ int32_t port = portListArray[index].ToInteger(&aErrorCode);
+ if (NS_SUCCEEDED(aErrorCode) && port < 65536) {
+ if (remove) {
+ restrictedPortList.RemoveElement(port);
+ } else {
+ restrictedPortList.AppendElement(port);
+ }
+ }
+ }
+ }
+ }
+
+ AutoWriteLock lock(mLock);
+ mRestrictedPortList.Assign(std::move(restrictedPortList));
+}
+
+class nsWakeupNotifier : public Runnable {
+ public:
+ explicit nsWakeupNotifier(nsIIOServiceInternal* ioService)
+ : Runnable("net::nsWakeupNotifier"), mIOService(ioService) {}
+
+ NS_IMETHOD Run() override { return mIOService->NotifyWakeup(); }
+
+ private:
+ virtual ~nsWakeupNotifier() = default;
+ nsCOMPtr<nsIIOServiceInternal> mIOService;
+};
+
+NS_IMETHODIMP
+nsIOService::NotifyWakeup() {
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+
+ NS_ASSERTION(observerService, "The observer service should not be null");
+
+ if (observerService && StaticPrefs::network_notify_changed()) {
+ (void)observerService->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC,
+ (u"" NS_NETWORK_LINK_DATA_CHANGED));
+ }
+
+ RecheckCaptivePortal();
+
+ return NS_OK;
+}
+
+void nsIOService::SetHttpHandlerAlreadyShutingDown() {
+ if (!mShutdown && !mOfflineForProfileChange) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ mHttpHandlerAlreadyShutingDown = true;
+ }
+}
+
+// nsIObserver interface
+NS_IMETHODIMP
+nsIOService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (UseSocketProcess() && SocketProcessReady() &&
+ mObserverTopicForSocketProcess.Contains(nsDependentCString(topic))) {
+ nsCString topicStr(topic);
+ nsString dataStr(data);
+ Unused << mSocketProcess->GetActor()->SendNotifyObserver(topicStr, dataStr);
+ }
+
+ if (!strcmp(topic, kProfileChangeNetTeardownTopic)) {
+ if (!mHttpHandlerAlreadyShutingDown) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ }
+ mHttpHandlerAlreadyShutingDown = false;
+ if (!mOffline) {
+ mOfflineForProfileChange = true;
+ SetOfflineInternal(true, false);
+ }
+ } else if (!strcmp(topic, kProfileChangeNetRestoreTopic)) {
+ if (mOfflineForProfileChange) {
+ mOfflineForProfileChange = false;
+ SetOfflineInternal(false, false);
+ }
+ } else if (!strcmp(topic, kProfileDoChange)) {
+ if (data && u"startup"_ns.Equals(data)) {
+ // Lazy initialization of network link service (see bug 620472)
+ InitializeNetworkLinkService();
+ // Set up the initilization flag regardless the actuall result.
+ // If we fail here, we will fail always on.
+ mNetworkLinkServiceInitialized = true;
+
+ // And now reflect the preference setting
+ PrefsChanged(MANAGE_OFFLINE_STATUS_PREF);
+
+ // Bug 870460 - Read cookie database at an early-as-possible time
+ // off main thread. Hence, we have more chance to finish db query
+ // before something calls into the cookie service.
+ nsCOMPtr<nsISupports> cookieServ =
+ do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ }
+ } else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ // Remember we passed XPCOM shutdown notification to prevent any
+ // changes of the offline status from now. We must not allow going
+ // online after this point.
+ mShutdown = true;
+
+ if (!mHttpHandlerAlreadyShutingDown && !mOfflineForProfileChange) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ }
+ mHttpHandlerAlreadyShutingDown = false;
+
+ SetOfflineInternal(true, false);
+
+ if (mCaptivePortalService) {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ mCaptivePortalService = nullptr;
+ }
+
+ SSLTokensCache::Shutdown();
+
+ DestroySocketProcess();
+
+ if (IsSocketProcessChild()) {
+ Preferences::UnregisterCallbacks(nsIOService::OnTLSPrefChange,
+ gCallbackSecurityPrefs, this);
+ PrepareForShutdownInSocketProcess();
+ }
+
+ // We're in XPCOM shutdown now. Unregister any dynamic protocol handlers
+ // after this point to avoid leaks.
+ {
+ AutoWriteLock lock(mLock);
+ mRuntimeProtocolHandlers.Clear();
+ }
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ OnNetworkLinkEvent(NS_ConvertUTF16toUTF8(data).get());
+ } else if (!strcmp(topic, NS_NETWORK_ID_CHANGED_TOPIC)) {
+ LOG(("nsIOService::OnNetworkLinkEvent Network id changed"));
+ } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
+ // coming back alive from sleep
+ // this indirection brought to you by:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1152048#c19
+ nsCOMPtr<nsIRunnable> wakeupNotifier = new nsWakeupNotifier(this);
+ NS_DispatchToMainThread(wakeupNotifier);
+ }
+
+ return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
+nsIOService::ParseRequestContentType(const nsACString& aTypeHeader,
+ nsACString& aCharset, bool* aHadCharset,
+ nsACString& aContentType) {
+ net_ParseRequestContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+ return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
+nsIOService::ParseResponseContentType(const nsACString& aTypeHeader,
+ nsACString& aCharset, bool* aHadCharset,
+ nsACString& aContentType) {
+ net_ParseContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::ProtocolHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
+ NS_ENSURE_ARG(uri);
+
+ *result = false;
+ nsAutoCString scheme;
+ nsresult rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto handler = LookupProtocolHandler(scheme);
+
+ uint32_t protocolFlags;
+ if (flags & nsIProtocolHandler::DYNAMIC_URI_FLAGS) {
+ AssertIsOnMainThread();
+ rv = handler.DynamicProtocolFlags(uri, &protocolFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ protocolFlags = handler.StaticProtocolFlags();
+ }
+
+ *result = (protocolFlags & flags) == flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
+ nsresult rv = ProtocolHasFlags(uri, flags, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*result) {
+ return rv;
+ }
+
+ // Dig deeper into the chain. Note that this is not a do/while loop to
+ // avoid the extra addref/release on |uri| in the common (non-nested) case.
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
+ while (nestedURI) {
+ nsCOMPtr<nsIURI> innerURI;
+ rv = nestedURI->GetInnerURI(getter_AddRefs(innerURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ProtocolHasFlags(innerURI, flags, result);
+
+ if (*result) {
+ return rv;
+ }
+
+ nestedURI = do_QueryInterface(innerURI);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIOService::SetManageOfflineStatus(bool aManage) {
+ LOG(("nsIOService::SetManageOfflineStatus aManage=%d\n", aManage));
+ mManageLinkStatus = aManage;
+
+ // When detection is not activated, the default connectivity state is true.
+ if (!mManageLinkStatus) {
+ SetConnectivityInternal(true);
+ return NS_OK;
+ }
+
+ InitializeNetworkLinkService();
+ // If the NetworkLinkService is already initialized, it does not call
+ // OnNetworkLinkEvent. This is needed, when mManageLinkStatus goes from
+ // false to true.
+ OnNetworkLinkEvent(NS_NETWORK_LINK_DATA_UNKNOWN);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetManageOfflineStatus(bool* aManage) {
+ *aManage = mManageLinkStatus;
+ return NS_OK;
+}
+
+// input argument 'data' is already UTF8'ed
+nsresult nsIOService::OnNetworkLinkEvent(const char* data) {
+ if (IsNeckoChild() || IsSocketProcessChild()) {
+ // There is nothing IO service could do on the child process
+ // with this at the moment. Feel free to add functionality
+ // here at will, though.
+ return NS_OK;
+ }
+
+ if (mShutdown) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCString dataAsString(data);
+ for (auto* cp : mozilla::dom::ContentParent::AllProcesses(
+ mozilla::dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ Unused << neckoParent->SendNetworkChangeNotification(dataAsString);
+ }
+
+ LOG(("nsIOService::OnNetworkLinkEvent data:%s\n", data));
+ if (!mNetworkLinkService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mManageLinkStatus) {
+ LOG(("nsIOService::OnNetworkLinkEvent mManageLinkStatus=false\n"));
+ return NS_OK;
+ }
+
+ bool isUp = true;
+ if (!strcmp(data, NS_NETWORK_LINK_DATA_CHANGED)) {
+ mLastNetworkLinkChange = PR_IntervalNow();
+ // CHANGED means UP/DOWN didn't change
+ // but the status of the captive portal may have changed.
+ RecheckCaptivePortal();
+ return NS_OK;
+ }
+ if (!strcmp(data, NS_NETWORK_LINK_DATA_DOWN)) {
+ isUp = false;
+ } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UP)) {
+ isUp = true;
+ } else if (!strcmp(data, NS_NETWORK_LINK_DATA_UNKNOWN)) {
+ nsresult rv = mNetworkLinkService->GetIsLinkUp(&isUp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_WARNING("Unhandled network event!");
+ return NS_OK;
+ }
+
+ return SetConnectivityInternal(isUp);
+}
+
+NS_IMETHODIMP
+nsIOService::EscapeString(const nsACString& aString, uint32_t aEscapeType,
+ nsACString& aResult) {
+ NS_ENSURE_ARG_MAX(aEscapeType, 4);
+
+ nsAutoCString stringCopy(aString);
+ nsCString result;
+
+ if (!NS_Escape(stringCopy, result, (nsEscapeMask)aEscapeType)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ aResult.Assign(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::EscapeURL(const nsACString& aStr, uint32_t aFlags,
+ nsACString& aResult) {
+ aResult.Truncate();
+ NS_EscapeURL(aStr.BeginReading(), aStr.Length(), aFlags | esc_AlwaysCopy,
+ aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::UnescapeString(const nsACString& aStr, uint32_t aFlags,
+ nsACString& aResult) {
+ aResult.Truncate();
+ NS_UnescapeURL(aStr.BeginReading(), aStr.Length(), aFlags | esc_AlwaysCopy,
+ aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::ExtractCharsetFromContentType(const nsACString& aTypeHeader,
+ nsACString& aCharset,
+ int32_t* aCharsetStart,
+ int32_t* aCharsetEnd,
+ bool* aHadCharset) {
+ nsAutoCString ignored;
+ net_ParseContentType(aTypeHeader, ignored, aCharset, aHadCharset,
+ aCharsetStart, aCharsetEnd);
+ if (*aHadCharset && *aCharsetStart == *aCharsetEnd) {
+ *aHadCharset = false;
+ }
+ return NS_OK;
+}
+
+// nsISpeculativeConnect
+class IOServiceProxyCallback final : public nsIProtocolProxyCallback {
+ ~IOServiceProxyCallback() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+
+ IOServiceProxyCallback(nsIInterfaceRequestor* aCallbacks,
+ nsIOService* aIOService,
+ Maybe<OriginAttributes>&& aOriginAttributes)
+ : mCallbacks(aCallbacks),
+ mIOService(aIOService),
+ mOriginAttributes(std::move(aOriginAttributes)) {}
+
+ private:
+ RefPtr<nsIInterfaceRequestor> mCallbacks;
+ RefPtr<nsIOService> mIOService;
+ Maybe<OriginAttributes> mOriginAttributes;
+};
+
+NS_IMPL_ISUPPORTS(IOServiceProxyCallback, nsIProtocolProxyCallback)
+
+NS_IMETHODIMP
+IOServiceProxyCallback::OnProxyAvailable(nsICancelable* request,
+ nsIChannel* channel, nsIProxyInfo* pi,
+ nsresult status) {
+ // Checking proxy status for speculative connect
+ nsAutoCString type;
+ if (NS_SUCCEEDED(status) && pi && NS_SUCCEEDED(pi->GetType(type)) &&
+ !type.EqualsLiteral("direct")) {
+ // proxies dont do speculative connect
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = mIOService->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsCOMPtr<nsISpeculativeConnect> speculativeHandler =
+ do_QueryInterface(handler);
+ if (!speculativeHandler) return NS_OK;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
+
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ bool anonymous = !!(loadFlags & nsIRequest::LOAD_ANONYMOUS);
+ if (mOriginAttributes) {
+ speculativeHandler->SpeculativeConnectWithOriginAttributesNative(
+ uri, std::move(mOriginAttributes.ref()), mCallbacks, anonymous);
+ } else {
+ speculativeHandler->SpeculativeConnect(uri, principal, mCallbacks,
+ anonymous);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsIOService::SpeculativeConnectInternal(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ Maybe<OriginAttributes>&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous) {
+ NS_ENSURE_ARG(aURI);
+
+ if (!aURI->SchemeIs("http") && !aURI->SchemeIs("https")) {
+ // We don't speculatively connect to non-HTTP[S] URIs.
+ return NS_OK;
+ }
+
+ if (IsNeckoChild()) {
+ gNeckoChild->SendSpeculativeConnect(
+ aURI, aPrincipal, std::move(aOriginAttributes), aAnonymous);
+ return NS_OK;
+ }
+
+ // Check for proxy information. If there is a proxy configured then a
+ // speculative connect should not be performed because the potential
+ // reward is slim with tcp peers closely located to the browser.
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aPrincipal;
+
+ MOZ_ASSERT(aPrincipal || aOriginAttributes,
+ "We expect passing a principal or OriginAttributes here.");
+
+ if (!aPrincipal && !aOriginAttributes) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aOriginAttributes) {
+ loadingPrincipal =
+ BasePrincipal::CreateContentPrincipal(aURI, aOriginAttributes.ref());
+ }
+
+ // XXX Bug 1724080: Avoid TCP connections on port 80 when https-only
+ // or https-first is enabled. Let's create a dummy loadinfo which we
+ // only use to determine whether we need ot upgrade the speculative
+ // connection from http to https.
+ nsCOMPtr<nsIURI> httpsURI;
+ if (aURI->SchemeIs("http")) {
+ nsCOMPtr<nsILoadInfo> httpsOnlyCheckLoadInfo =
+ new LoadInfo(loadingPrincipal, loadingPrincipal, nullptr,
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ nsIContentPolicy::TYPE_SPECULATIVE);
+
+ // Check if https-only, or https-first would upgrade the request
+ if (nsHTTPSOnlyUtils::ShouldUpgradeRequest(aURI, httpsOnlyCheckLoadInfo) ||
+ nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(
+ aURI, httpsOnlyCheckLoadInfo)) {
+ rv = NS_GetSecureUpgradedURI(aURI, getter_AddRefs(httpsURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ aURI = httpsURI.get();
+ }
+ }
+
+ // dummy channel used to create a TCP connection.
+ // we perform security checks on the *real* channel, responsible
+ // for any network loads. this real channel just checks the TCP
+ // pool if there is an available connection created by the
+ // channel we create underneath - hence it's safe to use
+ // the systemPrincipal as the loadingPrincipal for this channel.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NewChannelFromURI(
+ aURI,
+ nullptr, // aLoadingNode,
+ loadingPrincipal,
+ nullptr, // aTriggeringPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_SPECULATIVE, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aAnonymous) {
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ loadFlags |= nsIRequest::LOAD_ANONYMOUS;
+ channel->SetLoadFlags(loadFlags);
+ }
+
+ nsCOMPtr<nsICancelable> cancelable;
+ RefPtr<IOServiceProxyCallback> callback = new IOServiceProxyCallback(
+ aCallbacks, this, std::move(aOriginAttributes));
+ nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
+ if (pps2) {
+ return pps2->AsyncResolve2(channel, 0, callback, nullptr,
+ getter_AddRefs(cancelable));
+ }
+ return pps->AsyncResolve(channel, 0, callback, nullptr,
+ getter_AddRefs(cancelable));
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeConnect(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsIInterfaceRequestor* aCallbacks,
+ bool aAnonymous) {
+ return SpeculativeConnectInternal(aURI, aPrincipal, Nothing(), aCallbacks,
+ aAnonymous);
+}
+
+NS_IMETHODIMP nsIOService::SpeculativeConnectWithOriginAttributes(
+ nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous, JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ SpeculativeConnectWithOriginAttributesNative(aURI, std::move(attrs),
+ aCallbacks, aAnonymous);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsIOService::SpeculativeConnectWithOriginAttributesNative(
+ nsIURI* aURI, OriginAttributes&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous) {
+ Maybe<OriginAttributes> originAttributes;
+ originAttributes.emplace(aOriginAttributes);
+ Unused << SpeculativeConnectInternal(
+ aURI, nullptr, std::move(originAttributes), aCallbacks, aAnonymous);
+}
+
+NS_IMETHODIMP
+nsIOService::NotImplemented() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsIOService::GetSocketProcessLaunched(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = SocketProcessReady();
+ return NS_OK;
+}
+
+bool nsIOService::HasObservers(const char* aTopic) {
+ MOZ_ASSERT(false, "Calling this method is unexpected");
+ return false;
+}
+
+NS_IMETHODIMP
+nsIOService::GetSocketProcessId(uint64_t* aPid) {
+ NS_ENSURE_ARG_POINTER(aPid);
+
+ *aPid = 0;
+ if (!mSocketProcess) {
+ return NS_OK;
+ }
+
+ if (SocketProcessParent* actor = mSocketProcess->GetActor()) {
+ *aPid = (uint64_t)actor->OtherPid();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::RegisterProtocolHandler(const nsACString& aScheme,
+ nsIProtocolHandler* aHandler,
+ uint32_t aProtocolFlags,
+ int32_t aDefaultPort) {
+ if (mShutdown) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (aScheme.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString scheme(aScheme);
+ ToLowerCase(scheme);
+
+ AutoWriteLock lock(mLock);
+ return mRuntimeProtocolHandlers.WithEntryHandle(scheme, [&](auto&& entry) {
+ if (entry) {
+ NS_WARNING("Cannot override an existing dynamic protocol handler");
+ return NS_ERROR_FACTORY_EXISTS;
+ }
+ if (xpcom::StaticProtocolHandler::Lookup(scheme)) {
+ NS_WARNING("Cannot override an existing static protocol handler");
+ return NS_ERROR_FACTORY_EXISTS;
+ }
+ nsMainThreadPtrHandle<nsIProtocolHandler> handler(
+ new nsMainThreadPtrHolder<nsIProtocolHandler>("RuntimeProtocolHandler",
+ aHandler));
+ entry.Insert(RuntimeProtocolHandler{
+ .mHandler = std::move(handler),
+ .mProtocolFlags = aProtocolFlags,
+ .mDefaultPort = aDefaultPort,
+ });
+ return NS_OK;
+ });
+}
+
+NS_IMETHODIMP
+nsIOService::UnregisterProtocolHandler(const nsACString& aScheme) {
+ if (mShutdown) {
+ return NS_OK;
+ }
+ if (aScheme.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString scheme(aScheme);
+ ToLowerCase(scheme);
+
+ AutoWriteLock lock(mLock);
+ return mRuntimeProtocolHandlers.Remove(scheme)
+ ? NS_OK
+ : NS_ERROR_FACTORY_NOT_REGISTERED;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsIOService.h b/netwerk/base/nsIOService.h
new file mode 100644
index 0000000000..52c8b48e7f
--- /dev/null
+++ b/netwerk/base/nsIOService.h
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIOService_h__
+#define nsIOService_h__
+
+#include "nsStringFwd.h"
+#include "nsIIOService.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsINetUtil.h"
+#include "nsIChannelEventSink.h"
+#include "nsCategoryCache.h"
+#include "nsISpeculativeConnect.h"
+#include "nsWeakReference.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RWLock.h"
+#include "mozilla/net/ProtocolHandlerInfo.h"
+#include "prtime.h"
+#include "nsICaptivePortalService.h"
+#include "nsIObserverService.h"
+#include "nsTHashSet.h"
+#include "nsWeakReference.h"
+#include "nsNetCID.h"
+
+// We don't want to expose this observer topic.
+// Intended internal use only for remoting offline/inline events.
+// See Bug 552829
+#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
+#define NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC "ipc:network:set-connectivity"
+
+class nsINetworkLinkService;
+class nsIPrefBranch;
+class nsIProtocolProxyService2;
+class nsIProxyInfo;
+class nsPISocketTransportService;
+
+namespace mozilla {
+class MemoryReportingProcess;
+namespace net {
+class NeckoChild;
+class nsAsyncRedirectVerifyHelper;
+class SocketProcessHost;
+class SocketProcessMemoryReporter;
+
+class nsIOService final : public nsIIOService,
+ public nsIObserver,
+ public nsINetUtil,
+ public nsISpeculativeConnect,
+ public nsSupportsWeakReference,
+ public nsIIOServiceInternal,
+ public nsIObserverService {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIOSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINETUTIL
+ NS_DECL_NSISPECULATIVECONNECT
+ NS_DECL_NSIIOSERVICEINTERNAL
+ NS_DECL_NSIOBSERVERSERVICE
+
+ // Gets the singleton instance of the IO Service, creating it as needed
+ // Returns nullptr on out of memory or failure to initialize.
+ static already_AddRefed<nsIOService> GetInstance();
+
+ nsresult Init();
+ nsresult NewURI(const char* aSpec, nsIURI* aBaseURI, nsIURI** result,
+ nsIProtocolHandler** hdlrResult);
+
+ // Called by channels before a redirect happens. This notifies the global
+ // redirect observers.
+ nsresult AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
+ uint32_t flags,
+ nsAsyncRedirectVerifyHelper* helper);
+
+ bool IsOffline() { return mOffline; }
+ PRIntervalTime LastOfflineStateChange() { return mLastOfflineStateChange; }
+ PRIntervalTime LastConnectivityChange() { return mLastConnectivityChange; }
+ PRIntervalTime LastNetworkLinkChange() { return mLastNetworkLinkChange; }
+ bool IsNetTearingDown() {
+ return mShutdown || mOfflineForProfileChange ||
+ mHttpHandlerAlreadyShutingDown;
+ }
+ PRIntervalTime NetTearingDownStarted() { return mNetTearingDownStarted; }
+
+ // nsHttpHandler is going to call this function to inform nsIOService that
+ // network is in process of tearing down. Moving nsHttpConnectionMgr::Shutdown
+ // to nsIOService caused problems (bug 1242755) so we doing it in this way. As
+ // soon as nsIOService gets notification that it is shutdown it is going to
+ // reset mHttpHandlerAlreadyShutingDown.
+ void SetHttpHandlerAlreadyShutingDown();
+
+ bool IsLinkUp();
+
+ // Converts an internal URI (e.g. one that has a username and password in
+ // it) into one which we can expose to the user, for example on the URL bar.
+ static already_AddRefed<nsIURI> CreateExposableURI(nsIURI*);
+
+ // Used to count the total number of HTTP requests made
+ void IncrementRequestNumber() { mTotalRequests++; }
+ uint32_t GetTotalRequestNumber() { return mTotalRequests; }
+ // Used to keep "race cache with network" stats
+ void IncrementCacheWonRequestNumber() { mCacheWon++; }
+ uint32_t GetCacheWonRequestNumber() { return mCacheWon; }
+ void IncrementNetWonRequestNumber() { mNetWon++; }
+ uint32_t GetNetWonRequestNumber() { return mNetWon; }
+
+ // Used to trigger a recheck of the captive portal status
+ nsresult RecheckCaptivePortal();
+
+ void OnProcessLaunchComplete(SocketProcessHost* aHost, bool aSucceeded);
+ void OnProcessUnexpectedShutdown(SocketProcessHost* aHost);
+ bool SocketProcessReady();
+ static void NotifySocketProcessPrefsChanged(const char* aName, void* aSelf);
+ void NotifySocketProcessPrefsChanged(const char* aName);
+ static bool UseSocketProcess(bool aCheckAgain = false);
+
+ bool IsSocketProcessLaunchComplete();
+
+ // Call func immediately if socket process is launched completely. Otherwise,
+ // |func| will be queued and then executed in the *main thread* once socket
+ // process is launced.
+ void CallOrWaitForSocketProcess(const std::function<void()>& aFunc);
+
+ int32_t SocketProcessPid();
+ SocketProcessHost* SocketProcess() { return mSocketProcess; }
+
+ friend SocketProcessMemoryReporter;
+ RefPtr<MemoryReportingProcess> GetSocketProcessMemoryReporter();
+
+ // Lookup the ProtocolHandlerInfo based on a given scheme.
+ // Safe to call from any thread.
+ ProtocolHandlerInfo LookupProtocolHandler(const nsACString& aScheme);
+
+ static void OnTLSPrefChange(const char* aPref, void* aSelf);
+
+ nsresult LaunchSocketProcess();
+
+ static bool TooManySocketProcessCrash();
+ static void IncreaseSocketProcessCrashCount();
+
+ private:
+ // These shouldn't be called directly:
+ // - construct using GetInstance
+ // - destroy using Release
+ nsIOService();
+ ~nsIOService();
+ nsresult SetConnectivityInternal(bool aConnectivity);
+
+ nsresult OnNetworkLinkEvent(const char* data);
+
+ nsresult InitializeCaptivePortalService();
+ nsresult RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan);
+
+ // Prefs wrangling
+ static void PrefsChanged(const char* pref, void* self);
+ void PrefsChanged(const char* pref = nullptr);
+ void ParsePortList(const char* pref, bool remove);
+
+ nsresult InitializeSocketTransportService();
+ nsresult InitializeNetworkLinkService();
+ nsresult InitializeProtocolProxyService();
+
+ // consolidated helper function
+ void LookupProxyInfo(nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
+ nsCString* aScheme, nsIProxyInfo** outPI);
+
+ nsresult NewChannelFromURIWithProxyFlagsInternal(
+ nsIURI* aURI, nsIURI* aProxyURI, uint32_t aProxyFlags,
+ nsINode* aLoadingNode, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ const mozilla::Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo,
+ const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ uint32_t aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ uint32_t aSandboxFlags, bool aSkipCheckForBrokenURLOrZeroSized,
+ nsIChannel** result);
+
+ nsresult NewChannelFromURIWithProxyFlagsInternal(nsIURI* aURI,
+ nsIURI* aProxyURI,
+ uint32_t aProxyFlags,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result);
+
+ nsresult SpeculativeConnectInternal(
+ nsIURI* aURI, nsIPrincipal* aPrincipal,
+ Maybe<OriginAttributes>&& aOriginAttributes,
+ nsIInterfaceRequestor* aCallbacks, bool aAnonymous);
+
+ void DestroySocketProcess();
+
+ nsresult SetOfflineInternal(bool offline, bool notifySocketProcess = true);
+
+ bool UsesExternalProtocolHandler(const nsACString& aScheme)
+ MOZ_REQUIRES_SHARED(mLock);
+
+ private:
+ mozilla::Atomic<bool, mozilla::Relaxed> mOffline{true};
+ mozilla::Atomic<bool, mozilla::Relaxed> mOfflineForProfileChange{false};
+ bool mManageLinkStatus{false};
+ mozilla::Atomic<bool, mozilla::Relaxed> mConnectivity{true};
+
+ // Used to handle SetOffline() reentrancy. See the comment in
+ // SetOffline() for more details.
+ bool mSettingOffline{false};
+ bool mSetOfflineValue{false};
+
+ bool mSocketProcessLaunchComplete{false};
+
+ mozilla::Atomic<bool, mozilla::Relaxed> mShutdown{false};
+ mozilla::Atomic<bool, mozilla::Relaxed> mHttpHandlerAlreadyShutingDown{false};
+
+ nsCOMPtr<nsPISocketTransportService> mSocketTransportService;
+ nsCOMPtr<nsICaptivePortalService> mCaptivePortalService;
+ nsCOMPtr<nsINetworkLinkService> mNetworkLinkService;
+ bool mNetworkLinkServiceInitialized{false};
+
+ // cached categories
+ nsCategoryCache<nsIChannelEventSink> mChannelEventSinks{
+ NS_CHANNEL_EVENT_SINK_CATEGORY};
+
+ RWLock mLock{"nsIOService::mLock"};
+ nsTArray<int32_t> mRestrictedPortList MOZ_GUARDED_BY(mLock);
+ nsTArray<nsCString> mForceExternalSchemes MOZ_GUARDED_BY(mLock);
+ nsTHashMap<nsCString, RuntimeProtocolHandler> mRuntimeProtocolHandlers
+ MOZ_GUARDED_BY(mLock);
+
+ uint32_t mTotalRequests{0};
+ uint32_t mCacheWon{0};
+ uint32_t mNetWon{0};
+ static uint32_t sSocketProcessCrashedCount;
+
+ // These timestamps are needed for collecting telemetry on PR_Connect,
+ // PR_ConnectContinue and PR_Close blocking time. If we spend very long
+ // time in any of these functions we want to know if and what network
+ // change has happened shortly before.
+ mozilla::Atomic<PRIntervalTime> mLastOfflineStateChange;
+ mozilla::Atomic<PRIntervalTime> mLastConnectivityChange;
+ mozilla::Atomic<PRIntervalTime> mLastNetworkLinkChange;
+
+ // Time a network tearing down started.
+ mozilla::Atomic<PRIntervalTime> mNetTearingDownStarted{0};
+
+ SocketProcessHost* mSocketProcess{nullptr};
+
+ // Events should be executed after the socket process is launched. Will
+ // dispatch these events while socket process fires OnProcessLaunchComplete.
+ // Note: this array is accessed only on the main thread.
+ nsTArray<std::function<void()>> mPendingEvents;
+
+ // The observer notifications need to be forwarded to socket process.
+ nsTHashSet<nsCString> mObserverTopicForSocketProcess;
+ // Some noticications (e.g., NS_XPCOM_SHUTDOWN_OBSERVER_ID) are triggered in
+ // socket process, so we should not send the notifications again.
+ nsTHashSet<nsCString> mSocketProcessTopicBlockedList;
+ // Used to store the topics that are already observed by IOService.
+ nsTHashSet<nsCString> mIOServiceTopicList;
+
+ nsCOMPtr<nsIObserverService> mObserverService;
+
+ public:
+ // Used for all default buffer sizes that necko allocates.
+ static uint32_t gDefaultSegmentSize;
+ static uint32_t gDefaultSegmentCount;
+};
+
+/**
+ * Reference to the IO service singleton. May be null.
+ */
+extern nsIOService* gIOService;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsIOService_h__
diff --git a/netwerk/base/nsIParentChannel.idl b/netwerk/base/nsIParentChannel.idl
new file mode 100644
index 0000000000..a351019d7d
--- /dev/null
+++ b/netwerk/base/nsIParentChannel.idl
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+#include "nsIHttpChannel.idl"
+
+interface nsIRemoteTab;
+
+%{C++
+namespace mozilla {
+namespace net {
+class ParentChannelListener;
+}
+}
+%}
+
+[ptr] native ParentChannelListener(mozilla::net::ParentChannelListener);
+
+/**
+ * Implemented by chrome side of IPC protocols.
+ */
+
+[scriptable, uuid(e0fc4801-6030-4653-a59f-1fb282bd1a04)]
+interface nsIParentChannel : nsIStreamListener
+{
+ /**
+ * Called to set the ParentChannelListener object (optional).
+ */
+ [noscript] void setParentListener(in ParentChannelListener listener);
+
+ /**
+ * Called to set matched information when URL matches SafeBrowsing list.
+ * @param aList
+ * Name of the list that matched
+ * @param aProvider
+ * Name of provider that matched
+ * @param aFullHash
+ * String represents full hash that matched
+ */
+ [noscript] void setClassifierMatchedInfo(in ACString aList,
+ in ACString aProvider,
+ in ACString aFullHash);
+
+ /**
+ * Called to set matched tracking information when URL matches tracking annotation list.
+ * @param aList
+ * Comma-separated list of tables that matched
+ * @param aFullHashes
+ * Comma-separated list of base64 encoded full hashes that matched
+ */
+ [noscript] void setClassifierMatchedTrackingInfo(in ACString aLists,
+ in ACString aFullHashes);
+
+ /**
+ * Called to notify the HttpChannelChild that the resource being loaded
+ * has been classified.
+ * @param aClassificationFlags
+ * What classifier identifies this channel.
+ * @param aIsThirdParty
+ * Whether or not the resourced is considered first-party
+ * with the URI of the window.
+ */
+ [noscript] void notifyClassificationFlags(in uint32_t aClassificationFlags,
+ in bool aIsThirdParty);
+
+ /**
+ * Called to invoke deletion of the IPC protocol.
+ */
+ void delete();
+
+ /**
+ * The remote type of the target process for this load.
+ */
+ readonly attribute AUTF8String remoteType;
+};
diff --git a/netwerk/base/nsIParentRedirectingChannel.idl b/netwerk/base/nsIParentRedirectingChannel.idl
new file mode 100644
index 0000000000..6302775c51
--- /dev/null
+++ b/netwerk/base/nsIParentRedirectingChannel.idl
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIParentChannel.idl"
+
+interface nsIRemoteTab;
+interface nsIChannel;
+interface nsIAsyncVerifyRedirectCallback;
+
+[uuid(01987690-48cf-45de-bae3-e143c2adc2a8)]
+interface nsIAsyncVerifyRedirectReadyCallback : nsISupports
+{
+ /**
+ * Asynchronous callback when redirected channel finishes the preparation for
+ * completing the verification procedure.
+ *
+ * @param result
+ * SUCCEEDED if preparation for redirection verification succceed.
+ * If FAILED the redirection must be aborted.
+ */
+ void readyToVerify(in nsresult result);
+};
+
+/**
+ * Implemented by chrome side of IPC protocols that support redirect responses.
+ */
+
+[scriptable, uuid(3ed1d288-5324-46ee-8a98-33ac37d1080b)]
+interface nsIParentRedirectingChannel : nsIParentChannel
+{
+ /**
+ * Called when the channel got a response that redirects it to a different
+ * URI. The implementation is responsible for calling the redirect observers
+ * on the child process and provide the decision result to the callback.
+ *
+ * @param newURI
+ * the URI we redirect to
+ * @param callback
+ * redirect result callback, usage is compatible with how
+ * nsIChannelEventSink defines it
+ */
+ void startRedirect(in nsIChannel newChannel,
+ in uint32_t redirectFlags,
+ in nsIAsyncVerifyRedirectCallback callback);
+
+ /**
+ * Called to new channel when the original channel got Redirect2Verify
+ * response from child. Callback will be invoked when the new channel
+ * finishes the preparation for Redirect2Verify and can be called immediately.
+ *
+ * @param callback
+ * redirect ready callback, will be called when redirect verification
+ * procedure can proceed.
+ */
+ void continueVerification(in nsIAsyncVerifyRedirectReadyCallback callback);
+
+ /**
+ * Called after we are done with redirecting process and we know if to
+ * redirect or not. Forward the redirect result to the child process. From
+ * that moment the nsIParentChannel implementation expects it will be
+ * forwarded all notifications from the 'real' channel.
+ *
+ * Primarilly used by HttpChannelParent::OnRedirectResult and kept as
+ * mActiveChannel and mRedirectChannel in that class.
+ */
+ void completeRedirect(in nsresult succeeded);
+};
diff --git a/netwerk/base/nsIPermission.idl b/netwerk/base/nsIPermission.idl
new file mode 100644
index 0000000000..029f0d8395
--- /dev/null
+++ b/netwerk/base/nsIPermission.idl
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+
+/**
+ * This interface defines a "permission" object,
+ * used to specify allowed/blocked objects from
+ * user-specified sites (cookies, images etc).
+ */
+[scriptable, uuid(bb409a51-2371-4fea-9dc9-b7286a458b8c)]
+interface nsIPermission : nsISupports
+{
+ /**
+ * The principal for which this permission applies.
+ */
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * a case-sensitive ASCII string, indicating the type of permission
+ * (e.g., "cookie", "image", etc).
+ * This string is specified by the consumer when adding a permission
+ * via nsIPermissionManager.
+ * @see nsIPermissionManager
+ */
+ readonly attribute ACString type;
+
+ /**
+ * The permission (see nsIPermissionManager.idl for allowed values)
+ */
+ readonly attribute uint32_t capability;
+
+ /**
+ * The expiration type of the permission (session, time-based or none).
+ * Constants are EXPIRE_*, defined in nsIPermissionManager.
+ * @see nsIPermissionManager
+ */
+ readonly attribute uint32_t expireType;
+
+ /**
+ * The expiration time of the permission (milliseconds since Jan 1 1970
+ * 0:00:00).
+ */
+ readonly attribute int64_t expireTime;
+
+ /**
+ * The last modification time of the permission (milliseconds since Jan 1 1970
+ * 0:00:00).
+ */
+ readonly attribute int64_t modificationTime;
+
+ /**
+ * Test whether a principal would be affected by this permission.
+ *
+ * @param principal the principal to test
+ * @param exactHost If true, only the specific host will be matched,
+ * @see nsIPermissionManager::testExactPermission.
+ * If false, subdomains will also be searched,
+ * @see nsIPermissionManager::testPermission.
+ */
+ boolean matches(in nsIPrincipal principal,
+ in boolean exactHost);
+
+ /**
+ * Test whether a URI would be affected by this permission.
+ * NOTE: This performs matches with default origin attribute values.
+ *
+ * @param uri the uri to test
+ * @param exactHost If true, only the specific host will be matched,
+ * @see nsIPermissionManager::testExactPermission.
+ * If false, subdomains will also be searched,
+ * @see nsIPermissionManager::testPermission.
+ */
+ boolean matchesURI(in nsIURI uri,
+ in boolean exactHost);
+};
diff --git a/netwerk/base/nsIPermissionManager.idl b/netwerk/base/nsIPermissionManager.idl
new file mode 100644
index 0000000000..f234958010
--- /dev/null
+++ b/netwerk/base/nsIPermissionManager.idl
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains an interface to the Permission Manager,
+ * used to persistenly store permissions for different object types (cookies,
+ * images etc) on a site-by-site basis.
+ *
+ * This service broadcasts the following notification when the permission list
+ * is changed:
+ *
+ * topic : "perm-changed" (PERM_CHANGE_NOTIFICATION)
+ * broadcast whenever the permission list changes in some way. there
+ * are four possible data strings for this notification; one
+ * notification will be broadcast for each change, and will involve
+ * a single permission.
+ * subject: an nsIPermission interface pointer representing the permission object
+ * that changed.
+ * data : "deleted"
+ * a permission was deleted. the subject is the deleted permission.
+ * "added"
+ * a permission was added. the subject is the added permission.
+ * "changed"
+ * a permission was changed. the subject is the new permission.
+ * "cleared"
+ * the entire permission list was cleared. the subject is null.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIPermission;
+
+[scriptable, builtinclass, uuid(4dcb3851-eba2-4e42-b236-82d2596fca22)]
+interface nsIPermissionManager : nsISupports
+{
+ /**
+ * Predefined return values for the testPermission method and for
+ * the permission param of the add method
+ * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
+ * default permission when no entry is found for a host, and
+ * should not be used by consumers to indicate otherwise.
+ */
+ const uint32_t UNKNOWN_ACTION = 0;
+ const uint32_t ALLOW_ACTION = 1;
+ const uint32_t DENY_ACTION = 2;
+ const uint32_t PROMPT_ACTION = 3;
+
+ /**
+ * Predefined expiration types for permissions. Permissions can be permanent
+ * (never expire), expire at the end of the session, or expire at a specified
+ * time. Permissions that expire at the end of a session may also have a
+ * specified expiration time.
+ *
+ * EXPIRE_POLICY is a special expiration status. It is set when the permission
+ * is set by reading an enterprise policy. These permissions cannot be overridden.
+ */
+ const uint32_t EXPIRE_NEVER = 0;
+ const uint32_t EXPIRE_SESSION = 1;
+ const uint32_t EXPIRE_TIME = 2;
+ const uint32_t EXPIRE_POLICY = 3;
+
+
+ /**
+ * Get all custom permissions for a given nsIPrincipal. This will return an
+ * enumerator of all permissions which are not set to default and which
+ * belong to the matching principal of the given nsIPrincipal.
+ *
+ * @param principal the URI to get all permissions for
+ */
+ Array<nsIPermission> getAllForPrincipal(in nsIPrincipal principal);
+
+ /**
+ * Get all custom permissions of a specific type, specified with a prefix
+ * string. This will return an array of all permissions which are not set to
+ * default. Also the passed type argument is either equal to or a prefix of
+ * the type of the returned permissions.
+ *
+ * @param prefix the type prefix string
+ */
+ Array<nsIPermission> getAllWithTypePrefix(in ACString prefix);
+
+
+ /**
+ * Get all custom permissions whose type exactly match one of the types defined
+ * in the passed array argument.
+ * This will return an array of all permissions which are not set to default.
+ *
+ * @param types an array of case-sensitive ASCII strings, identifying the
+ * permissions to be matched.
+ */
+ Array<nsIPermission> getAllByTypes(in Array<ACString> types);
+
+ /**
+ * Get all custom permissions of a specific type and that were modified after
+ * the specified date. This will return an array of all permissions which are
+ * not set to default.
+ *
+ * @param type a case-sensitive ASCII string, identifying the permission.
+ * @param since a unix timestamp representing the number of milliseconds from
+ * Jan 1, 1970 00:00:00 UTC.
+ */
+ Array<nsIPermission> getAllByTypeSince(in ACString type, in int64_t since);
+
+ /**
+ * Add permission information for a given principal.
+ * It is internally calling the other add() method using the nsIURI from the
+ * principal.
+ * Passing a system principal will be a no-op because they will always be
+ * granted permissions.
+ */
+ void addFromPrincipal(in nsIPrincipal principal, in ACString type,
+ in uint32_t permission,
+ [optional] in uint32_t expireType,
+ [optional] in int64_t expireTime);
+
+ /**
+ * Add permanent permission information for a given principal in private
+ * browsing.
+ *
+ * Normally permissions in private browsing are cleared at the end of the
+ * session, this method allows you to override this behavior and set
+ * permanent permissions.
+ *
+ * WARNING: setting permanent permissions _will_ leak data in private
+ * browsing. Only use if you understand the consequences and trade-offs. If
+ * you are unsure, |addFromPrincipal| is very likely what you want to use
+ * instead.
+ */
+ void addFromPrincipalAndPersistInPrivateBrowsing(in nsIPrincipal principal,
+ in ACString type,
+ in uint32_t permission);
+
+ /**
+ * Remove permission information for a given principal.
+ * This is internally calling remove() with the host from the principal's URI.
+ * Passing system principal will be a no-op because we never add them to the
+ * database.
+ */
+ void removeFromPrincipal(in nsIPrincipal principal, in ACString type);
+
+ /**
+ * Remove the given permission from the permission manager.
+ *
+ * @param perm a permission obtained from the permission manager.
+ */
+ void removePermission(in nsIPermission perm);
+
+ /**
+ * Clear permission information for all websites.
+ */
+ void removeAll();
+
+ /**
+ * Clear all permission information added since the specified time.
+ */
+ void removeAllSince(in int64_t since);
+
+ /**
+ * Clear all permissions of the passed type.
+ */
+ void removeByType(in ACString type);
+
+ /**
+ * Clear all permissions of the passed type added since the specified time.
+ * @param type a case-sensitive ASCII string, identifying the permission.
+ * @param since a unix timestamp representing the number of milliseconds from
+ * Jan 1, 1970 00:00:00 UTC.
+ */
+ void removeByTypeSince(in ACString type, in int64_t since);
+
+ /**
+ * Test whether the principal has the permission to perform a given action.
+ * System principals will always have permissions granted.
+ * This function will perform a pref lookup to permissions.default.<type>
+ * if the specific permission type is part of the whitelist for that functionality.
+ */
+ uint32_t testPermissionFromPrincipal(in nsIPrincipal principal,
+ in ACString type);
+
+ /**
+ * Test whether the principal has the permission to perform a given action.
+ * This requires an exact hostname match. Subdomain principals do not match
+ * permissions of base domains.
+ * System principals will always have permissions granted.
+ * This function will perform a pref lookup to permissions.default.<type>
+ * if the specific permission type is part of the whitelist for that functionality.
+ */
+ uint32_t testExactPermissionFromPrincipal(in nsIPrincipal principal,
+ in ACString type);
+
+ /**
+ * Test whether a website has permission to perform the given action
+ * ignoring active sessions.
+ * System principals will always have permissions granted.
+ * This function will perform a pref lookup to permissions.default.<type>
+ * if the specific permission type is part of the whitelist for that functionality.
+ *
+ * @param principal the principal
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testExactPermanentPermission(in nsIPrincipal principal,
+ in ACString type);
+
+ /**
+ * Get the permission object associated with the given principal and action.
+ * @param principal The principal
+ * @param type A case-sensitive ASCII string identifying the consumer
+ * @param exactHost If true, only the specific host will be matched.
+ * If false, base domains of the principal will also
+ * be searched.
+ * @returns The matching permission object, or null if no matching object
+ * was found. No matching object is equivalent to UNKNOWN_ACTION.
+ * @note Clients in general should prefer the test* methods unless they
+ * need to know the specific stored details.
+ * @note This method will always return null for the system principal.
+ */
+ nsIPermission getPermissionObject(in nsIPrincipal principal,
+ in ACString type,
+ in boolean exactHost);
+
+ /**
+ * Returns all stored permissions.
+ * @return an array of nsIPermission objects
+ */
+ readonly attribute Array<nsIPermission> all;
+
+ /**
+ * Remove all permissions that will match the origin pattern.
+ */
+ void removePermissionsWithAttributes(in AString patternAsJSON);
+};
+
+%{ C++
+#define NS_PERMISSIONMANAGER_CONTRACTID "@mozilla.org/permissionmanager;1"
+
+#define PERM_CHANGE_NOTIFICATION "perm-changed"
+%}
diff --git a/netwerk/base/nsIPrivateBrowsingChannel.idl b/netwerk/base/nsIPrivateBrowsingChannel.idl
new file mode 100644
index 0000000000..3bc822f018
--- /dev/null
+++ b/netwerk/base/nsIPrivateBrowsingChannel.idl
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface is implemented by channels which support overriding the
+ * privacy state of the channel.
+ *
+ * This interface must be used only from the XPCOM main thread.
+ */
+[scriptable, uuid(df702bb0-55b8-11e2-bcfd-0800200c9a66)]
+interface nsIPrivateBrowsingChannel : nsISupports
+{
+ /**
+ * Determine whether the channel is tied to a private browsing window.
+ *
+ * This value can be set only before the channel is opened. Setting it
+ * after that does not have any effect. This value overrides the privacy
+ * state of the channel, which means that if you call this method, then
+ * the loadGroup and load context will no longer be consulted when we
+ * need to know the private mode status for a channel.
+ *
+ * Note that this value is only meant to be used when the channel's privacy
+ * status cannot be obtained from the loadGroup or load context (for
+ * example, when the channel is not associated with any loadGroup or load
+ * context.) Setting this value directly should be avoided if possible.
+ *
+ * Implementations must enforce the ordering semantics of this function by
+ * raising errors if setPrivate is called on a channel which has a loadGroup
+ * and/or callbacks that implement nsILoadContext, or if the loadGroup
+ * or notificationCallbacks are set after setPrivate has been called.
+ *
+ * @param aPrivate whether the channel should be opened in private mode.
+ */
+ void setPrivate(in boolean aPrivate);
+
+ /**
+ * States whether the channel is in private browsing mode. This may either
+ * happen because the channel is opened from a private mode context or
+ * when the mode is explicitly set with ::setPrivate().
+ *
+ * This attribute is equivalent to NS_UsePrivateBrowsing(), but scriptable.
+ */
+ readonly attribute boolean isChannelPrivate;
+
+ /*
+ * This function is used to determine whether the channel's private mode
+ * has been overridden by a call to setPrivate. It is intended to be used
+ * by NS_UsePrivateBrowsing(), and you should not call it directly.
+ *
+ * @param aValue the overridden value. This will only be set if the function
+ * returns true.
+ */
+ [noscript] boolean isPrivateModeOverriden(out boolean aValue);
+};
diff --git a/netwerk/base/nsIProgressEventSink.idl b/netwerk/base/nsIProgressEventSink.idl
new file mode 100644
index 0000000000..5420239e4c
--- /dev/null
+++ b/netwerk/base/nsIProgressEventSink.idl
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIRequest;
+
+/**
+ * nsIProgressEventSink
+ *
+ * This interface is used to asynchronously convey channel status and progress
+ * information that is generally not critical to the processing of the channel.
+ * The information is intended to be displayed to the user in some meaningful
+ * way.
+ *
+ * An implementation of this interface can be passed to a channel via the
+ * channel's notificationCallbacks attribute. See nsIChannel for more info.
+ *
+ * The channel will begin passing notifications to the progress event sink
+ * after its asyncOpen method has been called. Notifications will cease once
+ * the channel calls its listener's onStopRequest method or once the channel
+ * is canceled (via nsIRequest::cancel).
+ *
+ * NOTE: This interface is actually not specific to channels and may be used
+ * with other implementations of nsIRequest.
+ */
+[scriptable, uuid(87d55fba-cb7e-4f38-84c1-5c6c2b2a55e9)]
+interface nsIProgressEventSink : nsISupports
+{
+ /**
+ * Called to notify the event sink that progress has occurred for the
+ * given request.
+ *
+ * @param aRequest
+ * the request being observed (may QI to nsIChannel).
+ * @param aProgress
+ * numeric value in the range 0 to aProgressMax indicating the
+ * number of bytes transfered thus far.
+ * @param aProgressMax
+ * numeric value indicating maximum number of bytes that will be
+ * transfered (or -1 if total is unknown).
+ */
+ void onProgress(in nsIRequest aRequest,
+ in long long aProgress,
+ in long long aProgressMax);
+
+ /**
+ * Called to notify the event sink with a status message for the given
+ * request.
+ *
+ * @param aRequest
+ * the request being observed (may QI to nsIChannel).
+ * @param aStatus
+ * status code (not necessarily an error code) indicating the
+ * state of the channel (usually the state of the underlying
+ * transport). see nsISocketTransport for socket specific status
+ * codes.
+ * @param aStatusArg
+ * status code argument to be used with the string bundle service
+ * to convert the status message into localized, human readable
+ * text. the meaning of this parameter is specific to the value
+ * of the status code. for socket status codes, this parameter
+ * indicates the host:port associated with the status code.
+ */
+ void onStatus(in nsIRequest aRequest,
+ in nsresult aStatus,
+ in wstring aStatusArg);
+
+};
diff --git a/netwerk/base/nsIPrompt.idl b/netwerk/base/nsIPrompt.idl
new file mode 100644
index 0000000000..6fbed3355d
--- /dev/null
+++ b/netwerk/base/nsIPrompt.idl
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is the prompt interface which can be used without knowlege of a
+ * parent window. The parentage is hidden by the GetInterface though
+ * which it is gotten. This interface is identical to nsIPromptService
+ * but without the parent nsIDOMWindow parameter. See nsIPromptService
+ * for all documentation.
+ *
+ * Accesskeys can be attached to buttons and checkboxes by inserting
+ * an & before the accesskey character. For a real &, use && instead.
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(a63f70c0-148b-11d3-9333-00104ba0fd40)]
+interface nsIPrompt : nsISupports
+{
+ void alert(in wstring dialogTitle,
+ in wstring text);
+
+ void alertCheck(in wstring dialogTitle,
+ in wstring text,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean confirm(in wstring dialogTitle,
+ in wstring text);
+
+ boolean confirmCheck(in wstring dialogTitle,
+ in wstring text,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ const unsigned long BUTTON_POS_0 = 1;
+ const unsigned long BUTTON_POS_1 = 1 << 8;
+ const unsigned long BUTTON_POS_2 = 1 << 16;
+
+ const unsigned long BUTTON_TITLE_OK = 1;
+ const unsigned long BUTTON_TITLE_CANCEL = 2;
+ const unsigned long BUTTON_TITLE_YES = 3;
+ const unsigned long BUTTON_TITLE_NO = 4;
+ const unsigned long BUTTON_TITLE_SAVE = 5;
+ const unsigned long BUTTON_TITLE_DONT_SAVE = 6;
+ const unsigned long BUTTON_TITLE_REVERT = 7;
+
+ const unsigned long BUTTON_TITLE_IS_STRING = 127;
+
+ const unsigned long BUTTON_POS_0_DEFAULT = 0 << 24;
+ const unsigned long BUTTON_POS_1_DEFAULT = 1 << 24;
+ const unsigned long BUTTON_POS_2_DEFAULT = 2 << 24;
+
+ /* used for security dialogs, buttons are initially disabled */
+ const unsigned long BUTTON_DELAY_ENABLE = 1 << 26;
+
+ const unsigned long SHOW_SPINNER = 1 << 27;
+
+ const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) +
+ (BUTTON_TITLE_CANCEL * BUTTON_POS_1);
+ const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) +
+ (BUTTON_TITLE_NO * BUTTON_POS_1);
+
+
+ // Indicates whether a prompt should be shown in-content, on tab level or as a separate window
+ const unsigned long MODAL_TYPE_CONTENT = 1;
+ const unsigned long MODAL_TYPE_TAB = 2;
+ const unsigned long MODAL_TYPE_WINDOW = 3;
+ // Like MODAL_TYPE_WINDOW, but shown inside a parent window (with similar
+ // styles as _TAB and _CONTENT types) rather than as a new window:
+ const unsigned long MODAL_TYPE_INTERNAL_WINDOW = 4;
+
+ int32_t confirmEx(in wstring dialogTitle,
+ in wstring text,
+ in unsigned long buttonFlags,
+ in wstring button0Title,
+ in wstring button1Title,
+ in wstring button2Title,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean prompt(in wstring dialogTitle,
+ in wstring text,
+ inout wstring value,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean promptPassword(in wstring dialogTitle,
+ in wstring text,
+ inout wstring password);
+
+ boolean promptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ inout wstring username,
+ inout wstring password);
+
+ boolean select(in wstring dialogTitle,
+ in wstring text,
+ in Array<AString> selectList,
+ out long outSelection);
+};
diff --git a/netwerk/base/nsIProtocolHandler.idl b/netwerk/base/nsIProtocolHandler.idl
new file mode 100644
index 0000000000..890f3ef02f
--- /dev/null
+++ b/netwerk/base/nsIProtocolHandler.idl
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include "nsCOMPtr.h"
+
+/**
+ * Protocol handlers are registered with XPCOM under the following CONTRACTID prefix:
+ */
+#define NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "@mozilla.org/network/protocol;1?name="
+/**
+ * For example, "@mozilla.org/network/protocol;1?name=http"
+ */
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+#define IS_ORIGIN_IS_FULL_SPEC_DEFINED 1
+#endif
+%}
+
+interface nsIURI;
+interface nsIChannel;
+interface nsILoadInfo;
+
+/**
+ * nsIProtocolHandlerWithDynamicFlags
+ *
+ * Protocols that wish to return different flags depending on the URI should
+ * implement this interface.
+ */
+[scriptable, builtinclass, uuid(65a8e823-0591-4fc0-a56a-03265e0a4ce8)]
+interface nsIProtocolHandlerWithDynamicFlags : nsISupports
+{
+ /*
+ * Returns protocol flags for the given URI, which may be different from the
+ * flags for another URI of the same scheme.
+ *
+ * Only DYNAMIC_URI_FLAGS may be different from the registered flags for the
+ * protocol handler.
+ */
+ unsigned long getFlagsForURI(in nsIURI aURI);
+};
+
+/**
+ * nsIProtocolHandler
+ */
+[scriptable, uuid(a87210e6-7c8c-41f7-864d-df809015193e)]
+interface nsIProtocolHandler : nsISupports
+{
+ /**
+ * The scheme of this protocol (e.g., "file").
+ */
+ readonly attribute ACString scheme;
+
+ /**
+ * Constructs a new channel from the given URI for this protocol handler and
+ * sets the loadInfo for the constructed channel.
+ */
+ nsIChannel newChannel(in nsIURI aURI, in nsILoadInfo aLoadinfo);
+
+ /**
+ * Allows a protocol to override blacklisted ports.
+ *
+ * This method will be called when there is an attempt to connect to a port
+ * that is blacklisted. For example, for most protocols, port 25 (Simple Mail
+ * Transfer) is banned. When a URI containing this "known-to-do-bad-things"
+ * port number is encountered, this function will be called to ask if the
+ * protocol handler wants to override the ban.
+ */
+ boolean allowPort(in long port, in string scheme);
+
+
+ /**************************************************************************
+ * Constants for the protocol flags (the first is the default mask, the
+ * others are deviations):
+ *
+ * NOTE: Protocol flags are provided when the protocol handler is
+ * registered, either through a static component or dynamically with
+ * `nsIIOService.registerProtocolHandler`.
+ *
+ * NOTE: Implementation must ignore any flags they do not understand.
+ */
+
+ /**
+ * standard full URI with authority component and concept of relative
+ * URIs (http, ...)
+ */
+ const unsigned long URI_STD = 0;
+
+ /**
+ * no concept of relative URIs (about, javascript, finger, ...)
+ */
+ const unsigned long URI_NORELATIVE = (1<<0);
+
+ /**
+ * no authority component (file, ...)
+ */
+ const unsigned long URI_NOAUTH = (1<<1);
+
+ /**
+ * This protocol handler can be proxied via a proxy (socks or http)
+ * (e.g., irc, smtp, http, etc.). If the protocol supports transparent
+ * proxying, the handler should implement nsIProxiedProtocolHandler.
+ *
+ * If it supports only HTTP proxying, then it need not support
+ * nsIProxiedProtocolHandler, but should instead set the ALLOWS_PROXY_HTTP
+ * flag (see below).
+ *
+ * @see nsIProxiedProtocolHandler
+ */
+ const unsigned long ALLOWS_PROXY = (1<<2);
+
+ /**
+ * This protocol handler can be proxied using a http proxy (e.g., http,
+ * etc.). nsIIOService::newChannelFromURI will feed URIs from this
+ * protocol handler to the HTTP protocol handler instead. This flag is
+ * ignored if ALLOWS_PROXY is not set.
+ */
+ const unsigned long ALLOWS_PROXY_HTTP = (1<<3);
+
+ /**
+ * The URIs for this protocol have no inherent security context, so
+ * documents loaded via this protocol should inherit the security context
+ * from the document that loads them.
+ */
+ const unsigned long URI_INHERITS_SECURITY_CONTEXT = (1<<4);
+
+ /**
+ * "Automatic" loads that would replace the document (e.g. <meta> refresh,
+ * certain types of XLinks, possibly other loads that the application
+ * decides are not user triggered) are not allowed if the originating (NOT
+ * the target) URI has this protocol flag. Note that the decision as to
+ * what constitutes an "automatic" load is made externally, by the caller
+ * of nsIScriptSecurityManager::CheckLoadURI. See documentation for that
+ * method for more information.
+ *
+ * A typical protocol that might want to set this flag is a protocol that
+ * shows highly untrusted content in a viewing area that the user expects
+ * to have a lot of control over, such as an e-mail reader.
+ */
+ const unsigned long URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT = (1<<5);
+
+ /**
+ * +-------------------------------------------------------------------+
+ * | |
+ * | ALL PROTOCOL HANDLERS MUST SET ONE OF THE FOLLOWING FIVE FLAGS. |
+ * | |
+ * +-------------------------------------------------------------------+
+ *
+ * * URI_LOADABLE_BY_ANYONE
+ * * URI_DANGEROUS_TO_LOAD
+ * * URI_IS_UI_RESOURCE
+ * * URI_IS_LOCAL_FILE
+ * * URI_LOADABLE_BY_SUBSUMERS
+ *
+ * These flags are used to determine who is allowed to load URIs for this
+ * protocol. Note that if a URI is nested, only the flags for the
+ * innermost URI matter. See nsINestedURI.
+ *
+ * If none of these five flags are set, the ContentSecurityManager will
+ * deny the load.
+ */
+
+ /**
+ * The URIs for this protocol can be loaded by anyone. For example, any
+ * website should be allowed to trigger a load of a URI for this protocol.
+ * Web-safe protocols like "http" should set this flag.
+ */
+ const unsigned long URI_LOADABLE_BY_ANYONE = (1<<6);
+
+ /**
+ * The URIs for this protocol are UNSAFE if loaded by untrusted (web)
+ * content and may only be loaded by privileged code (for example, code
+ * which has the system principal). Various internal protocols should set
+ * this flag.
+ */
+ const unsigned long URI_DANGEROUS_TO_LOAD = (1<<7);
+
+ /**
+ * The URIs for this protocol point to resources that are part of the
+ * application's user interface. There are cases when such resources may
+ * be made accessible to untrusted content such as web pages, so this is
+ * less restrictive than URI_DANGEROUS_TO_LOAD but more restrictive than
+ * URI_LOADABLE_BY_ANYONE. See the documentation for
+ * nsIScriptSecurityManager::CheckLoadURI.
+ */
+ const unsigned long URI_IS_UI_RESOURCE = (1<<8);
+
+ /**
+ * Loading of URIs for this protocol from other origins should only be
+ * allowed if those origins should have access to the local filesystem.
+ * It's up to the application to decide what origins should have such
+ * access. Protocols like "file" that point to local data should set this
+ * flag.
+ */
+ const unsigned long URI_IS_LOCAL_FILE = (1<<9);
+
+ /**
+ * The URIs for this protocol can be loaded only by callers with a
+ * principal that subsumes this uri. For example, privileged code and
+ * websites that are same origin as this uri.
+ */
+ const unsigned long URI_LOADABLE_BY_SUBSUMERS = (1<<10);
+
+ /**
+ * Channels using this protocol never call OnDataAvailable
+ * on the listener passed to AsyncOpen and they therefore
+ * do not return any data that we can use.
+ */
+ const unsigned long URI_DOES_NOT_RETURN_DATA = (1<<11);
+
+ /**
+ * URIs for this protocol are considered to be local resources. This could
+ * be a local file (URI_IS_LOCAL_FILE), a UI resource (URI_IS_UI_RESOURCE),
+ * or something else that would not hit the network.
+ */
+ const unsigned long URI_IS_LOCAL_RESOURCE = (1<<12);
+
+ /**
+ * URIs for this protocol execute script when they are opened.
+ */
+ const unsigned long URI_OPENING_EXECUTES_SCRIPT = (1<<13);
+
+ /**
+ * Loading channels from this protocol has side-effects that make
+ * it unsuitable for saving to a local file.
+ */
+ const unsigned long URI_NON_PERSISTABLE = (1<<14);
+
+ /**
+ * URIs for this protocol require the webapps permission on the principal
+ * when opening URIs for a different domain. See bug#773886
+ */
+ const unsigned long URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM = (1<<15);
+
+ /**
+ * Channels for this protocol don't need to spin the event loop to handle
+ * Open() and reads on the resulting stream.
+ */
+ const unsigned long URI_SYNC_LOAD_IS_OK = (1<<16);
+
+ /**
+ * All the origins whose URI has this scheme are considered potentially
+ * trustworthy.
+ * Per the SecureContext spec, https: and wss: should be considered
+ * a priori secure, and implementations may consider other,
+ * implementation-specific URI schemes as secure.
+ */
+ const unsigned long URI_IS_POTENTIALLY_TRUSTWORTHY = (1<<17);
+
+ /**
+ * This URI may be fetched and the contents are visible to anyone. This is
+ * semantically equivalent to the resource being served with all-access CORS
+ * headers. This is only used in MV2 Extensions and should not otherwise
+ * be used.
+ */
+ const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 18);
+
+ /**
+ * If this flag is set, then the origin for this protocol is the full URI
+ * spec, not just the scheme + host + port.
+ *
+ * Note: this is not supported in Firefox. It is currently only available
+ * in Thunderbird and SeaMonkey.
+ */
+ const unsigned long ORIGIN_IS_FULL_SPEC = (1 << 19);
+
+ /**
+ * If this flag is set, the URI does not always allow content using the same
+ * protocol to link to it.
+ */
+ const unsigned long URI_SCHEME_NOT_SELF_LINKABLE = (1 << 20);
+
+ /**
+ * The URIs for this protocol can be loaded by extensions.
+ */
+ const unsigned long URI_LOADABLE_BY_EXTENSIONS = (1 << 21);
+
+ /**
+ * The URIs for this protocol can not be loaded into private contexts.
+ */
+ const unsigned long URI_DISALLOW_IN_PRIVATE_CONTEXT = (1 << 22);
+
+ /**
+ * This protocol handler forbids accessing cookies e.g. for mail related
+ * protocols. Only used in Mailnews (comm-central).
+ */
+ const unsigned long URI_FORBIDS_COOKIE_ACCESS = (1 << 23);
+
+ /**
+ * This is an extension web accessible uri that is loadable if checked
+ * against an allowlist using ExtensionPolicyService::SourceMayLoadExtensionURI.
+ */
+ const unsigned long WEBEXT_URI_WEB_ACCESSIBLE = (1 << 24);
+
+ /**
+ * This URI has a webexposed origin, meaning the URI has a non-null origin
+ * See https://url.spec.whatwg.org/#origin
+ */
+ const unsigned long URI_HAS_WEB_EXPOSED_ORIGIN = (1 << 25);
+
+ /**
+ * Flags which are allowed to be different from the static flags when
+ * returned from `nsIProtocolHandlerWithDynamicFlags::getFlagsForURI`.
+ *
+ * All other flags must match the flags provided when the protocol handler
+ * was registered.
+ */
+ const unsigned long DYNAMIC_URI_FLAGS =
+ URI_LOADABLE_BY_ANYONE | URI_DANGEROUS_TO_LOAD |
+ URI_IS_POTENTIALLY_TRUSTWORTHY | URI_FETCHABLE_BY_ANYONE |
+ URI_LOADABLE_BY_EXTENSIONS | URI_DISALLOW_IN_PRIVATE_CONTEXT |
+ WEBEXT_URI_WEB_ACCESSIBLE | URI_HAS_WEB_EXPOSED_ORIGIN;
+};
diff --git a/netwerk/base/nsIProtocolProxyCallback.idl b/netwerk/base/nsIProtocolProxyCallback.idl
new file mode 100644
index 0000000000..96c2181eca
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyCallback.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIProxyInfo;
+interface nsICancelable;
+
+/**
+ * This interface serves as a closure for nsIProtocolProxyService's
+ * asyncResolve method.
+ */
+[scriptable, uuid(fbb6eff6-0cc2-4d99-8d6f-0a12b462bdeb)]
+interface nsIProtocolProxyCallback : nsISupports
+{
+ /**
+ * This method is called when proxy info is available or when an error
+ * in the proxy resolution occurs.
+ *
+ * @param aRequest
+ * The value returned from asyncResolve.
+ * @param aChannel
+ * The channel passed to asyncResolve.
+ * @param aProxyInfo
+ * The resulting proxy info or null if there is no associated proxy
+ * info for aURI. As with the result of nsIProtocolProxyService's
+ * resolve method, a null result implies that a direct connection
+ * should be used.
+ * @param aStatus
+ * The status of the callback. This is a failure code if the request
+ * could not be satisfied, in which case the value of aStatus
+ * indicates the reason for the failure and aProxyInfo will be null.
+ */
+ void onProxyAvailable(in nsICancelable aRequest,
+ in nsIChannel aChannel,
+ in nsIProxyInfo aProxyInfo,
+ in nsresult aStatus);
+};
diff --git a/netwerk/base/nsIProtocolProxyFilter.idl b/netwerk/base/nsIProtocolProxyFilter.idl
new file mode 100644
index 0000000000..a771af5f43
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyFilter.idl
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIProtocolProxyService;
+interface nsIProxyInfo;
+interface nsIURI;
+
+/**
+ * Recipient of the result of implementers of nsIProtocolProxy(Channel)Filter
+ * allowing the proxyinfo be provided asynchronously.
+ */
+[scriptable, uuid(009E6C3F-FB64-40C5-8093-F1495C64773E)]
+interface nsIProxyProtocolFilterResult : nsISupports
+{
+ /**
+ * It's mandatory to call this method exactly once when the applyFilter()
+ * implementation doesn't throw and to not call it when applyFilter() does
+ * throw.
+ *
+ * It's mandatory to call this method on the same thread as the call to
+ * applyFilter() has been made on.
+ *
+ * Following the above conditions, can be called either from within
+ * applyFilter() or asynchronouly any time later.
+ */
+ void onProxyFilterResult(in nsIProxyInfo aProxy);
+};
+
+/**
+ * This interface is used to apply filters to the proxies selected for a given
+ * URI. Use nsIProtocolProxyService::registerFilter to hook up instances of
+ * this interface. See also nsIProtocolProxyChannelFilter.
+ */
+[scriptable, uuid(f424abd3-32b4-456c-9f45-b7e3376cb0d1)]
+interface nsIProtocolProxyFilter : nsISupports
+{
+ /**
+ * This method is called to apply proxy filter rules for the given URI
+ * and proxy object (or list of proxy objects).
+ *
+ * @param aURI
+ * The URI for which these proxy settings apply.
+ * @param aProxy
+ * The proxy (or list of proxies) that would be used by default for
+ * the given URI. This may be null.
+ *
+ * @param aCallback
+ * An object that the implementer is obligated to call on with
+ * the result (from within applyFilter() or asynchronously) when
+ * applyFilter didn't throw. The argument passed to onProxyFilterResult
+ * is the proxy (or list of proxies) that should be used in place of
+ * aProxy. This can be just be aProxy if the filter chooses not to
+ * modify the proxy. It can also be null to indicate that a direct
+ * connection should be used. Use nsIProtocolProxyService.newProxyInfo
+ * to construct nsIProxyInfo objects.
+ */
+ void applyFilter(in nsIURI aURI, in nsIProxyInfo aProxy,
+ in nsIProxyProtocolFilterResult aCallback);
+};
+
+/**
+ * This interface is used to apply filters to the proxies selected for a given
+ * channel. Use nsIProtocolProxyService::registerChannelFilter to hook up instances of
+ * this interface. See also nsIProtocolProxyFilter.
+ */
+[scriptable, uuid(245b0880-82c5-4e6e-be6d-bc586aa55a90)]
+interface nsIProtocolProxyChannelFilter : nsISupports
+{
+ /**
+ * This method is called to apply proxy filter rules for the given channel
+ * and proxy object (or list of proxy objects).
+ *
+ * @param aChannel
+ * The channel for which these proxy settings apply.
+ * @param aProxy
+ * The proxy (or list of proxies) that would be used by default for
+ * the given channel. This may be null.
+ *
+ * @param aCallback
+ * An object that the implementer is obligated to call on with
+ * the result (from within applyFilter() or asynchronously) when
+ * applyFilter didn't throw. The argument passed to onProxyFilterResult
+ * is the proxy (or list of proxies) that should be used in place of
+ * aProxy. This can be just be aProxy if the filter chooses not to
+ * modify the proxy. It can also be null to indicate that a direct
+ * connection should be used. Use nsIProtocolProxyService.newProxyInfo
+ * to construct nsIProxyInfo objects.
+ */
+ void applyFilter(in nsIChannel aChannel, in nsIProxyInfo aProxy,
+ in nsIProxyProtocolFilterResult aCallback);
+};
diff --git a/netwerk/base/nsIProtocolProxyService.idl b/netwerk/base/nsIProtocolProxyService.idl
new file mode 100644
index 0000000000..9bd65f5084
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyService.idl
@@ -0,0 +1,330 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICancelable;
+interface nsIProtocolProxyCallback;
+interface nsIProtocolProxyFilter;
+interface nsIProtocolProxyChannelFilter;
+interface nsIProxyInfo;
+interface nsIChannel;
+interface nsIURI;
+interface nsISerialEventTarget;
+
+[scriptable, uuid(77984234-aad5-47fc-a412-03398c2134a5)]
+interface nsIProxyConfigChangedCallback : nsISupports
+{
+ /**
+ * Called when one of the following conditions are changed.
+ * 1. System proxy settings changed.
+ * 2. A proxy filter is registered or unregistered.
+ * 3. Proxy related prefs changed.
+ */
+ void onProxyConfigChanged();
+};
+
+/**
+ * nsIProtocolProxyService provides methods to access information about
+ * various network proxies.
+ */
+[scriptable, builtinclass, uuid(ef57c8b6-e09d-4cd4-9222-2a5d2402e15d)]
+interface nsIProtocolProxyService : nsISupports
+{
+ /** Flag 1 << 0 is unused **/
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to prefer the SOCKS proxy
+ * to HTTP ones.
+ */
+ const unsigned long RESOLVE_PREFER_SOCKS_PROXY = 1 << 1;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to not analyze the uri's
+ * scheme specific proxy. When this flag is set the main HTTP proxy is the
+ * preferred one.
+ *
+ * NOTE: if RESOLVE_PREFER_SOCKS_PROXY is set then the SOCKS proxy is
+ * the preferred one.
+ *
+ * NOTE: if RESOLVE_PREFER_HTTPS_PROXY is set then the HTTPS proxy
+ * is the preferred one.
+ */
+ const unsigned long RESOLVE_IGNORE_URI_SCHEME = 1 << 2;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to request to prefer the HTTPS proxy
+ * to the others HTTP ones.
+ *
+ * NOTE: RESOLVE_PREFER_SOCKS_PROXY takes precedence over this flag.
+ *
+ * NOTE: This flag implies RESOLVE_IGNORE_URI_SCHEME.
+ */
+ const unsigned long RESOLVE_PREFER_HTTPS_PROXY =
+ (1 << 3) | RESOLVE_IGNORE_URI_SCHEME;
+
+ /**
+ * When the proxy configuration is manual this flag may be passed to the
+ * resolve and asyncResolve methods to that all methods will be tunneled via
+ * CONNECT through the http proxy.
+ */
+ const unsigned long RESOLVE_ALWAYS_TUNNEL = (1 << 4);
+
+ /**
+ * This method returns via callback a nsIProxyInfo instance that identifies
+ * a proxy to be used for the given channel. Otherwise, this method returns
+ * null indicating that a direct connection should be used.
+ *
+ * @param aChannelOrURI
+ * The channel for which a proxy is to be found, or, if no channel is
+ * available, a URI indicating the same. This method will return
+ * NS_ERROR_NOINTERFACE if this argument isn't either an nsIURI or an
+ * nsIChannel.
+ * @param aFlags
+ * A bit-wise combination of the RESOLVE_ flags defined above. Pass
+ * 0 to specify the default behavior. Any additional bits that do
+ * not correspond to a RESOLVE_ flag are reserved for future use.
+ * @param aCallback
+ * The object to be notified when the result is available.
+ * @param aMainThreadTarget
+ * A labelled event target for dispatching runnables to main thread.
+ *
+ * @return An object that can be used to cancel the asychronous operation.
+ * If canceled, the cancelation status (aReason) will be forwarded
+ * to the callback's onProxyAvailable method via the aStatus param.
+ *
+ * NOTE: If this proxy is unavailable, getFailoverForProxy may be called
+ * to determine the correct secondary proxy to be used.
+ *
+ * NOTE: If the protocol handler for the given URI supports
+ * nsIProxiedProtocolHandler, then the nsIProxyInfo instance returned from
+ * resolve may be passed to the newProxiedChannel method to create a
+ * nsIChannel to the given URI that uses the specified proxy.
+ *
+ * NOTE: However, if the nsIProxyInfo type is "http", then it means that
+ * the given URI should be loaded using the HTTP protocol handler, which
+ * also supports nsIProxiedProtocolHandler.
+ *
+ * @see nsIProxiedProtocolHandler::newProxiedChannel
+ */
+ nsICancelable asyncResolve(
+ in nsISupports aChannelOrURI, in unsigned long aFlags,
+ in nsIProtocolProxyCallback aCallback,
+ [optional] in nsISerialEventTarget aMainThreadTarget);
+
+ /**
+ * This method may be called to construct a nsIProxyInfo instance from
+ * the given parameters. This method may be useful in conjunction with
+ * nsISocketTransportService::createTransport for creating, for example,
+ * a SOCKS connection.
+ *
+ * @param aType
+ * The proxy type. This is a string value that identifies the proxy
+ * type. Standard values include:
+ * "http" - specifies a HTTP proxy
+ * "https" - specifies HTTP proxying over TLS connection to proxy
+ * "socks" - specifies a SOCKS version 5 proxy
+ * "socks4" - specifies a SOCKS version 4 proxy
+ * "direct" - specifies a direct connection (useful for failover)
+ * The type name is case-insensitive. Other string values may be
+ * possible, and new types may be defined by a future version of
+ * this interface.
+ * @param aHost
+ * The proxy hostname or IP address.
+ * @param aPort
+ * The proxy port.
+ * @param aFlags
+ * Flags associated with this connection. See nsIProxyInfo.idl
+ * for currently defined flags.
+ * @param aFailoverTimeout
+ * Specifies the length of time (in seconds) to ignore this proxy if
+ * this proxy fails. Pass UINT32_MAX to specify the default
+ * timeout value, causing nsIProxyInfo::failoverTimeout to be
+ * assigned the default value.
+ * @param aFailoverProxy
+ * Specifies the next proxy to try if this proxy fails. This
+ * parameter may be null.
+ */
+ nsIProxyInfo newProxyInfo(in ACString aType, in AUTF8String aHost,
+ in long aPort,
+ in ACString aProxyAuthorizationHeader,
+ in ACString aConnectionIsolationKey,
+ in unsigned long aFlags,
+ in unsigned long aFailoverTimeout,
+ in nsIProxyInfo aFailoverProxy);
+
+ /**
+ * This method may be called to construct a nsIProxyInfo instance for
+ * with the specified username and password.
+ * Currently implemented for SOCKS proxies only.
+ * @param aType
+ * The proxy type. This is a string value that identifies the proxy
+ * type. Standard values include:
+ * "socks" - specifies a SOCKS version 5 proxy
+ * "socks4" - specifies a SOCKS version 4 proxy
+ * The type name is case-insensitive. Other string values may be
+ * possible, and new types may be defined by a future version of
+ * this interface.
+ * @param aHost
+ * The proxy hostname or IP address.
+ * @param aPort
+ * The proxy port.
+ * @param aUsername
+ * The proxy username
+ * @param aPassword
+ * The proxy password
+ * @param aFlags
+ * Flags associated with this connection. See nsIProxyInfo.idl
+ * for currently defined flags.
+ * @param aFailoverTimeout
+ * Specifies the length of time (in seconds) to ignore this proxy if
+ * this proxy fails. Pass UINT32_MAX to specify the default
+ * timeout value, causing nsIProxyInfo::failoverTimeout to be
+ * assigned the default value.
+ * @param aFailoverProxy
+ * Specifies the next proxy to try if this proxy fails. This
+ * parameter may be null.
+ */
+ nsIProxyInfo newProxyInfoWithAuth(in ACString aType, in AUTF8String aHost,
+ in long aPort,
+ in AUTF8String aUsername, in AUTF8String aPassword,
+ in ACString aProxyAuthorizationHeader,
+ in ACString aConnectionIsolationKey,
+ in unsigned long aFlags,
+ in unsigned long aFailoverTimeout,
+ in nsIProxyInfo aFailoverProxy);
+
+ /**
+ * If the proxy identified by aProxyInfo is unavailable for some reason,
+ * this method may be called to access an alternate proxy that may be used
+ * instead. As a side-effect, this method may affect future result values
+ * from resolve/asyncResolve as well as from getFailoverForProxy.
+ *
+ * @param aProxyInfo
+ * The proxy that was unavailable.
+ * @param aURI
+ * The URI that was originally passed to resolve/asyncResolve.
+ * @param aReason
+ * The error code corresponding to the proxy failure. This value
+ * may be used to tune the delay before this proxy is used again.
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if there is no alternate proxy available.
+ */
+ nsIProxyInfo getFailoverForProxy(in nsIProxyInfo aProxyInfo,
+ in nsIURI aURI,
+ in nsresult aReason);
+
+ /**
+ * This method may be used to register a proxy filter instance. Each proxy
+ * filter is registered with an associated position that determines the
+ * order in which the filters are applied (starting from position 0). When
+ * resolve/asyncResolve is called, it generates a list of proxies for the
+ * given URI, and then it applies the proxy filters. The filters have the
+ * opportunity to modify the list of proxies.
+ *
+ * If two filters register for the same position, then the filters will be
+ * visited in the order in which they were registered.
+ *
+ * If the filter is already registered, then its position will be updated.
+ *
+ * After filters have been run, any disabled or disallowed proxies will be
+ * removed from the list. A proxy is disabled if it had previously failed-
+ * over to another proxy (see getFailoverForProxy). A proxy is disallowed,
+ * for example, if it is a HTTP proxy and the nsIProtocolHandler for the
+ * queried URI does not permit proxying via HTTP.
+ *
+ * If a nsIProtocolHandler disallows all proxying, then filters will never
+ * have a chance to intercept proxy requests for such URLs.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyFilter instance to be registered.
+ * @param aPosition
+ * The position of the filter.
+ *
+ * NOTE: It is possible to construct filters that compete with one another
+ * in undesirable ways. This API does not attempt to protect against such
+ * problems. It is recommended that any extensions that choose to call
+ * this method make their position value configurable at runtime (perhaps
+ * via the preferences service).
+ */
+ void registerFilter(in nsIProtocolProxyFilter aFilter,
+ in unsigned long aPosition);
+
+ /**
+ * Similar to registerFilter, but accepts an nsIProtocolProxyChannelFilter,
+ * which selects proxies according to channel rather than URI.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyChannelFilter instance to be registered.
+ * @param aPosition
+ * The position of the filter.
+ */
+ void registerChannelFilter(in nsIProtocolProxyChannelFilter aFilter,
+ in unsigned long aPosition);
+
+ /**
+ * This method may be used to unregister a proxy filter instance. All
+ * filters will be automatically unregistered at XPCOM shutdown.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyFilter instance to be unregistered.
+ */
+ void unregisterFilter(in nsIProtocolProxyFilter aFilter);
+
+ /**
+ * This method may be used to unregister a proxy channel filter instance. All
+ * filters will be automatically unregistered at XPCOM shutdown.
+ *
+ * @param aFilter
+ * The nsIProtocolProxyChannelFilter instance to be unregistered.
+ */
+ void unregisterChannelFilter(in nsIProtocolProxyChannelFilter aFilter);
+
+ /**
+ * This method is used to register a nsIProxyConfigChangedCallback.
+ *
+ * @param aCallback
+ * The aCallback instance to be registered.
+ */
+ void addProxyConfigCallback(in nsIProxyConfigChangedCallback aCallback);
+
+ /**
+ * This method is used to unregister a nsIProxyConfigChangedCallback.
+ *
+ * @param aCallback
+ * The aCallback instance to be unregistered.
+ */
+ void removeProxyConfigCallback(in nsIProxyConfigChangedCallback aCallback);
+
+
+ /**
+ * This method is used internal only. Called when proxy config is changed.
+ */
+ void notifyProxyConfigChangedInternal();
+
+ /**
+ * These values correspond to the possible integer values for the
+ * network.proxy.type preference.
+ */
+ const unsigned long PROXYCONFIG_DIRECT = 0;
+ const unsigned long PROXYCONFIG_MANUAL = 1;
+ const unsigned long PROXYCONFIG_PAC = 2;
+ const unsigned long PROXYCONFIG_WPAD = 4;
+ const unsigned long PROXYCONFIG_SYSTEM = 5;
+
+ /**
+ * This attribute specifies the current type of proxy configuration.
+ */
+ readonly attribute unsigned long proxyConfigType;
+
+ /**
+ * True if there is a PAC download in progress.
+ */
+ [notxpcom, nostdcall] readonly attribute boolean isPACLoading;
+};
diff --git a/netwerk/base/nsIProtocolProxyService2.idl b/netwerk/base/nsIProtocolProxyService2.idl
new file mode 100644
index 0000000000..fe031bac40
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyService2.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolProxyService.idl"
+
+/**
+ * An extension of nsIProtocolProxyService
+ */
+[scriptable, builtinclass, uuid(b2e5b2c0-e21e-4845-b336-be6d60a38951)]
+interface nsIProtocolProxyService2 : nsIProtocolProxyService
+{
+ /**
+ * Call this method to cause the PAC file (if any is configured) to be
+ * reloaded. The PAC file is loaded asynchronously.
+ */
+ void reloadPAC();
+
+ /**
+ * This method is identical to asyncResolve() except:
+ * - it only accepts an nsIChannel, not an nsIURI;
+ * - it may execute the callback function immediately (i.e from the stack
+ * of asyncResolve2()) if it is immediately ready to run.
+ * The nsICancelable return value will be null in that case.
+ */
+ nsICancelable asyncResolve2(
+ in nsIChannel aChannel, in unsigned long aFlags,
+ in nsIProtocolProxyCallback aCallback,
+ [optional] in nsISerialEventTarget aMainThreadTarget);
+};
diff --git a/netwerk/base/nsIProxiedChannel.idl b/netwerk/base/nsIProxiedChannel.idl
new file mode 100644
index 0000000000..c6796b9f9d
--- /dev/null
+++ b/netwerk/base/nsIProxiedChannel.idl
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIProxyInfo;
+
+/**
+ * An interface for accessing the proxy info that a channel was
+ * constructed with.
+ *
+ * @see nsIProxiedProtocolHandler
+ */
+[scriptable, uuid(6238f134-8c3f-4354-958f-dfd9d54a4446)]
+interface nsIProxiedChannel : nsISupports
+{
+ /**
+ * Gets the proxy info the channel was constructed with. null or a
+ * proxyInfo with type "direct" mean no proxy.
+ *
+ * The returned proxy info must not be modified.
+ */
+ readonly attribute nsIProxyInfo proxyInfo;
+
+ /**
+ * The HTTP response code returned from the proxy to the CONNECT method.
+ * The response code is only available when we get the response from
+ * the proxy server, so this value is known in and after OnStartRequest.
+ *
+ * If CONNECT method is not used, httpProxyConnectResponseCode is always -1.
+ * After OnStartRequest, httpProxyConnectResponseCode is the real HTTP
+ * response code or 0 if we can't reach to the proxy.
+ */
+ readonly attribute int32_t httpProxyConnectResponseCode;
+};
diff --git a/netwerk/base/nsIProxiedProtocolHandler.idl b/netwerk/base/nsIProxiedProtocolHandler.idl
new file mode 100644
index 0000000000..d9436e94a7
--- /dev/null
+++ b/netwerk/base/nsIProxiedProtocolHandler.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+interface nsIChannel;
+interface nsIURI;
+interface nsIProxyInfo;
+interface nsILoadInfo;
+
+[scriptable, builtinclass, uuid(3756047a-fa2b-4b45-9948-3b5f8fc375e7)]
+interface nsIProxiedProtocolHandler : nsIProtocolHandler
+{
+ /** Create a new channel with the given proxyInfo
+ *
+ * @param uri the channel uri
+ * @param proxyInfo any proxy information that has already been determined,
+ * or null if channel should later determine the proxy on its own using
+ * proxyResolveFlags/proxyURI
+ * @param proxyResolveFlags used if the proxy is later determined
+ * from nsIProtocolProxyService::asyncResolve
+ * @param proxyURI used if the proxy is later determined from
+ * nsIProtocolProxyService::asyncResolve with this as the proxyURI name.
+ * Generally this is the same as uri (or null which has the same
+ * effect), except in the case of websockets which wants to bootstrap
+ * to an http:// channel but make its proxy determination based on
+ * a ws:// uri.
+ * @param aLoadInfo used to evaluate who initated the resource request.
+ */
+ nsIChannel newProxiedChannel(in nsIURI uri, in nsIProxyInfo proxyInfo,
+ in unsigned long proxyResolveFlags,
+ in nsIURI proxyURI,
+ in nsILoadInfo aLoadInfo);
+};
diff --git a/netwerk/base/nsIProxyInfo.idl b/netwerk/base/nsIProxyInfo.idl
new file mode 100644
index 0000000000..dc762fbd2d
--- /dev/null
+++ b/netwerk/base/nsIProxyInfo.idl
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface identifies a proxy server.
+ */
+[scriptable, uuid(63fff172-2564-4138-96c6-3ae7d245fbed)]
+interface nsIProxyInfo : nsISupports
+{
+ /**
+ * This attribute specifies the hostname of the proxy server.
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * This attribute specifies the port number of the proxy server.
+ */
+ readonly attribute long port;
+
+ /**
+ * This attribute specifies the type of the proxy server as an ASCII string.
+ *
+ * Some special values for this attribute include (but are not limited to)
+ * the following:
+ * "http" HTTP proxy (or SSL CONNECT for HTTPS)
+ * "https" HTTP proxying over TLS connection to proxy
+ * "socks" SOCKS v5 proxy
+ * "socks4" SOCKS v4 proxy
+ * "direct" no proxy
+ * "unknown" unknown proxy (see nsIProtocolProxyService::resolve)
+ *
+ * A future version of this interface may define additional types.
+ */
+ readonly attribute ACString type;
+
+ /**
+ * This attribute specifies flags that modify the proxy type. The value of
+ * this attribute is the bit-wise combination of the Proxy Flags defined
+ * below. Any undefined bits are reserved for future use.
+ */
+ readonly attribute unsigned long flags;
+
+ /**
+ * This attribute specifies flags that were used by nsIProxyProtocolService when
+ * creating this ProxyInfo element.
+ */
+ readonly attribute unsigned long resolveFlags;
+
+ /**
+ * Specifies a proxy username.
+ */
+ readonly attribute ACString username;
+
+ /**
+ * Specifies a proxy password.
+ */
+ readonly attribute ACString password;
+
+ /**
+ * This attribute specifies the failover timeout in seconds for this proxy.
+ * If a nsIProxyInfo is reported as failed via nsIProtocolProxyService::
+ * getFailoverForProxy, then the failed proxy will not be used again for this
+ * many seconds.
+ */
+ readonly attribute unsigned long failoverTimeout;
+
+ /**
+ * This attribute specifies the proxy to failover to when this proxy fails.
+ */
+ attribute nsIProxyInfo failoverProxy;
+
+ /**
+ * Specifies an ID related to the source of this proxy configuration. If
+ * it is created in response to an extension API, it will be the extension ID.
+ */
+ attribute ACString sourceId;
+
+ /**
+ * Any non-empty value will be passed directly as Proxy-Authorization header
+ * value for the CONNECT request attempt. However, this header set on the
+ * resource request itself takes precedence.
+ */
+ readonly attribute ACString proxyAuthorizationHeader;
+
+ /**
+ * An optional key used for additional isolation of this proxy connection.
+ */
+ readonly attribute ACString connectionIsolationKey;
+
+ /****************************************************************************
+ * The following "Proxy Flags" may be bit-wise combined to construct the
+ * flags attribute defined on this interface. All unspecified bits are
+ * reserved for future use.
+ */
+
+ /**
+ * This flag is set if the proxy is to perform name resolution itself. If
+ * this is the case, the hostname is used in some fashion, and we shouldn't
+ * do any form of DNS lookup ourselves.
+ */
+ const unsigned short TRANSPARENT_PROXY_RESOLVES_HOST = 1 << 0;
+};
diff --git a/netwerk/base/nsIRandomGenerator.idl b/netwerk/base/nsIRandomGenerator.idl
new file mode 100644
index 0000000000..576483b549
--- /dev/null
+++ b/netwerk/base/nsIRandomGenerator.idl
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include <type_traits>
+%}
+
+/**
+ * Interface used to generate random data.
+ *
+ * @threadsafe
+ */
+[scriptable, uuid(2362d97a-747a-4576-8863-697667309209)]
+interface nsIRandomGenerator : nsISupports {
+ /**
+ * Generates the specified amount of random bytes.
+ *
+ * @param aLength
+ * The length of the data to generate.
+ * @param aBuffer
+ * A buffer that contains random bytes of size aLength.
+ */
+ void generateRandomBytes(in unsigned long aLength,
+ [retval, array, size_is(aLength)] out octet aBuffer);
+
+ /**
+ * Fills aBuffer with random bytes.
+ *
+ * @param aBuffer
+ * A buffer to fill with random bytes.
+ * @param aLength
+ * Length of aBuffer.
+ */
+ void generateRandomBytesInto([array, size_is(aLength)] in octet aBuffer,
+ in unsigned long aLength);
+
+%{C++
+ template<typename T>
+ std::enable_if_t<!std::is_pointer_v<T>, nsresult> GenerateRandomBytesInto(T& aResult) {
+ return GenerateRandomBytesInto(reinterpret_cast<uint8_t*>(&aResult), sizeof(T));
+ }
+%}
+};
diff --git a/netwerk/base/nsIRedirectChannelRegistrar.idl b/netwerk/base/nsIRedirectChannelRegistrar.idl
new file mode 100644
index 0000000000..26dfdf3c6f
--- /dev/null
+++ b/netwerk/base/nsIRedirectChannelRegistrar.idl
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIParentChannel;
+
+/**
+ * Used on the chrome process as a service to join channel implementation
+ * and parent IPC protocol side under a unique id. Provides this way a generic
+ * communication while redirecting to various protocols.
+ *
+ * See also nsIChildChannel and nsIParentChannel.
+ */
+
+[scriptable, uuid (efa36ea2-5b07-46fc-9534-a5acb8b77b72)]
+interface nsIRedirectChannelRegistrar : nsISupports
+{
+ /**
+ * Register the redirect target channel. The passed id needs to be a
+ * unique ID for that channel (see `nsContentUtils::GenerateLoadIdentifier`).
+ *
+ * Primarily used in ParentChannelListener::AsyncOnChannelRedirect to get
+ * a channel id sent to the HttpChannelChild being redirected.
+ */
+ void registerChannel(in nsIChannel channel, in uint64_t id);
+
+ /**
+ * First, search for the channel registered under the id. If found return
+ * it. Then, register under the same id the parent side of IPC protocol
+ * to let it be later grabbed back by the originator of the redirect and
+ * notifications from the real channel could be forwarded to this parent
+ * channel.
+ *
+ * Primarily used in parent side of an IPC protocol implementation
+ * in reaction to nsIChildChannel.connectParent(id) called from the child
+ * process.
+ */
+ nsIChannel linkChannels(in uint64_t id, in nsIParentChannel channel);
+
+ /**
+ * Returns back the channel previously registered under the ID with
+ * registerChannel method.
+ *
+ * Primarilly used in chrome IPC side of protocols when attaching a redirect
+ * target channel to an existing 'real' channel implementation.
+ */
+ nsIChannel getRegisteredChannel(in uint64_t id);
+
+ /**
+ * Returns the stream listener that shall be attached to the redirect target
+ * channel, all notification from the redirect target channel will be
+ * forwarded to this stream listener.
+ *
+ * Primarilly used in HttpChannelParent::OnRedirectResult callback to grab
+ * the created parent side of the channel and forward notifications to it.
+ */
+ nsIParentChannel getParentChannel(in uint64_t id);
+
+ /**
+ * To not force all channel implementations to support weak reference
+ * consumers of this service must ensure release of registered channels them
+ * self. This releases both the real and parent channel registered under
+ * the id.
+ *
+ * Primarilly used in HttpChannelParent::OnRedirectResult callback.
+ */
+ void deregisterChannels(in uint64_t id);
+};
diff --git a/netwerk/base/nsIRedirectHistoryEntry.idl b/netwerk/base/nsIRedirectHistoryEntry.idl
new file mode 100644
index 0000000000..50086574f8
--- /dev/null
+++ b/netwerk/base/nsIRedirectHistoryEntry.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+
+/**
+ * This nsIRedirectHistoryEntry defines an interface for specifying channel
+ * redirect information
+ */
+
+[scriptable, uuid(133b2905-0eba-411c-a8bb-f59787142aa2)]
+interface nsIRedirectHistoryEntry : nsISupports
+{
+ /**
+ * The principal of this redirect entry
+ */
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * The referring URI of this redirect entry. This may be null.
+ */
+ readonly attribute nsIURI referrerURI;
+
+ /**
+ * The remote address of this redirect entry.
+ */
+ readonly attribute ACString remoteAddress;
+
+};
diff --git a/netwerk/base/nsIRedirectResultListener.idl b/netwerk/base/nsIRedirectResultListener.idl
new file mode 100644
index 0000000000..57241350a4
--- /dev/null
+++ b/netwerk/base/nsIRedirectResultListener.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(85cd2640-e91e-41ac-bdca-1dbf10dc131e)]
+interface nsIRedirectResultListener : nsISupports
+{
+ /**
+ * When an HTTP redirect has been processed (either successfully or not)
+ * nsIHttpChannel will call this function if its callbacks implement this
+ * interface.
+ *
+ * @param proceeding
+ * Indicated whether the redirect will be proceeding, or not (i.e.
+ * has been canceled, or failed).
+ */
+ void onRedirectResult(in nsresult status);
+};
diff --git a/netwerk/base/nsIRequest.idl b/netwerk/base/nsIRequest.idl
new file mode 100644
index 0000000000..8fa9462136
--- /dev/null
+++ b/netwerk/base/nsIRequest.idl
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+#include "nsString.h"
+%}
+
+interface nsILoadGroup;
+
+typedef unsigned long nsLoadFlags;
+
+/**
+ * nsIRequest
+ */
+[scriptable, uuid(ef6bfbd2-fd46-48d8-96b7-9f8f0fd387fe)]
+interface nsIRequest : nsISupports
+{
+ /**
+ * The name of the request. Often this is the URI of the request.
+ */
+ readonly attribute AUTF8String name;
+
+ /**
+ * Indicates whether the request is pending. nsIRequest::isPending is
+ * true when there is an outstanding asynchronous event that will make
+ * the request no longer be pending. Requests do not necessarily start
+ * out pending; in some cases, requests have to be explicitly initiated
+ * (e.g. nsIChannel implementations are only pending once asyncOpen
+ * returns successfully).
+ *
+ * Requests can become pending multiple times during their lifetime.
+ *
+ * @return TRUE if the request has yet to reach completion.
+ * @return FALSE if the request has reached completion (e.g., after
+ * OnStopRequest has fired).
+ * @note Suspended requests are still considered pending.
+ */
+ boolean isPending();
+
+ /**
+ * The error status associated with the request.
+ */
+ readonly attribute nsresult status;
+
+ /**
+ * Cancels the current request. This will close any open input or
+ * output streams and terminate any async requests. Users should
+ * normally pass NS_BINDING_ABORTED, although other errors may also
+ * be passed. The error passed in will become the value of the
+ * status attribute.
+ *
+ * Implementations must not send any notifications (e.g. via
+ * nsIRequestObserver) synchronously from this function. Similarly,
+ * removal from the load group (if any) must also happen asynchronously.
+ *
+ * Requests that use nsIStreamListener must not call onDataAvailable
+ * anymore after cancel has been called.
+ *
+ * @param aStatus the reason for canceling this request.
+ *
+ * NOTE: most nsIRequest implementations expect aStatus to be a
+ * failure code; however, some implementations may allow aStatus to
+ * be a success code such as NS_OK. In general, aStatus should be
+ * a failure code.
+ */
+ void cancel(in nsresult aStatus);
+
+ /**
+ * Suspends the current request. This may have the effect of closing
+ * any underlying transport (in order to free up resources), although
+ * any open streams remain logically opened and will continue delivering
+ * data when the transport is resumed.
+ *
+ * Calling cancel() on a suspended request must not send any
+ * notifications (such as onstopRequest) until the request is resumed.
+ *
+ * NOTE: some implementations are unable to immediately suspend, and
+ * may continue to deliver events already posted to an event queue. In
+ * general, callers should be capable of handling events even after
+ * suspending a request.
+ */
+ void suspend();
+
+ /**
+ * Resumes the current request. This may have the effect of re-opening
+ * any underlying transport and will resume the delivery of data to
+ * any open streams.
+ */
+ void resume();
+
+ /**
+ * The load group of this request. While pending, the request is a
+ * member of the load group. It is the responsibility of the request
+ * to implement this policy.
+ */
+ attribute nsILoadGroup loadGroup;
+
+ /**
+ * The load flags of this request. Bits 0-15 are reserved.
+ *
+ * When added to a load group, this request's load flags are merged with
+ * the load flags of the load group.
+ */
+ attribute nsLoadFlags loadFlags;
+
+ /**
+ * Mask defining the bits reserved for nsIRequest LoadFlags
+ */
+ const unsigned long LOAD_REQUESTMASK = 0xFFFF;
+
+ /**************************************************************************
+ * Listed below are the various load flags which may be or'd together.
+ */
+
+ /**
+ * No special load flags:
+ */
+ const unsigned long LOAD_NORMAL = 0;
+
+ /**
+ * Do not deliver status notifications to the nsIProgressEventSink and
+ * do not block the loadgroup from completing (should this load belong to one).
+ * Note: Progress notifications will still be delivered.
+ */
+ const unsigned long LOAD_BACKGROUND = 1 << 0;
+
+ /**
+ * This flag marks the request as being made to load the data for an html
+ * <object> tag. This means that the LOAD_DOCUMENT_URI flag may be set after
+ * the channel has been provided with the MIME type.
+ */
+ const unsigned long LOAD_HTML_OBJECT_DATA = 1 << 1;
+
+ /**
+ * This flag marks the request as belonging to a document that requires access
+ * to the document.cookies API.
+ */
+ const unsigned long LOAD_DOCUMENT_NEEDS_COOKIE = 1 << 2;
+
+ cenum TRRMode : 32 {
+ TRR_DEFAULT_MODE = 0,
+ TRR_DISABLED_MODE = 1,
+ TRR_FIRST_MODE = 2,
+ TRR_ONLY_MODE = 3
+ };
+
+ /**
+ * These methods encode/decode the TRR mode to/from the loadFlags.
+ * Helper methods Get/SetTRRModeImpl are provided so implementations don't
+ * need to duplicate code.
+ *
+ * Requests with TRR_DEFAULT_MODE will use the mode indicated by the pref
+ * - see network.trr.mode in all.js
+ * Requests with TRR_DISABLED_MODE will always use native DNS, even if the
+ * pref is set to mode3 (TRR-only).
+ * Requests with TRR_FIRST_MODE will first use TRR then fallback to regular
+ * DNS, unless TRR is disabled by setting the pref to mode5, parental
+ * control being enabled, or the domain being in the exclusion list.
+ * Requests with TRR_ONLY_MODE will only use TRR, unless not allowed by
+ * the same conditions mentioned above.
+ */
+ nsIRequest_TRRMode getTRRMode();
+ void setTRRMode(in nsIRequest_TRRMode mode);
+
+ %{C++
+ inline TRRMode GetTRRMode() {
+ TRRMode mode = TRR_DEFAULT_MODE;
+ GetTRRMode(&mode);
+ return mode;
+ }
+
+ inline nsresult GetTRRModeImpl(nsIRequest::TRRMode* aTRRMode) {
+ NS_ENSURE_ARG_POINTER(aTRRMode);
+ nsLoadFlags flags = nsIRequest::LOAD_NORMAL;
+ nsresult rv = GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aTRRMode = static_cast<nsIRequest::TRRMode>(
+ (flags & nsIRequest::LOAD_TRR_MASK) >> 3);
+ return NS_OK;
+ }
+
+ inline nsresult SetTRRModeImpl(nsIRequest::TRRMode aTRRMode) {
+ MOZ_ASSERT(aTRRMode <= 3, "invalid value");
+ nsLoadFlags flags = nsIRequest::LOAD_NORMAL;
+ nsresult rv = GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ flags = (flags & ~nsIRequest::LOAD_TRR_MASK) | (aTRRMode << 3);
+ return SetLoadFlags(flags);
+ }
+ %}
+
+ /**
+ * These two bits encode the TRR mode.
+ * Do not get/set manually, rather use the getTRRMode/setTRRMode methods.
+ */
+ const unsigned long LOAD_TRR_MASK = (1 << 3) | (1 << 4);
+ const unsigned long LOAD_TRR_DISABLED_MODE = 1 << 3;
+ const unsigned long LOAD_TRR_FIRST_MODE = 1 << 4;
+ const unsigned long LOAD_TRR_ONLY_MODE = (1 << 3) | (1 << 4);
+
+ void cancelWithReason(in nsresult aStatus, in ACString aReason);
+ attribute ACString canceledReason;
+
+ %{C++
+ protected:
+ nsCString mCanceledReason;
+
+ public:
+ inline nsresult SetCanceledReasonImpl(const nsACString& aReason) {
+ if (mCanceledReason.IsEmpty()) {
+ mCanceledReason.Assign(aReason);
+ }
+
+ return NS_OK;
+ }
+
+ inline nsresult CancelWithReasonImpl(nsresult aStatus,
+ const nsACString& aReason) {
+ SetCanceledReasonImpl(aReason);
+ return Cancel(aStatus);
+ }
+
+ inline nsresult GetCanceledReasonImpl(nsACString& aReason) {
+ aReason.Assign(mCanceledReason);
+ return NS_OK;
+ }
+ %}
+
+ /**
+ * This is used for a temporary workaround for a web-compat issue. The flag is
+ * only set on CORS preflight request to allowed sending client certificates
+ * on a connection for an anonymous request.
+ */
+ const long LOAD_ANONYMOUS_ALLOW_CLIENT_CERT = 1 << 5;
+
+ /**************************************************************************
+ * The following flags control the flow of data into the cache.
+ */
+
+ /**
+ * This flag prevents caching of any kind. It does not, however, prevent
+ * cached content from being used to satisfy this request.
+ */
+ const unsigned long INHIBIT_CACHING = 1 << 7;
+
+ /**
+ * This flag prevents caching on disk (or other persistent media), which
+ * may be needed to preserve privacy.
+ */
+ const unsigned long INHIBIT_PERSISTENT_CACHING = 1 << 8;
+
+ /**************************************************************************
+ * The following flags control what happens when the cache contains data
+ * that could perhaps satisfy this request. They are listed in descending
+ * order of precidence.
+ */
+
+ /**
+ * Force an end-to-end download of content data from the origin server.
+ * This flag is used for a shift-reload.
+ */
+ const unsigned long LOAD_BYPASS_CACHE = 1 << 9;
+
+ /**
+ * Attempt to force a load from the cache, bypassing ALL validation logic
+ * (note: this is stronger than VALIDATE_NEVER, which still validates for
+ * certain conditions).
+ *
+ * If the resource is not present in cache, it will be loaded from the
+ * network. Combine this flag with LOAD_ONLY_FROM_CACHE if you wish to
+ * perform cache-only loads without validation checks.
+ *
+ * This flag is used when browsing via history. It is not recommended for
+ * normal browsing as it may likely violate reasonable assumptions made by
+ * the server and confuse users.
+ */
+ const unsigned long LOAD_FROM_CACHE = 1 << 10;
+
+ /**
+ * The following flags control the frequency of cached content validation
+ * when neither LOAD_BYPASS_CACHE or LOAD_FROM_CACHE are set. By default,
+ * cached content is automatically validated if necessary before reuse.
+ *
+ * VALIDATE_ALWAYS forces validation of any cached content independent of
+ * its expiration time (unless it is https with Cache-Control: immutable)
+ *
+ * VALIDATE_NEVER disables validation of cached content, unless it arrived
+ * with the "Cache: no-store" header, or arrived via HTTPS with the
+ * "Cache: no-cache" header.
+ *
+ * VALIDATE_ONCE_PER_SESSION disables validation of expired content,
+ * provided it has already been validated (at least once) since the start
+ * of this session.
+ *
+ * NOTE TO IMPLEMENTORS:
+ * These flags are intended for normal browsing, and they should therefore
+ * not apply to content that must be validated before each use. Consider,
+ * for example, a HTTP response with a "Cache-control: no-cache" header.
+ * According to RFC2616, this response must be validated before it can
+ * be taken from a cache. Breaking this requirement could result in
+ * incorrect and potentially undesirable side-effects.
+ */
+ const unsigned long VALIDATE_ALWAYS = 1 << 11;
+ const unsigned long VALIDATE_NEVER = 1 << 12;
+ const unsigned long VALIDATE_ONCE_PER_SESSION = 1 << 13;
+
+ /**
+ * When set, this flag indicates that no user-specific data should be added
+ * to the request when opened. This means that things like authorization
+ * tokens or cookie headers should not be added.
+ */
+ const unsigned long LOAD_ANONYMOUS = 1 << 14;
+
+ /**
+ * When set, this flag indicates that caches of network connections,
+ * particularly HTTP persistent connections, should not be used.
+ * Use this together with LOAD_INITIAL_DOCUMENT_URI as otherwise it has no
+ * effect.
+ */
+ const unsigned long LOAD_FRESH_CONNECTION = 1 << 15;
+
+ // Note that all flags are taken, the rest of the flags
+ // are located in nsIChannel and nsICachingChannel
+};
diff --git a/netwerk/base/nsIRequestContext.idl b/netwerk/base/nsIRequestContext.idl
new file mode 100644
index 0000000000..6c9f265af2
--- /dev/null
+++ b/netwerk/base/nsIRequestContext.idl
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+// Forward-declare mozilla::net::SpdyPushCache
+namespace mozilla {
+namespace net {
+class SpdyPushCache;
+}
+}
+%}
+
+interface nsILoadGroup;
+interface nsIChannel;
+interface nsIStreamListener;
+
+[ptr] native SpdyPushCachePtr(mozilla::net::SpdyPushCache);
+
+/**
+ * Requests capable of tail-blocking must implement this
+ * interfaces (typically channels).
+ * If the request is tail-blocked, it will be held in its request
+ * context queue until unblocked.
+ */
+[uuid(7EB361D4-37A5-42C9-AFAE-F6C88FE7C394)]
+interface nsIRequestTailUnblockCallback : nsISupports
+{
+ /**
+ * Called when the requests is unblocked and proceed.
+ * @param result
+ * NS_OK - the request is OK to go, unblocking is not
+ * caused by cancelation of the request.
+ * any error - the request must behave as it were canceled
+ * with the result as status.
+ */
+ void onTailUnblock(in nsresult aResult);
+};
+
+/**
+ * The nsIRequestContext is used to maintain state about connections
+ * that are in some way associated with each other (often by being part
+ * of the same load group) and how they interact with blocking items like
+ * HEAD css/js loads.
+ *
+ * This used to be known as nsILoadGroupConnectionInfo and nsISchedulingContext.
+ */
+[uuid(658e3e6e-8633-4b1a-8d66-fa9f72293e63)]
+interface nsIRequestContext : nsISupports
+{
+ /**
+ * A unique identifier for this request context
+ */
+ [notxpcom, nostdcall] readonly attribute unsigned long long ID;
+
+ /**
+ * Called by the associated document when its load starts. This resets
+ * context's internal states.
+ */
+ void beginLoad();
+
+ /**
+ * Called when the associated document notified the DOMContentLoaded event.
+ */
+ void DOMContentLoaded();
+
+ /**
+ * Number of active blocking transactions associated with this context
+ */
+ readonly attribute unsigned long blockingTransactionCount;
+
+ /**
+ * Increase the number of active blocking transactions associated
+ * with this context by one.
+ */
+ void addBlockingTransaction();
+
+ /**
+ * Decrease the number of active blocking transactions associated
+ * with this context by one. The return value is the number of remaining
+ * blockers.
+ */
+ unsigned long removeBlockingTransaction();
+
+ /**
+ * This gives out a weak pointer to the push cache.
+ * The nsIRequestContext implementation owns the cache
+ * and will destroy it when overwritten or when the context
+ * ends.
+ */
+ [notxpcom,nostdcall] attribute SpdyPushCachePtr spdyPushCache;
+
+ /**
+ * Increases/decrease the number of non-tailed requests in this context.
+ * If the count drops to zero, all tail-blocked callbacks are notified
+ * shortly after that to be unblocked.
+ */
+ void addNonTailRequest();
+ void removeNonTailRequest();
+
+ /**
+ * If the request context is in tail-blocked state, the callback
+ * is queued and result is true. The callback will be notified
+ * about tail-unblocking or when the request context is canceled.
+ */
+ [must_use] boolean isContextTailBlocked(in nsIRequestTailUnblockCallback callback);
+
+ /**
+ * Called when the request is sitting in the tail queue but has been
+ * canceled before untailing. This just removes the request from the
+ * queue so that it is not notified on untail and not referenced.
+ */
+ void cancelTailedRequest(in nsIRequestTailUnblockCallback request);
+
+ /**
+ * This notifies all queued tail-blocked requests, they will be notified
+ * aResult and released afterwards. Called by the load group when
+ * it's canceled.
+ */
+ void cancelTailPendingRequests(in nsresult aResult);
+};
+
+/**
+ * The nsIRequestContextService is how anyone gets access to a request
+ * context when they haven't been explicitly given a strong reference to an
+ * existing one. It is responsible for creating and handing out strong
+ * references to nsIRequestContexts, but only keeps weak references itself.
+ * The shared request context will go away once no one else is keeping a
+ * reference to it. If you ask for a request context that has no one else
+ * holding a reference to it, you'll get a brand new request context. Anyone
+ * who asks for the same request context while you're holding a reference
+ * will get a reference to the same request context you have.
+ */
+[uuid(7fcbf4da-d828-4acc-b144-e5435198f727)]
+interface nsIRequestContextService : nsISupports
+{
+ /**
+ * Get an existing request context from its ID
+ */
+ nsIRequestContext getRequestContext(in unsigned long long id);
+ /**
+ * Shorthand to get request context from a load group
+ */
+ nsIRequestContext getRequestContextFromLoadGroup(in nsILoadGroup lg);
+
+ /**
+ * Create a new request context
+ */
+ nsIRequestContext newRequestContext();
+
+ /**
+ * Remove an existing request context from its ID
+ */
+ void removeRequestContext(in unsigned long long id);
+};
diff --git a/netwerk/base/nsIRequestObserver.idl b/netwerk/base/nsIRequestObserver.idl
new file mode 100644
index 0000000000..674e923a32
--- /dev/null
+++ b/netwerk/base/nsIRequestObserver.idl
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIRequest;
+
+/**
+ * nsIRequestObserver
+ */
+[scriptable, uuid(fd91e2e0-1481-11d3-9333-00104ba0fd40)]
+interface nsIRequestObserver : nsISupports
+{
+ /**
+ * Called to signify the beginning of an asynchronous request.
+ *
+ * @param aRequest request being observed
+ *
+ * An exception thrown from onStartRequest has the side-effect of
+ * causing the request to be canceled.
+ *
+ * Note: if this request is an nsIMultiPartChannelListener then
+ * OnStartRequest may be called multiple times.
+ */
+ void onStartRequest(in nsIRequest aRequest);
+
+ /**
+ * Called to signify the end of an asynchronous request. This
+ * call is always preceded by a call to onStartRequest.
+ *
+ * @param aRequest request being observed
+ * @param aStatusCode reason for stopping (NS_OK if completed successfully)
+ *
+ * An exception thrown from onStopRequest is generally ignored.
+ *
+ * Note: if this request is an nsIMultiPartChannelListener then
+ * OnStopRequest may be called multiple times.
+ */
+ void onStopRequest(in nsIRequest aRequest,
+ in nsresult aStatusCode);
+};
diff --git a/netwerk/base/nsIRequestObserverProxy.idl b/netwerk/base/nsIRequestObserverProxy.idl
new file mode 100644
index 0000000000..7b79f53427
--- /dev/null
+++ b/netwerk/base/nsIRequestObserverProxy.idl
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequestObserver.idl"
+
+interface nsIEventTarget;
+
+/**
+ * A request observer proxy is used to ship data over to another thread
+ * specified by the thread's dispatch target. The "true" request observer's
+ * methods are invoked on the other thread.
+ *
+ * This interface only provides the initialization needed after construction.
+ * Otherwise, these objects are used simply as nsIRequestObserver's.
+ */
+[scriptable, uuid(c2b06151-1bf8-4eef-aea9-1532f12f5a10)]
+interface nsIRequestObserverProxy : nsIRequestObserver
+{
+ /**
+ * Initializes an nsIRequestObserverProxy.
+ *
+ * @param observer - receives observer notifications on the main thread
+ * @param context - the context argument that will be passed to OnStopRequest
+ * and OnStartRequest. This has to be stored permanently on
+ * initialization because it sometimes can't be
+ * AddRef/Release'd off-main-thread.
+ */
+ void init(in nsIRequestObserver observer, in nsISupports context);
+};
diff --git a/netwerk/base/nsIResumableChannel.idl b/netwerk/base/nsIResumableChannel.idl
new file mode 100644
index 0000000000..6737e8bf9d
--- /dev/null
+++ b/netwerk/base/nsIResumableChannel.idl
@@ -0,0 +1,39 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+[scriptable, uuid(4ad136fa-83af-4a22-a76e-503642c0f4a8)]
+interface nsIResumableChannel : nsISupports {
+ /**
+ * Prepare this channel for resuming. The request will not start until
+ * asyncOpen or open is called. Calling resumeAt after open or asyncOpen
+ * has been called has undefined behaviour.
+ *
+ * @param startPos the starting offset, in bytes, to use to download
+ * @param entityID information about the file, to match before obtaining
+ * the file. Pass an empty string to use anything.
+ *
+ * During OnStartRequest, this channel will have a status of
+ * NS_ERROR_NOT_RESUMABLE if the file cannot be resumed, eg because the
+ * server doesn't support this. This error may occur even if startPos
+ * is 0, so that the front end can warn the user.
+ * Similarly, the status of this channel during OnStartRequest may be
+ * NS_ERROR_ENTITY_CHANGED, which indicates that the entity has changed,
+ * as indicated by a changed entityID.
+ * In both of these cases, no OnDataAvailable will be called, and
+ * OnStopRequest will immediately follow with the same status code.
+ */
+ void resumeAt(in unsigned long long startPos,
+ in ACString entityID);
+
+ /**
+ * The entity id for this URI. Available after OnStartRequest.
+ * @throw NS_ERROR_NOT_RESUMABLE if this load is not resumable.
+ */
+ readonly attribute ACString entityID;
+};
diff --git a/netwerk/base/nsISecCheckWrapChannel.idl b/netwerk/base/nsISecCheckWrapChannel.idl
new file mode 100644
index 0000000000..21f4d0c290
--- /dev/null
+++ b/netwerk/base/nsISecCheckWrapChannel.idl
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * nsISecCheckWrapChannel
+ * Describes an XPCOM component used to wrap channels for performing
+ * security checks. Channels wrapped inside this class can use
+ * this interface to query the wrapped inner channel.
+ */
+
+[scriptable, uuid(9446c5d5-c9fb-4a6e-acf9-ca4fc666efe0)]
+interface nsISecCheckWrapChannel : nsISupports
+{
+ /**
+ * Returns the wrapped channel inside this class.
+ */
+ readonly attribute nsIChannel innerChannel;
+
+};
diff --git a/netwerk/base/nsISecureBrowserUI.idl b/netwerk/base/nsISecureBrowserUI.idl
new file mode 100644
index 0000000000..3984310d93
--- /dev/null
+++ b/netwerk/base/nsISecureBrowserUI.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsITransportSecurityInfo;
+
+[scriptable, builtinclass, uuid(718c662a-f810-4a80-a6c9-0b1810ecade2)]
+interface nsISecureBrowserUI : nsISupports
+{
+ readonly attribute unsigned long state;
+ readonly attribute bool isSecureContext;
+ readonly attribute nsITransportSecurityInfo secInfo;
+};
+
+%{C++
+#define NS_SECURE_BROWSER_UI_CONTRACTID "@mozilla.org/secure_browser_ui;1"
+%}
diff --git a/netwerk/base/nsISensitiveInfoHiddenURI.idl b/netwerk/base/nsISensitiveInfoHiddenURI.idl
new file mode 100644
index 0000000000..abb3f082b9
--- /dev/null
+++ b/netwerk/base/nsISensitiveInfoHiddenURI.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(a5761968-6e1a-4f2d-8191-ec749602b178)]
+interface nsISensitiveInfoHiddenURI : nsISupports
+{
+ /**
+ * Returns the spec attribute with sensitive information hidden. This will
+ * only affect uri with password. The password part of uri will be
+ * transformed into "****".
+ */
+ AUTF8String getSensitiveInfoHiddenSpec();
+};
diff --git a/netwerk/base/nsISerializationHelper.idl b/netwerk/base/nsISerializationHelper.idl
new file mode 100644
index 0000000000..740927f407
--- /dev/null
+++ b/netwerk/base/nsISerializationHelper.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Simple scriptable serialization helper. Can be used as a service.
+ */
+
+interface nsISerializable;
+
+[scriptable, uuid(31654c0f-35f3-44c6-b31e-37a11516e6bc)]
+interface nsISerializationHelper : nsISupports
+{
+ /**
+ * Serialize the object to a base64 string. This string can be later passed
+ * as an input to deserializeObject method.
+ */
+ ACString serializeToString(in nsISerializable serializable);
+
+ /**
+ * Takes base64 encoded string that cointains serialization of a single
+ * object. Most commonly, input is result of previous call to
+ * serializeToString.
+ */
+ nsISupports deserializeObject(in ACString input);
+};
diff --git a/netwerk/base/nsIServerSocket.idl b/netwerk/base/nsIServerSocket.idl
new file mode 100644
index 0000000000..d6fd348778
--- /dev/null
+++ b/netwerk/base/nsIServerSocket.idl
@@ -0,0 +1,269 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIServerSocketListener;
+interface nsISocketTransport;
+
+native PRNetAddr(union PRNetAddr);
+[ptr] native PRNetAddrPtr(union PRNetAddr);
+
+typedef unsigned long nsServerSocketFlag;
+
+/**
+ * nsIServerSocket
+ *
+ * An interface to a server socket that can accept incoming connections.
+ */
+[scriptable, uuid(7a9c39cb-a13f-4eef-9bdf-a74301628742)]
+interface nsIServerSocket : nsISupports
+{
+ /**
+ * @name Server Socket Flags
+ * These flags define various socket options.
+ * @{
+ */
+ /// The server socket will only respond to connections on the
+ /// local loopback interface. Otherwise, it will accept connections
+ /// from any interface. To specify a particular network interface,
+ /// use initWithAddress.
+ const nsServerSocketFlag LoopbackOnly = 0x00000001;
+ /// The server socket will not be closed when Gecko is set
+ /// offline.
+ const nsServerSocketFlag KeepWhenOffline = 0x00000002;
+ /** @} */
+
+ /**
+ * init
+ *
+ * This method initializes a server socket.
+ *
+ * @param aPort
+ * The port of the server socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aLoopbackOnly
+ * If true, the server socket will only respond to connections on the
+ * local loopback interface. Otherwise, it will accept connections
+ * from any interface. To specify a particular network interface,
+ * use initWithAddress.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ void init(in long aPort,
+ in boolean aLoopbackOnly,
+ in long aBackLog);
+
+ /**
+ * the same as init(), but initializes an IPv6 server socket
+ */
+ void initIPv6(in long aPort,
+ in boolean aLoopbackOnly,
+ in long aBackLog);
+
+ /**
+ * Similar to init(), but initializes a server socket that supports
+ * both IPv4 and IPv6.
+ */
+ void initDualStack(in long aPort,
+ in long aBackLog);
+
+ /**
+ * initSpecialConnection
+ *
+ * This method initializes a server socket and offers the ability to have
+ * that socket not get terminated if Gecko is set offline.
+ *
+ * @param aPort
+ * The port of the server socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aFlags
+ * Flags for the socket.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ void initSpecialConnection(in long aPort,
+ in nsServerSocketFlag aFlags,
+ in long aBackLog);
+
+
+ /**
+ * initWithAddress
+ *
+ * This method initializes a server socket, and binds it to a particular
+ * local address (and hence a particular local network interface).
+ *
+ * @param aAddr
+ * The address to which this server socket should be bound.
+ * @param aBackLog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ [noscript] void initWithAddress([const] in PRNetAddrPtr aAddr, in long aBackLog);
+
+ /**
+ * initWithFilename
+ *
+ * This method initializes a Unix domain or "local" server socket. Such
+ * a socket has a name in the filesystem, like an ordinary file. To
+ * connect, a client supplies the socket's filename, and the usual
+ * permission checks on socket apply.
+ *
+ * This makes Unix domain sockets useful for communication between the
+ * programs being run by a specific user on a single machine: the
+ * operating system takes care of authentication, and the user's home
+ * directory or profile directory provide natural per-user rendezvous
+ * points.
+ *
+ * Since Unix domain sockets are always local to the machine, they are
+ * not affected by the nsIIOService's 'offline' flag.
+ *
+ * The system-level socket API may impose restrictions on the length of
+ * the filename that are stricter than those of the underlying
+ * filesystem. If the file name is too long, this returns
+ * NS_ERROR_FILE_NAME_TOO_LONG.
+ *
+ * All components of the path prefix of |aPath| must name directories;
+ * otherwise, this returns NS_ERROR_FILE_NOT_DIRECTORY.
+ *
+ * This call requires execute permission on all directories containing
+ * the one in which the socket is to be created, and write and execute
+ * permission on the directory itself. Otherwise, this returns
+ * NS_ERROR_CONNECTION_REFUSED.
+ *
+ * This call creates the socket's directory entry. There must not be
+ * any existing entry with the given name. If there is, this returns
+ * NS_ERROR_SOCKET_ADDRESS_IN_USE.
+ *
+ * On systems that don't support Unix domain sockets at all, this
+ * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * @param aPath nsIFile
+ * The file name at which the socket should be created.
+ *
+ * @param aPermissions unsigned long
+ * Unix-style permission bits to be applied to the new socket.
+ *
+ * Note about permissions: Linux's unix(7) man page claims that some
+ * BSD-derived systems ignore permissions on UNIX-domain sockets;
+ * NetBSD's bind(2) man page agrees, but says it does check now (dated
+ * 2005). POSIX has required 'connect' to fail if write permission on
+ * the socket itself is not granted since 2003 (Issue 6). NetBSD says
+ * that the permissions on the containing directory (execute) have
+ * always applied, so creating sockets in appropriately protected
+ * directories should be secure on both old and new systems.
+ */
+ void initWithFilename(in nsIFile aPath, in unsigned long aPermissions,
+ in long aBacklog);
+
+ /**
+ * initWithAbstractAddress
+ *
+ * This mehtod is a flavor of initWithFilename method. This initializes
+ * a UNIX domain socket that uses abstract socket address.
+ * This socket type is only supported on Linux and Android.
+ *
+ * On systems that don't support this type's UNIX domain sockets at all,
+ * this returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * @param aName
+ * The abstract socket address which the socket should be created.
+ * @param aBacklog
+ * The maximum length the queue of pending connections may grow to.
+ */
+ void initWithAbstractAddress(in AUTF8String aName,
+ in long aBacklog);
+
+ /**
+ * close
+ *
+ * This method closes a server socket. This does not affect already
+ * connected client sockets (i.e., the nsISocketTransport instances
+ * created from this server socket). This will cause the onStopListening
+ * event to asynchronously fire with a status of NS_BINDING_ABORTED.
+ */
+ void close();
+
+ /**
+ * asyncListen
+ *
+ * This method puts the server socket in the listening state. It will
+ * asynchronously listen for and accept client connections. The listener
+ * will be notified once for each client connection that is accepted. The
+ * listener's onSocketAccepted method will be called on the same thread
+ * that called asyncListen (the calling thread must have a nsIEventTarget).
+ *
+ * The listener will be passed a reference to an already connected socket
+ * transport (nsISocketTransport). See below for more details.
+ *
+ * @param aListener
+ * The listener to be notified when client connections are accepted.
+ */
+ void asyncListen(in nsIServerSocketListener aListener);
+
+ /**
+ * Returns the port of this server socket.
+ */
+ readonly attribute long port;
+
+ /**
+ * Returns the address to which this server socket is bound. Since a
+ * server socket may be bound to multiple network devices, this address
+ * may not necessarily be specific to a single network device. In the
+ * case of an IP socket, the IP address field would be zerod out to
+ * indicate a server socket bound to all network devices. Therefore,
+ * this method cannot be used to determine the IP address of the local
+ * system. See nsIDNSService::myHostName if this is what you need.
+ */
+ [noscript] PRNetAddr getAddress();
+};
+
+/**
+ * nsIServerSocketListener
+ *
+ * This interface is notified whenever a server socket accepts a new connection.
+ * The transport is in the connected state, and read/write streams can be opened
+ * using the normal nsITransport API. The address of the client can be found by
+ * calling the nsISocketTransport::GetAddress method or by inspecting
+ * nsISocketTransport::GetHost, which returns a string representation of the
+ * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal).
+ */
+[scriptable, uuid(836d98ec-fee2-4bde-b609-abd5e966eabd)]
+interface nsIServerSocketListener : nsISupports
+{
+ /**
+ * onSocketAccepted
+ *
+ * This method is called when a client connection is accepted.
+ *
+ * @param aServ
+ * The server socket.
+ * @param aTransport
+ * The connected socket transport.
+ */
+ void onSocketAccepted(in nsIServerSocket aServ,
+ in nsISocketTransport aTransport);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The server socket is effectively dead after this notification.
+ *
+ * @param aServ
+ * The server socket.
+ * @param aStatus
+ * The reason why the server socket stopped listening. If the
+ * server socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIServerSocket aServ, in nsresult aStatus);
+};
diff --git a/netwerk/base/nsISimpleStreamListener.idl b/netwerk/base/nsISimpleStreamListener.idl
new file mode 100644
index 0000000000..99169481f5
--- /dev/null
+++ b/netwerk/base/nsISimpleStreamListener.idl
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIOutputStream;
+
+/**
+ * A simple stream listener can be used with AsyncRead to supply data to
+ * a output stream.
+ */
+[scriptable, uuid(a9b84f6a-0824-4278-bae6-bfca0570a26e)]
+interface nsISimpleStreamListener : nsIStreamListener
+{
+ /**
+ * Initialize the simple stream listener.
+ *
+ * @param aSink data will be read from the channel to this output stream.
+ * Must implement writeFrom.
+ * @param aObserver optional stream observer (can be NULL)
+ */
+ void init(in nsIOutputStream aSink,
+ in nsIRequestObserver aObserver);
+};
diff --git a/netwerk/base/nsISimpleURIMutator.idl b/netwerk/base/nsISimpleURIMutator.idl
new file mode 100644
index 0000000000..786c8701d2
--- /dev/null
+++ b/netwerk/base/nsISimpleURIMutator.idl
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIURIMutator;
+
+[scriptable, builtinclass, uuid(e055bddd-f3c2-404b-adec-db9304e93be2)]
+interface nsISimpleURIMutator : nsISupports
+{
+ /**
+ * Same behaviour as nsIURISetSpec.setSpec() but filters whitespace.
+ */
+ nsIURIMutator setSpecAndFilterWhitespace(in AUTF8String aSpec);
+};
diff --git a/netwerk/base/nsISocketFilter.idl b/netwerk/base/nsISocketFilter.idl
new file mode 100644
index 0000000000..0846fa2eda
--- /dev/null
+++ b/netwerk/base/nsISocketFilter.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsINetAddr.idl"
+
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+
+
+/**
+ * Filters are created and run on the parent, and filter all packets, both
+ * ingoing and outgoing. The child must specify the name of a recognized filter
+ * in order to create a socket.
+ */
+[uuid(afe2c40c-b9b9-4207-b898-e5cde18c6139)]
+interface nsISocketFilter : nsISupports
+{
+ const long SF_INCOMING = 0;
+ const long SF_OUTGOING = 1;
+
+ bool filterPacket([const]in NetAddrPtr remote_addr,
+ [const, array, size_is(len)]in uint8_t data,
+ in unsigned long len,
+ in long direction);
+};
+
+/**
+ * Factory of a specified filter.
+ */
+[uuid(81ee76c6-4753-4125-9c8c-290ed9ba62fb)]
+interface nsISocketFilterHandler : nsISupports
+{
+ nsISocketFilter newFilter();
+};
+
+%{C++
+/**
+ * Filter handlers are registered with XPCOM under the following CONTRACTID prefix:
+ */
+#define NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX "@mozilla.org/network/udp-filter-handler;1?name="
+#define NS_NETWORK_TCP_SOCKET_FILTER_HANDLER_PREFIX "@mozilla.org/network/tcp-filter-handler;1?name="
+
+#define NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX "stun"
+
+#define NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX
+
+
+#define NS_STUN_TCP_SOCKET_FILTER_HANDLER_CONTRACTID NS_NETWORK_TCP_SOCKET_FILTER_HANDLER_PREFIX NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX
+%}
diff --git a/netwerk/base/nsISocketTransport.idl b/netwerk/base/nsISocketTransport.idl
new file mode 100644
index 0000000000..58b869203e
--- /dev/null
+++ b/netwerk/base/nsISocketTransport.idl
@@ -0,0 +1,382 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsITransport.idl"
+#include "nsIRequest.idl"
+#include "nsITRRSkipReason.idl"
+
+interface nsIInterfaceRequestor;
+interface nsINetAddr;
+interface nsITLSSocketControl;
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+native OriginAttributes(mozilla::OriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes);
+
+/**
+ * nsISocketTransport
+ *
+ * NOTE: Connection setup is triggered by opening an input or output stream,
+ * it does not start on its own. Completion of the connection setup is
+ * indicated by a STATUS_CONNECTED_TO notification to the event sink (if set).
+ *
+ * NOTE: This is a free-threaded interface, meaning that the methods on
+ * this interface may be called from any thread.
+ */
+[scriptable, builtinclass, uuid(79221831-85e2-43a8-8152-05d77d6fde31)]
+interface nsISocketTransport : nsITransport
+{
+ /**
+ * Get the peer's host for the underlying socket connection.
+ * For Unix domain sockets, this is a pathname, or the empty string for
+ * unnamed and abstract socket addresses.
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * Get the port for the underlying socket connection.
+ * For Unix domain sockets, this is zero.
+ */
+ readonly attribute long port;
+
+ /**
+ * The origin attributes are used to create sockets. The first party domain
+ * will eventually be used to isolate OCSP cache and is only non-empty when
+ * "privacy.firstparty.isolate" is enabled. Setting this is the only way to
+ * carry origin attributes down to NSPR layers which are final consumers.
+ * It must be set before the socket transport is built.
+ */
+ [implicit_jscontext, binaryname(ScriptableOriginAttributes)]
+ attribute jsval originAttributes;
+
+ [noscript, nostdcall, binaryname(GetOriginAttributes)]
+ OriginAttributes binaryGetOriginAttributes();
+
+ [noscript, nostdcall, binaryname(SetOriginAttributes)]
+ void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs);
+
+ /**
+ * Returns the IP address of the socket connection peer. This
+ * attribute is defined only once a connection has been established.
+ */
+ [noscript] NetAddr getPeerAddr();
+
+ /**
+ * Returns the IP address of the initiating end. This attribute
+ * is defined only once a connection has been established.
+ */
+ [noscript] NetAddr getSelfAddr();
+
+ /**
+ * Bind to a specific local address.
+ */
+ [noscript] void bind(in NetAddrPtr aLocalAddr);
+
+ /**
+ * Returns a scriptable version of getPeerAddr. This attribute is defined
+ * only once a connection has been established.
+ */
+ nsINetAddr getScriptablePeerAddr();
+
+ /**
+ * Returns a scriptable version of getSelfAddr. This attribute is defined
+ * only once a connection has been established.
+ */
+ nsINetAddr getScriptableSelfAddr();
+
+ /**
+ * TLS socket control object. This attribute is only available once the
+ * socket is connected.
+ */
+ readonly attribute nsITLSSocketControl tlsSocketControl;
+
+ /**
+ * Security notification callbacks passed to the secure socket provider
+ * via nsITLSSocketControl at socket creation time.
+ *
+ * NOTE: this attribute cannot be changed once a stream has been opened.
+ */
+ attribute nsIInterfaceRequestor securityCallbacks;
+
+ /**
+ * Test if this socket transport is (still) connected.
+ */
+ boolean isAlive();
+
+ /**
+ * Socket timeouts in seconds. To specify no timeout, pass UINT32_MAX
+ * as aValue to setTimeout. The implementation may truncate timeout values
+ * to a smaller range of values (e.g., 0 to 0xFFFF).
+ */
+ unsigned long getTimeout(in unsigned long aType);
+ void setTimeout(in unsigned long aType, in unsigned long aValue);
+
+ /**
+ * Sets the SO_LINGER option with the specified values for the l_onoff and
+ * l_linger parameters. This applies PR_SockOpt_Linger before PR_Close and
+ * can be used with a timeout of zero to send an RST packet when closing.
+ */
+ void setLinger(in boolean aPolarity, in short aTimeout);
+
+ /**
+ * True to set addr and port reuse socket options.
+ */
+ void setReuseAddrPort(in bool reuseAddrPort);
+
+ /**
+ * Values for the aType parameter passed to get/setTimeout.
+ */
+ const unsigned long TIMEOUT_CONNECT = 0;
+ const unsigned long TIMEOUT_READ_WRITE = 1;
+
+ /**
+ * nsITransportEventSink status codes.
+ *
+ * Although these look like XPCOM error codes and are passed in an nsresult
+ * variable, they are *not* error codes. Note that while they *do* overlap
+ * with existing error codes in Necko, these status codes are confined
+ * within a very limited context where no error codes may appear, so there
+ * is no ambiguity.
+ *
+ * The values of these status codes must never change.
+ *
+ * The status codes appear in near-chronological order (not in numeric
+ * order). STATUS_RESOLVING may be skipped if the host does not need to be
+ * resolved. STATUS_WAITING_FOR is an optional status code, which the impl
+ * of this interface may choose not to generate.
+ *
+ * In C++, these constants have a type of uint32_t, so C++ callers must use
+ * the NS_NET_STATUS_* constants defined below, which have a type of
+ * nsresult.
+ */
+ const unsigned long STATUS_RESOLVING = 0x4b0003;
+ const unsigned long STATUS_RESOLVED = 0x4b000b;
+ const unsigned long STATUS_CONNECTING_TO = 0x4b0007;
+ const unsigned long STATUS_CONNECTED_TO = 0x4b0004;
+ const unsigned long STATUS_SENDING_TO = 0x4b0005;
+ const unsigned long STATUS_WAITING_FOR = 0x4b000a;
+ const unsigned long STATUS_RECEIVING_FROM = 0x4b0006;
+ const unsigned long STATUS_TLS_HANDSHAKE_STARTING = 0x4b000c;
+ const unsigned long STATUS_TLS_HANDSHAKE_ENDED = 0x4b000d;
+
+ /**
+ * connectionFlags is a bitmask that can be used to modify underlying
+ * behavior of the socket connection. See the flags below.
+ */
+ attribute unsigned long connectionFlags;
+
+ /**
+ * Values for the connectionFlags
+ *
+ * When making a new connection BYPASS_CACHE will force the Necko DNS
+ * cache entry to be refreshed with a new call to NSPR if it is set before
+ * opening the new stream.
+ */
+ const unsigned long BYPASS_CACHE = (1 << 0);
+
+ /**
+ * When setting this flag, the socket will not apply any
+ * credentials when establishing a connection. For example,
+ * an SSL connection would not send any client-certificates
+ * if this flag is set.
+ */
+ const unsigned long ANONYMOUS_CONNECT = (1 << 1);
+
+ /**
+ * If set, we will skip all IPv6 addresses the host may have and only
+ * connect to IPv4 ones.
+ */
+ const unsigned long DISABLE_IPV6 = (1 << 2);
+
+ /**
+ * If set, indicates that the connection was initiated from a source
+ * defined as being private in the sense of Private Browsing. Generally,
+ * there should be no state shared between connections that are private
+ * and those that are not; it is OK for multiple private connections
+ * to share state with each other, and it is OK for multiple non-private
+ * connections to share state with each other.
+ */
+ const unsigned long NO_PERMANENT_STORAGE = (1 << 3);
+
+ /**
+ * If set, we will skip all IPv4 addresses the host may have and only
+ * connect to IPv6 ones.
+ */
+ const unsigned long DISABLE_IPV4 = (1 << 4);
+
+ /**
+ * If set, indicates that the socket should not connect if the hostname
+ * resolves to an RFC1918 address or IPv6 equivalent.
+ */
+ const unsigned long DISABLE_RFC1918 = (1 << 5);
+
+ /**
+ * If set, do not use newer protocol features that might have interop problems
+ * on the Internet. Intended only for use with critical infra like the updater.
+ * default is false.
+ */
+ const unsigned long BE_CONSERVATIVE = (1 << 6);
+
+ /**
+ * If set, do not use TRR for resolving the host name. Intended only for
+ * retries or other scenarios when TRR is deemed likely to have returned a
+ * wrong adddress.
+ */
+ const unsigned long DISABLE_TRR = (1 << 7);
+
+ /**
+ * Values for the connectionFlags
+ *
+ * When using BYPASS_CACHE, setting this bit will invalidate the existing
+ * cached entry immediately while the new resolve is being done to avoid
+ * other users from using stale content in the mean time.
+ */
+ const unsigned long REFRESH_CACHE = (1 << 8);
+
+ /**
+ * If this flag is set then it means that if connecting the preferred ip
+ * family has failed, retry with the oppsite one once more.
+ */
+ const unsigned long RETRY_WITH_DIFFERENT_IP_FAMILY = (1 << 9);
+
+ /**
+ * If we know that a server speaks only tls <1.3 there is no need to try
+ * to use ech.
+ */
+ const unsigned long DONT_TRY_ECH = (1 << 10);
+
+ /**
+ * These two bits encode the TRR mode of the request.
+ * Use the static helper methods convert between the TRR mode and flags.
+ */
+ const unsigned long TRR_MODE_FLAGS = (1 << 11) | (1 << 12);
+
+%{C++
+
+ static uint32_t GetFlagsFromTRRMode(nsIRequest::TRRMode aMode) {
+ return static_cast<uint32_t>(aMode) << 11;
+ }
+
+ static nsIRequest::TRRMode GetTRRModeFromFlags(uint32_t aFlags) {
+ return static_cast<nsIRequest::TRRMode>((aFlags & TRR_MODE_FLAGS) >> 11);
+ }
+%}
+
+ /**
+ * If set, we will use IP hint addresses to connect to the host.
+ */
+ const unsigned long USE_IP_HINT_ADDRESS = (1 << 13);
+
+ /**
+ * This is used for a temporary workaround for a web-compat issue. The flag is
+ * only set on CORS preflight request to allowed sending client certificates
+ * on a connection for an anonymous request.
+ */
+ const unsigned long ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT = (1 << 14);
+
+ /**
+ * If set, we've retrying after a failed connection attempt.
+ */
+ const unsigned long IS_RETRY = (1 << 15);
+
+ /**
+ * If set, this is a speculative connection.
+ */
+ const unsigned long IS_SPECULATIVE_CONNECTION = (1 << 16);
+
+ /**
+ * An opaque flags for non-standard behavior of the TLS system.
+ * It is unlikely this will need to be set outside of telemetry studies
+ * relating to the TLS implementation.
+ */
+ attribute unsigned long tlsFlags;
+
+ /**
+ * Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or
+ * IPTOS_CLASS_CSx (or IPTOS_DSCP_EF, but currently no supported
+ * services require expedited-forwarding).
+ * Not setting this value will leave the socket with the default
+ * ToS value, which on most systems if IPTOS_CLASS_CS0 (formerly
+ * IPTOS_PREC_ROUTINE).
+ */
+ attribute octet QoSBits;
+
+ /**
+ * TCP send and receive buffer sizes. A value of 0 means OS level
+ * auto-tuning is in effect.
+ */
+ attribute unsigned long recvBufferSize;
+ attribute unsigned long sendBufferSize;
+
+ /**
+ * TCP keepalive configuration (support varies by platform).
+ * Note that the attribute as well as the setter can only accessed
+ * in the socket thread.
+ */
+ attribute boolean keepaliveEnabled;
+ void setKeepaliveVals(in long keepaliveIdleTime,
+ in long keepaliveRetryInterval);
+
+ /**
+ * If true, this socket transport has found out the prefered family
+ * according it's connection flags could not be used to establish
+ * connections any more. Hence, the preference should be reset.
+ */
+ readonly attribute boolean resetIPFamilyPreference;
+
+ /**
+ * This attribute holds information whether echConfig has been used.
+ * The value is set after PR_Connect is called.
+ */
+ readonly attribute boolean echConfigUsed;
+
+ /**
+ * Called to set the echConfig to the securityInfo object.
+ */
+ void setEchConfig(in ACString echConfig);
+
+ /**
+ * IP address resolved using TRR.
+ */
+ bool resolvedByTRR();
+
+ /**
+ * Returns the effectiveTRRMode used for the DNS resolution.
+ */
+ readonly attribute nsIRequest_TRRMode effectiveTRRMode;
+
+ /**
+ * Returns the TRR skip reason used for the DNS resolution.
+ */
+ readonly attribute nsITRRSkipReason_value trrSkipReason;
+
+ /**
+ * Indicate whether this socket is created from a private window. If yes,
+ * this socket will be closed when the last private window is closed.
+ */
+ [noscript] void setIsPrivate(in boolean isPrivate);
+
+ /**
+ * If DNS is performed externally, this flag informs the caller that it may
+ * retry connecting with a different DNS configuration (e.g. different IP
+ * family preference). The flag is set only if a network error is encounder,
+ * e.g. NS_ERROR_CONNECTION_REFUSED, NS_ERROR_RESET, etc.
+ */
+ readonly attribute boolean retryDnsIfPossible;
+
+ /**
+ * Return the current status of the socket.
+ */
+ [noscript] readonly attribute nsresult status;
+};
diff --git a/netwerk/base/nsISocketTransportService.idl b/netwerk/base/nsISocketTransportService.idl
new file mode 100644
index 0000000000..64891fcfa0
--- /dev/null
+++ b/netwerk/base/nsISocketTransportService.idl
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDNSRecord;
+interface nsIFile;
+interface nsISocketTransport;
+interface nsIProxyInfo;
+interface nsIRunnable;
+
+%{C++
+class nsASocketHandler;
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescPtr(PRFileDesc);
+[ptr] native nsASocketHandlerPtr(nsASocketHandler);
+
+[scriptable, function, uuid(338947df-2f3b-4d24-9ce4-ecf161c1b7df)]
+interface nsISTSShutdownObserver : nsISupports {
+
+ /**
+ * Observe will be called when the SocketTransportService is shutting down,
+ * before threads are stopped.
+ */
+ void observe();
+};
+
+[builtinclass, scriptable, uuid(ad56b25f-e6bb-4db3-9f7b-5b7db33fd2b1)]
+interface nsISocketTransportService : nsISupports
+{
+ /**
+ * Creates a transport for a specified host and port.
+ *
+ * @param aSocketTypes
+ * array of socket type strings. Empty array if using default
+ * socket type.
+ * @param aHost
+ * specifies the target hostname or IP address literal of the peer
+ * for this socket.
+ * @param aPort
+ * specifies the target port of the peer for this socket.
+ * @param aProxyInfo
+ * specifies the transport-layer proxy type to use. null if no
+ * proxy. used for communicating information about proxies like
+ * SOCKS (which are transparent to upper protocols).
+ * @param aDnsRecord
+ * the dns record to be used for the connection
+ *
+ * @see nsIProxiedProtocolHandler
+ * @see nsIProtocolProxyService::GetProxyInfo
+ *
+ * NOTE: this function can be called from any thread
+ */
+ nsISocketTransport createTransport(in Array<ACString> aSocketTypes,
+ in AUTF8String aHost,
+ in long aPort,
+ in nsIProxyInfo aProxyInfo,
+ in nsIDNSRecord dnsRecord);
+
+ /**
+ * Create a transport built on a Unix domain socket, connecting to the
+ * given filename.
+ *
+ * Since Unix domain sockets are always local to the machine, they are
+ * not affected by the nsIIOService's 'offline' flag.
+ *
+ * On systems that don't support Unix domain sockets at all, this
+ * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * The system-level socket API may impose restrictions on the length of
+ * the filename that are stricter than those of the underlying
+ * filesystem. If the file name is too long, this returns
+ * NS_ERROR_FILE_NAME_TOO_LONG.
+ *
+ * The |aPath| parameter must specify an existing directory entry.
+ * Otherwise, this returns NS_ERROR_FILE_NOT_FOUND.
+ *
+ * The program must have search permission on all components of the
+ * path prefix of |aPath|, and read and write permission on |aPath|
+ * itself. Without such permission, this returns
+ * NS_ERROR_CONNECTION_REFUSED.
+ *
+ * The |aPath| parameter must refer to a unix-domain socket. Otherwise,
+ * this returns NS_ERROR_CONNECTION_REFUSED. (POSIX specifies
+ * ECONNREFUSED when "the target address was not listening for
+ * connections", and this is what Linux returns.)
+ *
+ * @param aPath
+ * The file name of the Unix domain socket to which we should
+ * connect.
+ */
+ nsISocketTransport createUnixDomainTransport(in nsIFile aPath);
+
+ /**
+ * Create a transport built on a Unix domain socket that uses abstract
+ * address name.
+ *
+ * If abstract socket address isn't supported on System, this returns
+ * NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED.
+ *
+ * @param aName
+ * The name of abstract socket adress of the Unix domain socket to
+ * which we should connect.
+ */
+ nsISocketTransport
+ createUnixDomainAbstractAddressTransport(in ACString aName);
+
+ /**
+ * Adds a new socket to the list of controlled sockets.
+ *
+ * This will fail with the error code NS_ERROR_NOT_AVAILABLE if the maximum
+ * number of sockets is already reached.
+ * In this case, the notifyWhenCanAttachSocket method should be used.
+ *
+ * @param aFd
+ * Open file descriptor of the socket to control.
+ * @param aHandler
+ * Socket handler that will receive notifications when the socket is
+ * ready or detached.
+ *
+ * NOTE: this function may only be called from an event dispatch on the
+ * socket thread.
+ */
+ [noscript] void attachSocket(in PRFileDescPtr aFd,
+ in nsASocketHandlerPtr aHandler);
+
+ /**
+ * if the number of sockets reaches the limit, then consumers can be
+ * notified when the number of sockets becomes less than the limit. the
+ * notification is asynchronous, delivered via the given nsIRunnable
+ * instance on the socket transport thread.
+ *
+ * @param aEvent
+ * Event that will receive the notification when a new socket can
+ * be attached
+ *
+ * NOTE: this function may only be called from an event dispatch on the
+ * socket thread.
+ */
+ [noscript] void notifyWhenCanAttachSocket(in nsIRunnable aEvent);
+
+ [noscript] void addShutdownObserver(in nsISTSShutdownObserver aObserver);
+ [noscript] void removeShutdownObserver(in nsISTSShutdownObserver aObserver);
+};
+
+[builtinclass, scriptable, uuid(c5204623-5b58-4a16-8b2e-67c34dd02e3f)]
+interface nsIRoutedSocketTransportService : nsISocketTransportService
+{
+ // use this instead of createTransport when you have a transport
+ // that distinguishes between origin and route (aka connection)
+ nsISocketTransport createRoutedTransport(in Array<ACString> aSocketTypes,
+ in AUTF8String aHost, // origin
+ in long aPort, // origin
+ in AUTF8String aHostRoute,
+ in long aPortRoute,
+ in nsIProxyInfo aProxyInfo,
+ in nsIDNSRecord aDnsRecord);
+};
diff --git a/netwerk/base/nsISpeculativeConnect.idl b/netwerk/base/nsISpeculativeConnect.idl
new file mode 100644
index 0000000000..e1f890a04b
--- /dev/null
+++ b/netwerk/base/nsISpeculativeConnect.idl
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+interface nsIInterfaceRequestor;
+
+%{C++
+namespace mozilla {
+
+class OriginAttributes;
+
+}
+%}
+
+native OriginAttributes(mozilla::OriginAttributes&&);
+
+[scriptable, builtinclass, uuid(d74a17ac-5b8a-4824-a309-b1f04a3c4aed)]
+interface nsISpeculativeConnect : nsISupports
+{
+ /**
+ * Called as a hint to indicate a new transaction for the URI is likely coming
+ * soon. The implementer may use this information to start a TCP
+ * and/or SSL level handshake for that resource immediately so that it is
+ * ready and/or progressed when the transaction is actually submitted.
+ *
+ * No obligation is taken on by the implementer, nor is the submitter obligated
+ * to actually open the new channel.
+ *
+ * @param aURI the URI of the hinted transaction
+ * @param aPrincipal the principal that will be used for opening the
+ * channel of the hinted transaction.
+ * @param aCallbacks any security callbacks for use with SSL for interfaces.
+ * May be null.
+ * @param aAnonymous indicates if this is an anonymous connection.
+ *
+ */
+ void speculativeConnect(in nsIURI aURI,
+ in nsIPrincipal aPrincipal,
+ in nsIInterfaceRequestor aCallbacks,
+ in boolean aAnonymous);
+
+ /**
+ * This method is similar to speculativeConnect, but it use
+ * the partition key of the originAttributes directly to create the
+ * connection.
+ */
+ [implicit_jscontext]
+ void speculativeConnectWithOriginAttributes(
+ in nsIURI aURI,
+ in jsval originAttributes,
+ in nsIInterfaceRequestor aCallbacks,
+ in boolean aAnonymous);
+
+ [notxpcom]
+ void speculativeConnectWithOriginAttributesNative(
+ in nsIURI aURI,
+ in OriginAttributes originAttributes,
+ in nsIInterfaceRequestor aCallbacks,
+ in boolean aAnonymous);
+};
+
+/**
+ * This is used to override the default values for various values (documented
+ * inline) to determine whether or not to actually make a speculative
+ * connection.
+ */
+[uuid(1040ebe3-6ed1-45a6-8587-995e082518d7)]
+interface nsISpeculativeConnectionOverrider : nsISupports
+{
+ /**
+ * Used to determine the maximum number of unused speculative connections
+ * we will have open for a host at any one time
+ */
+ [infallible] readonly attribute unsigned long parallelSpeculativeConnectLimit;
+
+ /**
+ * Used to determine if we will ignore the existence of any currently idle
+ * connections when we decide whether or not to make a speculative
+ * connection.
+ */
+ [infallible] readonly attribute boolean ignoreIdle;
+
+ /*
+ * Used by the Predictor to gather telemetry data on speculative connection
+ * usage.
+ */
+ [infallible] readonly attribute boolean isFromPredictor;
+
+ /**
+ * by default speculative connections are not made to RFC 1918 addresses
+ */
+ [infallible] readonly attribute boolean allow1918;
+};
diff --git a/netwerk/base/nsIStandardURL.idl b/netwerk/base/nsIStandardURL.idl
new file mode 100644
index 0000000000..dae8ecc13c
--- /dev/null
+++ b/netwerk/base/nsIStandardURL.idl
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIURIMutator;
+
+/**
+ * nsIStandardURL defines the interface to an URL with the standard
+ * file path format common to protocols like http, ftp, and file.
+ * It supports initialization from a relative path and provides
+ * some customization on how URLs are normalized.
+ */
+[scriptable, builtinclass, uuid(babd6cca-ebe7-4329-967c-d6b9e33caa81)]
+interface nsIStandardURL : nsISupports
+{
+ /**
+ * blah:foo/bar => blah://foo/bar
+ * blah:/foo/bar => blah:///foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah:///foo/bar
+ */
+ const unsigned long URLTYPE_STANDARD = 1;
+
+ /**
+ * blah:foo/bar => blah://foo/bar
+ * blah:/foo/bar => blah://foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah://foo/bar
+ */
+ const unsigned long URLTYPE_AUTHORITY = 2;
+
+ /**
+ * blah:foo/bar => blah:///foo/bar
+ * blah:/foo/bar => blah:///foo/bar
+ * blah://foo/bar => blah://foo/bar
+ * blah:///foo/bar => blah:///foo/bar
+ */
+ const unsigned long URLTYPE_NO_AUTHORITY = 3;
+};
+
+[scriptable, builtinclass, uuid(fc894e98-23a1-43cd-a7fe-72876f8ea2ee)]
+interface nsIStandardURLMutator : nsISupports
+{
+ /**
+ * Initialize a standard URL.
+ *
+ * @param aUrlType - one of the URLTYPE_ flags listed above.
+ * @param aDefaultPort - if the port parsed from the URL string matches
+ * this port, then the port will be removed from the
+ * canonical form of the URL.
+ * @param aSpec - URL string.
+ * @param aOriginCharset - the charset from which this URI string
+ * originated. this corresponds to the charset
+ * that should be used when communicating this
+ * URI to an origin server, for example. if
+ * null, then provide aBaseURI implements this
+ * interface, the origin charset of aBaseURI will
+ * be assumed, otherwise defaulting to UTF-8 (i.e.,
+ * no charset transformation from aSpec).
+ * @param aBaseURI - if null, aSpec must specify an absolute URI.
+ * otherwise, aSpec will be resolved relative
+ * to aBaseURI.
+ */
+ nsIURIMutator init(in unsigned long aUrlType,
+ in long aDefaultPort,
+ in AUTF8String aSpec,
+ in string aOriginCharset,
+ in nsIURI aBaseURI);
+
+ /**
+ * Set the default port.
+ *
+ * Note: If this object is already using its default port (i.e. if it has
+ * mPort == -1), then it will now implicitly be using the new default port.
+ *
+ * @param aNewDefaultPort - if the URI has (or is later given) a port that
+ * matches this default, then we won't include a
+ * port number in the canonical form of the URL.
+ */
+ nsIURIMutator setDefaultPort(in long aNewDefaultPort);
+};
diff --git a/netwerk/base/nsIStreamListener.idl b/netwerk/base/nsIStreamListener.idl
new file mode 100644
index 0000000000..68ade60cd1
--- /dev/null
+++ b/netwerk/base/nsIStreamListener.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequestObserver.idl"
+
+interface nsIInputStream;
+
+/**
+ * nsIStreamListener
+ */
+[scriptable, uuid(3b4c8a77-76ba-4610-b316-678c73a3b88c)]
+interface nsIStreamListener : nsIRequestObserver
+{
+ /**
+ * Called when the next chunk of data (corresponding to the request) may
+ * be read without blocking the calling thread. The onDataAvailable impl
+ * must read exactly |aCount| bytes of data before returning.
+ *
+ * @param aRequest request corresponding to the source of the data
+ * @param aInputStream input stream containing the data chunk
+ * @param aOffset
+ * Number of bytes that were sent in previous onDataAvailable calls
+ * for this request. In other words, the sum of all previous count
+ * parameters.
+ * @param aCount number of bytes available in the stream
+ *
+ * NOTE: The aInputStream parameter must implement readSegments.
+ *
+ * An exception thrown from onDataAvailable has the side-effect of
+ * causing the request to be canceled.
+ */
+ void onDataAvailable(in nsIRequest aRequest,
+ in nsIInputStream aInputStream,
+ in unsigned long long aOffset,
+ in unsigned long aCount);
+};
diff --git a/netwerk/base/nsIStreamListenerTee.idl b/netwerk/base/nsIStreamListenerTee.idl
new file mode 100644
index 0000000000..2aa9c34877
--- /dev/null
+++ b/netwerk/base/nsIStreamListenerTee.idl
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIThreadRetargetableStreamListener.idl"
+
+interface nsIOutputStream;
+interface nsIRequestObserver;
+interface nsIEventTarget;
+
+/**
+ * As data "flows" into a stream listener tee, it is copied to the output stream
+ * and then forwarded to the real listener.
+ */
+[scriptable, uuid(62b27fc1-6e8c-4225-8ad0-b9d44252973a)]
+interface nsIStreamListenerTee : nsIThreadRetargetableStreamListener
+{
+ /**
+ * Initalize the tee.
+ *
+ * @param listener
+ * the original listener the tee will propagate onStartRequest,
+ * onDataAvailable and onStopRequest notifications to, exceptions from
+ * the listener will be propagated back to the channel
+ * @param sink
+ * the stream the data coming from the channel will be written to,
+ * should be blocking
+ * @param requestObserver
+ * optional parameter, listener that gets only onStartRequest and
+ * onStopRequest notifications; exceptions threw within this optional
+ * observer are also propagated to the channel, but exceptions from
+ * the original listener (listener parameter) are privileged
+ */
+ void init(in nsIStreamListener listener,
+ in nsIOutputStream sink,
+ [optional] in nsIRequestObserver requestObserver);
+
+ /**
+ * Initalize the tee like above, but with the extra parameter to make it
+ * possible to copy the output asynchronously
+ * @param anEventTarget
+ * if set, this event-target is used to copy data to the output stream,
+ * giving an asynchronous tee
+ */
+ void initAsync(in nsIStreamListener listener,
+ in nsIEventTarget eventTarget,
+ in nsIOutputStream sink,
+ [optional] in nsIRequestObserver requestObserver);
+
+};
diff --git a/netwerk/base/nsIStreamLoader.idl b/netwerk/base/nsIStreamLoader.idl
new file mode 100644
index 0000000000..1b54f59074
--- /dev/null
+++ b/netwerk/base/nsIStreamLoader.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIThreadRetargetableStreamListener.idl"
+
+interface nsIRequest;
+interface nsIStreamLoader;
+
+[scriptable, uuid(359F7990-D4E9-11d3-A1A5-0050041CAF44)]
+interface nsIStreamLoaderObserver : nsISupports
+{
+ /**
+ * Called when the entire stream has been loaded.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param status the status of the underlying channel
+ * @param resultLength the length of the data loaded
+ * @param result the data
+ *
+ * This method will always be called asynchronously by the
+ * nsIStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to take over responsibility for the
+ * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA
+ * in place of NS_OK as its success code. The loader will then
+ * "forget" about the data and not free() it after
+ * onStreamComplete() returns; observer must call free()
+ * when the data is no longer required.
+ */
+ void onStreamComplete(in nsIStreamLoader loader,
+ in nsISupports ctxt,
+ in nsresult status,
+ in unsigned long resultLength,
+ [const,array,size_is(resultLength)] in octet result);
+};
+
+/**
+ * Asynchronously loads a channel into a memory buffer.
+ *
+ * To use this interface, first call init() with a nsIStreamLoaderObserver
+ * that will be notified when the data has been loaded. Then call asyncOpen()
+ * on the channel with the nsIStreamLoader as the listener. The context
+ * argument in the asyncOpen() call will be passed to the onStreamComplete()
+ * callback.
+ *
+ * XXX define behaviour for sizes >4 GB
+ */
+[scriptable, uuid(323bcff1-7513-4e1f-a541-1c9213c2ed1b)]
+interface nsIStreamLoader : nsIThreadRetargetableStreamListener
+{
+ /**
+ * Initialize this stream loader, and start loading the data.
+ *
+ * @param aStreamObserver
+ * An observer that will be notified when the data is complete.
+ * @param aRequestObserver
+ * An optional observer that will be notified when the request
+ * has started or stopped.
+ */
+ void init(in nsIStreamLoaderObserver aStreamObserver,
+ [optional] in nsIRequestObserver aRequestObserver);
+
+ /**
+ * Gets the number of bytes read so far.
+ */
+ readonly attribute unsigned long numBytesRead;
+
+ /**
+ * Gets the request that loaded this file.
+ * null after the request has finished loading.
+ */
+ readonly attribute nsIRequest request;
+};
diff --git a/netwerk/base/nsIStreamTransportService.idl b/netwerk/base/nsIStreamTransportService.idl
new file mode 100644
index 0000000000..3a1c5a4239
--- /dev/null
+++ b/netwerk/base/nsIStreamTransportService.idl
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsITransport;
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIInputAvailableCallback;
+
+/**
+ * This service read/writes a stream on a background thread.
+ *
+ * Note: instead of using this interface, probably you want to use
+ * NS_MakeAsyncNonBlockingInputStream.
+ *
+ * Use this service to transform any blocking stream (e.g., file stream)
+ * into a fully asynchronous stream that can be read/written without
+ * blocking the main thread.
+ */
+[builtinclass, scriptable, uuid(5e0adf7d-9785-45c3-a193-04f25a75da8f)]
+interface nsIStreamTransportService : nsISupports
+{
+ /**
+ * CreateInputTransport
+ *
+ * @param aStream
+ * The input stream that will be read on a background thread.
+ * This stream must implement "blocking" stream semantics.
+ * @param aCloseWhenDone
+ * Specify this flag to have the input stream closed once its
+ * contents have been completely read.
+ *
+ * @return nsITransport instance.
+ */
+ nsITransport createInputTransport(in nsIInputStream aStream,
+ in boolean aCloseWhenDone);
+
+ void InputAvailable(in nsIInputStream aStream,
+ in nsIInputAvailableCallback aCallback);
+};
+
+[uuid(ff2da731-44d0-4dd9-8236-c99387fec721)]
+interface nsIInputAvailableCallback : nsISupports
+{
+ void onInputAvailableComplete(in unsigned long long available,
+ in nsresult available_return_code);
+};
diff --git a/netwerk/base/nsISyncStreamListener.idl b/netwerk/base/nsISyncStreamListener.idl
new file mode 100644
index 0000000000..9a46dda787
--- /dev/null
+++ b/netwerk/base/nsISyncStreamListener.idl
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+[scriptable, uuid(7e1aa658-6e3f-4521-9946-9685a169f764)]
+interface nsISyncStreamListener : nsIStreamListener
+{
+ /**
+ * Returns an input stream that when read will fetch data delivered to the
+ * sync stream listener. The nsIInputStream implementation will wait for
+ * OnDataAvailable events before returning from Read.
+ *
+ * NOTE: Reading from the returned nsIInputStream may spin the current
+ * thread's event queue, which could result in any event being processed.
+ */
+ readonly attribute nsIInputStream inputStream;
+};
diff --git a/netwerk/base/nsISystemProxySettings.idl b/netwerk/base/nsISystemProxySettings.idl
new file mode 100644
index 0000000000..9cd561fb64
--- /dev/null
+++ b/netwerk/base/nsISystemProxySettings.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface allows the proxy code to use platform-specific proxy
+ * settings when the proxy preference is set to "automatic discovery". This service
+ * acts like a PAC parser to netwerk, but it will actually read the system settings and
+ * either return the proper proxy data from the autoconfig URL specified in the system proxy,
+ * or generate proxy data based on the system's manual proxy settings.
+ */
+[scriptable, uuid(971591cd-277e-409a-bbf6-0a79879cd307)]
+interface nsISystemProxySettings : nsISupports
+{
+ /**
+ * Whether or not it is appropriate to execute getProxyForURI off the main thread.
+ * If that method can block (e.g. for WPAD as windows does) then it must be
+ * not mainThreadOnly to avoid creating main thread jank. The main thread only option is
+ * provided for implementations that do not block but use other main thread only
+ * functions such as dbus.
+ */
+ readonly attribute bool mainThreadOnly;
+
+ /**
+ * If non-empty, use this PAC file. If empty, call getProxyForURI instead.
+ */
+ readonly attribute AUTF8String PACURI;
+
+ /**
+ * See ProxyAutoConfig::getProxyForURI; this function behaves similarly except
+ * a more relaxed return string is allowed that includes full urls instead of just
+ * host:port syntax. e.g. "PROXY http://www.foo.com:8080" instead of
+ * "PROXY www.foo.com:8080"
+ */
+ AUTF8String getProxyForURI(in AUTF8String testSpec,
+ in AUTF8String testScheme,
+ in AUTF8String testHost,
+ in int32_t testPort);
+};
diff --git a/netwerk/base/nsITLSServerSocket.idl b/netwerk/base/nsITLSServerSocket.idl
new file mode 100644
index 0000000000..e944f23af7
--- /dev/null
+++ b/netwerk/base/nsITLSServerSocket.idl
@@ -0,0 +1,183 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIServerSocket.idl"
+
+interface nsIX509Cert;
+interface nsITLSServerSecurityObserver;
+interface nsISocketTransport;
+
+[scriptable, uuid(cc2c30f9-cfaa-4b8a-bd44-c24881981b74)]
+interface nsITLSServerSocket : nsIServerSocket
+{
+ /**
+ * serverCert
+ *
+ * The server's certificate that is presented to the client during the TLS
+ * handshake. This is required to be set before calling |asyncListen|.
+ */
+ attribute nsIX509Cert serverCert;
+
+ /**
+ * setSessionTickets
+ *
+ * Whether the server should support session tickets. Defaults to true. This
+ * should be set before calling |asyncListen| if you wish to change the
+ * default.
+ */
+ void setSessionTickets(in boolean aSessionTickets);
+
+ /**
+ * Values for setRequestClientCertificate
+ */
+ // Never request
+ const unsigned long REQUEST_NEVER = 0;
+ // Request (but do not require) during the first handshake only
+ const unsigned long REQUEST_FIRST_HANDSHAKE = 1;
+ // Request (but do not require) during each handshake
+ const unsigned long REQUEST_ALWAYS = 2;
+ // Require during the first handshake only
+ const unsigned long REQUIRE_FIRST_HANDSHAKE = 3;
+ // Require during each handshake
+ const unsigned long REQUIRE_ALWAYS = 4;
+
+ /**
+ * setRequestClientCertificate
+ *
+ * Whether the server should request and/or require a client auth certificate
+ * from the client. Defaults to REQUEST_NEVER. See the possible options
+ * above. This should be set before calling |asyncListen| if you wish to
+ * change the default.
+ */
+ void setRequestClientCertificate(in unsigned long aRequestClientCert);
+
+ /**
+ * setVersionRange
+ *
+ * The server's TLS versions that is used by the TLS handshake.
+ * This is required to be set before calling |asyncListen|.
+ *
+ * aMinVersion and aMaxVersion is a TLS version value from
+ * |nsITLSClientStatus| constants.
+ */
+ void setVersionRange(in unsigned short aMinVersion,
+ in unsigned short aMaxVersion);
+};
+
+/**
+ * Security summary for a given TLS client connection being handled by a
+ * |nsITLSServerSocket| server.
+ *
+ * This is accessible through the security info object on the transport, which
+ * will be an instance of |nsITLSServerConnectionInfo| (see below).
+ *
+ * The values of these attributes are available once the |onHandshakeDone|
+ * method of the security observer has been called (see
+ * |nsITLSServerSecurityObserver| below).
+ */
+[scriptable, uuid(19668ea4-e5ad-4182-9698-7e890d48f327)]
+interface nsITLSClientStatus : nsISupports
+{
+ /**
+ * peerCert
+ *
+ * The client's certificate, if one was requested via |requestCertificate|
+ * above and supplied by the client.
+ */
+ readonly attribute nsIX509Cert peerCert;
+
+ /**
+ * Values for tlsVersionUsed, as defined by TLS
+ */
+ const short SSL_VERSION_3 = 0x0300;
+ const short TLS_VERSION_1 = 0x0301;
+ const short TLS_VERSION_1_1 = 0x0302;
+ const short TLS_VERSION_1_2 = 0x0303;
+ const short TLS_VERSION_1_3 = 0x0304;
+ const short TLS_VERSION_UNKNOWN = -1;
+
+ /**
+ * tlsVersionUsed
+ *
+ * The version of TLS used by the connection. See values above.
+ */
+ readonly attribute short tlsVersionUsed;
+
+ /**
+ * cipherName
+ *
+ * Name of the cipher suite used, such as
+ * "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".
+ * See security/nss/lib/ssl/sslinfo.c for the possible values.
+ */
+ readonly attribute ACString cipherName;
+
+ /**
+ * keyLength
+ *
+ * The "effective" key size of the symmetric key in bits.
+ */
+ readonly attribute unsigned long keyLength;
+
+ /**
+ * macLength
+ *
+ * The size of the MAC in bits.
+ */
+ readonly attribute unsigned long macLength;
+};
+
+/**
+ * Connection info for a given TLS client connection being handled by a
+ * |nsITLSServerSocket| server. This object is thread-safe.
+ *
+ * This is exposed as the security info object on the transport, so it can be
+ * accessed via |transport.securityInfo|.
+ *
+ * This interface is available by the time the |onSocketAttached| is called,
+ * which is the first time the TLS server consumer is notified of a new client.
+ */
+[scriptable, uuid(8a93f5d5-eddd-4c62-a4bd-bfd297653184)]
+interface nsITLSServerConnectionInfo : nsISupports
+{
+ /**
+ * setSecurityObserver
+ *
+ * Set the security observer to be notified when the TLS handshake has
+ * completed.
+ */
+ void setSecurityObserver(in nsITLSServerSecurityObserver observer);
+
+ /**
+ * serverSocket
+ *
+ * The nsITLSServerSocket instance that accepted this client connection.
+ */
+ readonly attribute nsITLSServerSocket serverSocket;
+
+ /**
+ * status
+ *
+ * Security summary for this TLS client connection. Note that the values of
+ * this interface are not available until the TLS handshake has completed.
+ * See |nsITLSClientStatus| above for more details.
+ */
+ readonly attribute nsITLSClientStatus status;
+};
+
+[scriptable, uuid(1f62e1ae-e546-4a38-8917-d428472ed736)]
+interface nsITLSServerSecurityObserver : nsISupports
+{
+ /**
+ * onHandsakeDone
+ *
+ * This method is called once the TLS handshake is completed. This takes
+ * place after |onSocketAccepted| has been called, which typically opens the
+ * streams to keep things moving along. It's important to be aware that the
+ * handshake has not completed at the point that |onSocketAccepted| is called,
+ * so no security verification can be done until this method is called.
+ */
+ void onHandshakeDone(in nsITLSServerSocket aServer,
+ in nsITLSClientStatus aStatus);
+};
diff --git a/netwerk/base/nsIThreadRetargetableRequest.idl b/netwerk/base/nsIThreadRetargetableRequest.idl
new file mode 100644
index 0000000000..5087987567
--- /dev/null
+++ b/netwerk/base/nsIThreadRetargetableRequest.idl
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsISerialEventTarget;
+
+/**
+ * nsIThreadRetargetableRequest
+ *
+ * Should be implemented by requests that support retargeting delivery of
+ * data off the main thread.
+ */
+[uuid(27b84c48-5a73-4ba4-a8a4-8b5e649a145e)]
+interface nsIThreadRetargetableRequest : nsISupports
+{
+ /**
+ * Called to retarget delivery of OnDataAvailable to another thread. Should
+ * only be called before AsyncOpen for nsIWebsocketChannels, or during
+ * OnStartRequest for nsIChannels.
+ * Note: For nsIChannels, OnStartRequest and OnStopRequest will still be
+ * delivered on the main thread.
+ *
+ * @param aNewTarget New event target, e.g. thread or threadpool.
+ *
+ * Note: no return value is given. If the retargeting cannot be handled,
+ * normal delivery to the main thread will continue. As such, listeners
+ * should be ready to deal with OnDataAvailable on either the main thread or
+ * the new target thread.
+ */
+ void retargetDeliveryTo(in nsISerialEventTarget aNewTarget);
+
+ /**
+ * Returns the event target where OnDataAvailable events will be dispatched.
+ *
+ * This is only valid after OnStartRequest has been called. Any time before
+ * that point, the value may be changed by `retargetDeliveryTo` calls.
+ */
+ readonly attribute nsISerialEventTarget deliveryTarget;
+};
diff --git a/netwerk/base/nsIThreadRetargetableStreamListener.idl b/netwerk/base/nsIThreadRetargetableStreamListener.idl
new file mode 100644
index 0000000000..8d4ce396fa
--- /dev/null
+++ b/netwerk/base/nsIThreadRetargetableStreamListener.idl
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIStreamListener.idl"
+/**
+ * nsIThreadRetargetableStreamListener
+ *
+ * To be used by classes which implement nsIStreamListener and whose
+ * OnDataAvailable callback may be retargeted for delivery off the main thread.
+ */
+[scriptable, uuid(fb2304b8-f82f-4433-af68-d874a2ebbdc1)]
+interface nsIThreadRetargetableStreamListener : nsIStreamListener
+{
+ /**
+ * Checks this listener and any next listeners it may have to verify that
+ * they can receive OnDataAvailable off the main thread. It is the
+ * responsibility of the implementing class to decide on the criteria to
+ * determine if retargeted delivery of these methods is possible, but it must
+ * check any and all nsIStreamListener objects that might be called in the
+ * listener chain.
+ *
+ * An exception should be thrown if a listener in the chain does not
+ * support retargeted delivery, i.e. if the next listener does not implement
+ * nsIThreadRetargetableStreamListener, or a call to its checkListenerChain()
+ * fails.
+ */
+ void checkListenerChain();
+
+ /**
+ * Used for notifying listeners about data stop.
+ * After this notification, the listeners could potentially start processing
+ * the data. Note that onDataFinished can be called on or off the main thread.
+ * It is the responsibility of the listeners to handle this correctly.
+ *
+ * The ChannelEventQueue implementation ensures that the OnDataFinished is
+ * run on the ODA target thread after the last OnDataAvailable is executed on
+ * the ODA target thread and before OnStopRequest is called.
+ * Hence, the following order is guaranteed for the listeners, even with ODA/ODF running off MainThread.
+ * 1. OnStartRequest
+ * 2. OnDataAvailable
+ * 3. OnDataFinished
+ * 4. OnStopRequest
+ */
+ void onDataFinished(in nsresult aStatusCode);
+};
diff --git a/netwerk/base/nsIThrottledInputChannel.idl b/netwerk/base/nsIThrottledInputChannel.idl
new file mode 100644
index 0000000000..ae8d7321db
--- /dev/null
+++ b/netwerk/base/nsIThrottledInputChannel.idl
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIAsyncInputStream;
+
+/**
+ * An instance of this interface can be used to throttle the uploads
+ * of a group of associated channels.
+ */
+[scriptable, uuid(6b4b96fe-3c67-4587-af7b-58b6b17da411)]
+interface nsIInputChannelThrottleQueue : nsISupports
+{
+ /**
+ * Initialize this object with the mean and maximum bytes per
+ * second that will be allowed. Neither value may be zero, and
+ * the maximum must not be less than the mean.
+ *
+ * @param aMeanBytesPerSecond
+ * Mean number of bytes per second.
+ * @param aMaxBytesPerSecond
+ * Maximum number of bytes per second.
+ */
+ void init(in unsigned long aMeanBytesPerSecond, in unsigned long aMaxBytesPerSecond);
+
+ /**
+ * Internal use only. Get the values set by init method.
+ */
+ [noscript] readonly attribute unsigned long meanBytesPerSecond;
+ [noscript] readonly attribute unsigned long maxBytesPerSecond;
+
+
+ /**
+ * Return the number of bytes that are available to the caller in
+ * this time slice.
+ *
+ * @param aRemaining
+ * The number of bytes available to be processed
+ * @return the number of bytes allowed to be processed during this
+ * time slice; this will never be greater than aRemaining.
+ */
+ unsigned long available(in unsigned long aRemaining);
+
+ /**
+ * Record a successful read.
+ *
+ * @param aBytesRead
+ * The number of bytes actually read.
+ */
+ void recordRead(in unsigned long aBytesRead);
+
+ /**
+ * Return the number of bytes allowed through this queue. This is
+ * the sum of all the values passed to recordRead. This method is
+ * primarily useful for testing.
+ */
+ unsigned long long bytesProcessed();
+
+ /**
+ * Wrap the given input stream in a new input stream which
+ * throttles the incoming data.
+ *
+ * @param aInputStream the input stream to wrap
+ * @return a new input stream that throttles the data.
+ */
+ nsIAsyncInputStream wrapStream(in nsIInputStream aInputStream);
+};
+
+/**
+ * A throttled input channel can be managed by an
+ * nsIInputChannelThrottleQueue to limit how much data is sent during
+ * a given time slice.
+ */
+[scriptable, uuid(0a32a100-c031-45b6-9e8b-0444c7d4a143)]
+interface nsIThrottledInputChannel : nsISupports
+{
+ /**
+ * The queue that manages this channel. Multiple channels can
+ * share a single queue. A null value means that no throttling
+ * will be done.
+ */
+ attribute nsIInputChannelThrottleQueue throttleQueue;
+};
diff --git a/netwerk/base/nsITimedChannel.idl b/netwerk/base/nsITimedChannel.idl
new file mode 100644
index 0000000000..4707bf1b7a
--- /dev/null
+++ b/netwerk/base/nsITimedChannel.idl
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIArray;
+interface nsIPrincipal;
+%{C++
+namespace mozilla {
+class TimeStamp;
+}
+#include "nsTArrayForwardDeclare.h"
+#include "nsCOMPtr.h"
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+
+[scriptable, uuid(c2d9e95b-9cc9-4f47-9ef6-1de0cf7ebc75)]
+interface nsIServerTiming : nsISupports {
+ [must_use] readonly attribute ACString name;
+ [must_use] readonly attribute double duration;
+ [must_use] readonly attribute ACString description;
+};
+
+[ref] native nsServerTimingArrayRef(nsTArray<nsCOMPtr<nsIServerTiming>>);
+
+// All properties return zero if the value is not available
+[scriptable, uuid(ca63784d-959c-4c3a-9a59-234a2a520de0)]
+interface nsITimedChannel : nsISupports {
+ // Set this attribute to true to enable collection of timing data.
+ // channelCreationTime will be available even with this attribute set to
+ // false.
+ attribute boolean timingEnabled;
+
+ // The number of redirects
+ attribute uint8_t redirectCount;
+ attribute uint8_t internalRedirectCount;
+
+ // These properties should only be written externally when they must be
+ // propagated across an internal redirect. For example, when a service
+ // worker interception falls back to network we need to copy the original
+ // timing values to the new nsHttpChannel.
+ [noscript] attribute TimeStamp channelCreation;
+ [noscript] attribute TimeStamp asyncOpen;
+
+ // The following are only set when the request is intercepted by a service
+ // worker no matter the response is synthesized.
+ [noscript] attribute TimeStamp launchServiceWorkerStart;
+ [noscript] attribute TimeStamp launchServiceWorkerEnd;
+ [noscript] attribute TimeStamp dispatchFetchEventStart;
+ [noscript] attribute TimeStamp dispatchFetchEventEnd;
+ [noscript] attribute TimeStamp handleFetchEventStart;
+ [noscript] attribute TimeStamp handleFetchEventEnd;
+
+ // The following are only set when the document is not (only) read from the
+ // cache
+ [noscript] readonly attribute TimeStamp domainLookupStart;
+ [noscript] readonly attribute TimeStamp domainLookupEnd;
+ [noscript] readonly attribute TimeStamp connectStart;
+ [noscript] readonly attribute TimeStamp tcpConnectEnd;
+ [noscript] readonly attribute TimeStamp secureConnectionStart;
+ [noscript] readonly attribute TimeStamp connectEnd;
+ [noscript] readonly attribute TimeStamp requestStart;
+ [noscript] readonly attribute TimeStamp responseStart;
+ [noscript] readonly attribute TimeStamp responseEnd;
+
+ // The redirect attributes timings must be writeble, se we can transfer
+ // the data from one channel to the redirected channel.
+ [noscript] attribute TimeStamp redirectStart;
+ [noscript] attribute TimeStamp redirectEnd;
+
+ // The initiator type
+ [noscript] attribute AString initiatorType;
+
+ // This flag should be set to false only if a cross-domain redirect occurred
+ [noscript] attribute boolean allRedirectsSameOrigin;
+ // This flag is set to false if the timing allow check fails
+ [noscript] attribute boolean allRedirectsPassTimingAllowCheck;
+ // Implements the timing-allow-check to determine if we should report
+ // timing info for the resourceTiming object.
+ [noscript] boolean timingAllowCheck(in nsIPrincipal origin);
+ %{C++
+ inline bool TimingAllowCheck(nsIPrincipal* aOrigin) {
+ bool allowed = false;
+ return NS_SUCCEEDED(TimingAllowCheck(aOrigin, &allowed)) && allowed;
+ }
+ %}
+
+ // The following are only set if the document is (partially) read from the
+ // cache
+ [noscript] readonly attribute TimeStamp cacheReadStart;
+ [noscript] readonly attribute TimeStamp cacheReadEnd;
+
+ // The time when the transaction was submitted to the Connection Manager.
+ // Not reported to resource/navigation timing, only for performance telemetry.
+ [noscript] readonly attribute TimeStamp transactionPending;
+
+ // All following are PRTime versions of the above.
+ readonly attribute PRTime channelCreationTime;
+ readonly attribute PRTime asyncOpenTime;
+ readonly attribute PRTime launchServiceWorkerStartTime;
+ readonly attribute PRTime launchServiceWorkerEndTime;
+ readonly attribute PRTime dispatchFetchEventStartTime;
+ readonly attribute PRTime dispatchFetchEventEndTime;
+ readonly attribute PRTime handleFetchEventStartTime;
+ readonly attribute PRTime handleFetchEventEndTime;
+ readonly attribute PRTime domainLookupStartTime;
+ readonly attribute PRTime domainLookupEndTime;
+ readonly attribute PRTime connectStartTime;
+ readonly attribute PRTime tcpConnectEndTime;
+ readonly attribute PRTime secureConnectionStartTime;
+ readonly attribute PRTime connectEndTime;
+ readonly attribute PRTime requestStartTime;
+ readonly attribute PRTime responseStartTime;
+ readonly attribute PRTime responseEndTime;
+ readonly attribute PRTime cacheReadStartTime;
+ readonly attribute PRTime cacheReadEndTime;
+ readonly attribute PRTime redirectStartTime;
+ readonly attribute PRTime redirectEndTime;
+ // Not reported to resource/navigation timing, only for performance telemetry.
+ readonly attribute PRTime transactionPendingTime;
+
+ // If this attribute is false, this resource MUST NOT be reported in resource timing.
+ [noscript] attribute boolean reportResourceTiming;
+
+ readonly attribute nsIArray serverTiming;
+ nsServerTimingArrayRef getNativeServerTiming();
+};
diff --git a/netwerk/base/nsITraceableChannel.idl b/netwerk/base/nsITraceableChannel.idl
new file mode 100644
index 0000000000..4d325d4fcb
--- /dev/null
+++ b/netwerk/base/nsITraceableChannel.idl
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+
+/**
+ * A channel implementing this interface allows one to intercept its data by
+ * inserting intermediate stream listeners.
+ */
+[scriptable, uuid(68167b0b-ef34-4d79-a09a-8045f7c5140e)]
+interface nsITraceableChannel : nsISupports
+{
+ /*
+ * Replace the channel's listener with a new one, and return the listener
+ * the channel used to have. The new listener intercepts OnStartRequest,
+ * OnDataAvailable and OnStopRequest calls and must pass them to
+ * the original listener after examination. If multiple callers replace
+ * the channel's listener, a chain of listeners is created.
+ * The caller of setNewListener has no way to control at which place
+ * in the chain its listener is placed.
+ *
+ * @param aMustApplyContentConversion Pass true if the new listener requires
+ * content conversion to already be applied by the channel.
+ *
+ * Note: The caller of setNewListener must not delay passing
+ * OnStartRequest to the original listener.
+ *
+ * Note2: A channel may restrict when the listener can be replaced.
+ * It is not recommended to allow listener replacement after OnStartRequest
+ * has been called.
+ */
+ nsIStreamListener setNewListener(in nsIStreamListener aListener, [optional] in boolean aMustApplyContentConversion);
+};
diff --git a/netwerk/base/nsITransport.idl b/netwerk/base/nsITransport.idl
new file mode 100644
index 0000000000..856c4e2000
--- /dev/null
+++ b/netwerk/base/nsITransport.idl
@@ -0,0 +1,162 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsITransportEventSink;
+interface nsIEventTarget;
+
+/**
+ * nsITransport
+ *
+ * This interface provides a common way of accessing i/o streams connected
+ * to some resource. This interface does not in any way specify the resource.
+ * It provides methods to open blocking or non-blocking, buffered or unbuffered
+ * streams to the resource. The name "transport" is meant to connote the
+ * inherent data transfer implied by this interface (i.e., data is being
+ * transfered in some fashion via the streams exposed by this interface).
+ *
+ * A transport can have an event sink associated with it. The event sink
+ * receives transport-specific events as the transfer is occuring. For a
+ * socket transport, these events can include status about the connection.
+ * See nsISocketTransport for more info about socket transport specifics.
+ */
+[scriptable, uuid(2a8c6334-a5e6-4ec3-9865-1256541446fb)]
+interface nsITransport : nsISupports
+{
+ /**
+ * Open flags.
+ */
+ const unsigned long OPEN_BLOCKING = 1<<0;
+ const unsigned long OPEN_UNBUFFERED = 1<<1;
+
+ /**
+ * Open an input stream on this transport.
+ *
+ * Flags have the following meaning:
+ *
+ * OPEN_BLOCKING
+ * If specified, then the resulting stream will have blocking stream
+ * semantics. This means that if the stream has no data and is not
+ * closed, then reading from it will block the calling thread until
+ * at least one byte is available or until the stream is closed.
+ * If this flag is NOT specified, then the stream has non-blocking
+ * stream semantics. This means that if the stream has no data and is
+ * not closed, then reading from it returns NS_BASE_STREAM_WOULD_BLOCK.
+ * In addition, in non-blocking mode, the stream is guaranteed to
+ * support nsIAsyncInputStream. This interface allows the consumer of
+ * the stream to be notified when the stream can again be read.
+ *
+ * OPEN_UNBUFFERED
+ * If specified, the resulting stream may not support ReadSegments.
+ * ReadSegments is only gauranteed to be implemented when this flag is
+ * NOT specified.
+ *
+ * @param aFlags
+ * optional transport specific flags.
+ * @param aSegmentSize
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * size of each buffer segment (pass 0 to use default value).
+ * @param aSegmentCount
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * maximum number of buffer segments (pass 0 to use default value).
+ */
+ nsIInputStream openInputStream(in unsigned long aFlags,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount);
+
+ /**
+ * Open an output stream on this transport.
+ *
+ * Flags have the following meaning:
+ *
+ * OPEN_BLOCKING
+ * If specified, then the resulting stream will have blocking stream
+ * semantics. This means that if the stream is full and is not closed,
+ * then writing to it will block the calling thread until ALL of the
+ * data can be written or until the stream is closed. If this flag is
+ * NOT specified, then the stream has non-blocking stream semantics.
+ * This means that if the stream is full and is not closed, then writing
+ * to it returns NS_BASE_STREAM_WOULD_BLOCK. In addition, in non-
+ * blocking mode, the stream is guaranteed to support
+ * nsIAsyncOutputStream. This interface allows the consumer of the
+ * stream to be notified when the stream can again accept more data.
+ *
+ * OPEN_UNBUFFERED
+ * If specified, the resulting stream may not support WriteSegments and
+ * WriteFrom. WriteSegments and WriteFrom are only guaranteed to be
+ * implemented when this flag is NOT specified.
+ *
+ * @param aFlags
+ * optional transport specific flags.
+ * @param aSegmentSize
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * size of each buffer segment (pass 0 to use default value).
+ * @param aSegmentCount
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * maximum number of buffer segments (pass 0 to use default value).
+ */
+ nsIOutputStream openOutputStream(in unsigned long aFlags,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount);
+
+ /**
+ * Close the transport and any open streams.
+ *
+ * @param aReason
+ * the reason for closing the stream.
+ */
+ void close(in nsresult aReason);
+
+ /**
+ * Set the transport event sink.
+ *
+ * @param aSink
+ * receives transport layer notifications
+ * @param aEventTarget
+ * indicates the event target to which the notifications should
+ * be delivered. if NULL, then the notifications may occur on
+ * any thread.
+ */
+ void setEventSink(in nsITransportEventSink aSink,
+ in nsIEventTarget aEventTarget);
+
+ /**
+ * Generic nsITransportEventSink status codes. nsITransport
+ * implementations may override these status codes with their own more
+ * specific status codes (e.g., see nsISocketTransport).
+ *
+ * In C++, these constants have a type of uint32_t, so C++ callers must use
+ * the NS_NET_STATUS_* constants defined below, which have a type of
+ * nsresult.
+ */
+ const unsigned long STATUS_READING = 0x4b0008;
+ const unsigned long STATUS_WRITING = 0x4b0009;
+};
+
+[scriptable, uuid(EDA4F520-67F7-484b-A691-8C3226A5B0A6)]
+interface nsITransportEventSink : nsISupports
+{
+ /**
+ * Transport status notification.
+ *
+ * @param aTransport
+ * the transport sending this status notification.
+ * @param aStatus
+ * the transport status. See nsISocketTransport for socket specific
+ * status codes and more comments.
+ * @param aProgress
+ * the amount of data either read or written depending on the value
+ * of the status code. this value is relative to aProgressMax.
+ * @param aProgressMax
+ * the maximum amount of data that will be read or written. if
+ * unknown, -1 will be passed.
+ */
+ void onTransportStatus(in nsITransport aTransport,
+ in nsresult aStatus,
+ in long long aProgress,
+ in long long aProgressMax);
+};
diff --git a/netwerk/base/nsIUDPSocket.idl b/netwerk/base/nsIUDPSocket.idl
new file mode 100644
index 0000000000..41bd0ebc17
--- /dev/null
+++ b/netwerk/base/nsIUDPSocket.idl
@@ -0,0 +1,407 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsINetAddr;
+interface nsIUDPSocketListener;
+interface nsIUDPSocketSyncListener;
+interface nsIUDPMessage;
+interface nsISocketTransport;
+interface nsIOutputStream;
+interface nsIInputStream;
+interface nsIPrincipal;
+
+%{ C++
+#include "nsTArrayForwardDeclare.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+[ref] native Uint8TArrayRef(FallibleTArray<uint8_t>);
+
+/**
+ * nsIUDPSocket
+ *
+ * An interface to a UDP socket that can accept incoming connections.
+ */
+[scriptable, uuid(d423bf4e-4499-40cf-bc03-153e2bf206d1)]
+interface nsIUDPSocket : nsISupports
+{
+ /**
+ * init
+ *
+ * This method initializes a UDP socket.
+ *
+ * @param aPort
+ * The port of the UDP socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aLoopbackOnly
+ * If true, the UDP socket will only respond to connections on the
+ * local loopback interface. Otherwise, it will accept connections
+ * from any interface. To specify a particular network interface,
+ * use initWithAddress.
+ * @param aPrincipal
+ * The principal connected to this socket.
+ * @param aAddressReuse
+ * If true, the socket is allowed to be bound to an address that is
+ * already in use. Default is true.
+ */
+ [optional_argc] void init(in long aPort,
+ in boolean aLoopbackOnly,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ [optional_argc] void init2(in AUTF8String aAddr,
+ in long aPort,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ /**
+ * initWithAddress
+ *
+ * This method initializes a UDP socket, and binds it to a particular
+ * local address (and hence a particular local network interface).
+ *
+ * @param aAddr
+ * The address to which this UDP socket should be bound.
+ * @param aPrincipal
+ * The principal connected to this socket.
+ * @param aAddressReuse
+ * If true, the socket is allowed to be bound to an address that is
+ * already in use. Default is true.
+ */
+ [noscript, optional_argc] void initWithAddress([const] in NetAddrPtr aAddr,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ /**
+ * close
+ *
+ * This method closes a UDP socket. This does not affect already
+ * connected client sockets (i.e., the nsISocketTransport instances
+ * created from this UDP socket). This will cause the onStopListening
+ * event to asynchronously fire with a status of NS_BINDING_ABORTED.
+ */
+ void close();
+
+ /**
+ * asyncListen
+ *
+ * This method puts the UDP socket in the listening state. It will
+ * asynchronously listen for and accept client connections. The listener
+ * will be notified once for each client connection that is accepted. The
+ * listener's onSocketAccepted method will be called on the same thread
+ * that called asyncListen (the calling thread must have a nsIEventTarget).
+ *
+ * The listener will be passed a reference to an already connected socket
+ * transport (nsISocketTransport). See below for more details.
+ *
+ * @param aListener
+ * The listener to be notified when client connections are accepted.
+ */
+ void asyncListen(in nsIUDPSocketListener aListener);
+
+ /**
+ * This adds a nsIUDPSocketSyncListener listener (defined below).
+ * When data is available onPacketReceived is called and the lisener uses
+ * recvWithAddr to actually retrive data from the socket.
+ * The listener can be use only if it runs on the socket thread.
+ * If it is used off the socket thread there is a risk of triggering a bug
+ * in OS thatcan cause a crash.
+ */
+ void syncListen(in nsIUDPSocketSyncListener aListener);
+
+ /**
+ * connect
+ *
+ * This method connects the UDP socket to a remote UDP address.
+ *
+ * @param aRemoteAddr
+ * The remote address to connect to
+ */
+ void connect([const] in NetAddrPtr aAddr);
+
+ /**
+ * Returns the local address of this UDP socket
+ */
+ readonly attribute nsINetAddr localAddr;
+
+ /**
+ * Returns the port of this UDP socket.
+ */
+ readonly attribute long port;
+
+ /**
+ * Returns the address to which this UDP socket is bound. Since a
+ * UDP socket may be bound to multiple network devices, this address
+ * may not necessarily be specific to a single network device. In the
+ * case of an IP socket, the IP address field would be zerod out to
+ * indicate a UDP socket bound to all network devices. Therefore,
+ * this method cannot be used to determine the IP address of the local
+ * system. See nsIDNSService::myHostName if this is what you need.
+ */
+ [noscript] NetAddr getAddress();
+
+ /**
+ * send
+ *
+ * Send out the datagram to specified remote host and port.
+ * DNS lookup will be triggered.
+ *
+ * @param host The remote host name.
+ * @param port The remote port.
+ * @param data The buffer containing the data to be written.
+ * @return number of bytes written. (0 or length of data)
+ */
+ unsigned long send(in AUTF8String host, in unsigned short port,
+ in Array<uint8_t> data);
+
+ /**
+ * sendWithAddr
+ *
+ * Send out the datagram to specified remote host and port.
+ *
+ * @param addr The remote host address.
+ * @param data The buffer containing the data to be written.
+ * @return number of bytes written. (0 or length of data)
+ */
+ unsigned long sendWithAddr(in nsINetAddr addr,
+ in Array<uint8_t> data);
+
+
+ /**
+ * Receive a datagram.
+ * @param addr The remote host address.
+ * @param data The buffer to store received datagram.
+ */
+ [noscript] void recvWithAddr(out NetAddr addr,
+ out Array<uint8_t> data);
+
+ /**
+ * sendWithAddress
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param addr The remote host address.
+ * @param data The buffer containing the data to be written.
+ * @return number of bytes written. (0 or length of data)
+ */
+ [noscript] unsigned long sendWithAddress([const] in NetAddrPtr addr,
+ [array, size_is(length), const] in uint8_t data,
+ in unsigned long length);
+
+ /**
+ * sendBinaryStream
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param host The remote host name.
+ * @param port The remote port.
+ * @param stream The input stream to be sent. This must be a buffered stream implementation.
+ */
+ void sendBinaryStream(in AUTF8String host, in unsigned short port,
+ in nsIInputStream stream);
+
+ /**
+ * sendBinaryStreamWithAddress
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param addr The remote host address.
+ * @param stream The input stream to be sent. This must be a buffered stream implementation.
+ */
+ void sendBinaryStreamWithAddress([const] in NetAddrPtr addr,
+ in nsIInputStream stream);
+
+ /**
+ * joinMulticast
+ *
+ * Join the multicast group specified by |addr|. You are then able to
+ * receive future datagrams addressed to the group.
+ *
+ * @param addr
+ * The multicast group address.
+ * @param iface
+ * The local address of the interface on which to join the group. If
+ * this is not specified, the OS may join the group on all interfaces
+ * or only the primary interface.
+ */
+ void joinMulticast(in AUTF8String addr, [optional] in AUTF8String iface);
+ [noscript] void joinMulticastAddr([const] in NetAddr addr,
+ [const, optional] in NetAddrPtr iface);
+
+ /**
+ * leaveMulticast
+ *
+ * Leave the multicast group specified by |addr|. You will no longer
+ * receive future datagrams addressed to the group.
+ *
+ * @param addr
+ * The multicast group address.
+ * @param iface
+ * The local address of the interface on which to leave the group.
+ * If this is not specified, the OS may leave the group on all
+ * interfaces or only the primary interface.
+ */
+ void leaveMulticast(in AUTF8String addr, [optional] in AUTF8String iface);
+ [noscript] void leaveMulticastAddr([const] in NetAddr addr,
+ [const, optional] in NetAddrPtr iface);
+
+ /**
+ * multicastLoopback
+ *
+ * Whether multicast datagrams sent via this socket should be looped back to
+ * this host (assuming this host has joined the relevant group). Defaults
+ * to true.
+ * Note: This is currently write-only.
+ */
+ attribute boolean multicastLoopback;
+
+ /**
+ * multicastInterface
+ *
+ * The interface that should be used for sending future multicast datagrams.
+ * Note: This is currently write-only.
+ */
+ attribute AUTF8String multicastInterface;
+
+ /**
+ * multicastInterfaceAddr
+ *
+ * The interface that should be used for sending future multicast datagrams.
+ * Note: This is currently write-only.
+ */
+ [noscript] attribute NetAddr multicastInterfaceAddr;
+
+ /**
+ * recvBufferSize
+ *
+ * The size of the receive buffer. Default depends on the OS.
+ */
+ [noscript] attribute long recvBufferSize;
+
+ /**
+ * sendBufferSize
+ *
+ * The size of the send buffer. Default depends on the OS.
+ */
+ [noscript] attribute long sendBufferSize;
+
+ /**
+ * dontFragment
+ *
+ * The don't fragment flag.
+ * The socket must be initialized before calling this function.
+ */
+ [noscript] attribute boolean dontFragment;
+};
+
+/**
+ * nsIUDPSocketListener
+ *
+ * This interface is notified whenever a UDP socket accepts a new connection.
+ * The transport is in the connected state, and read/write streams can be opened
+ * using the normal nsITransport API. The address of the client can be found by
+ * calling the nsISocketTransport::GetAddress method or by inspecting
+ * nsISocketTransport::GetHost, which returns a string representation of the
+ * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal).
+ */
+[scriptable, uuid(2E4B5DD3-7358-4281-B81F-10C62EF39CB5)]
+interface nsIUDPSocketListener : nsISupports
+{
+ /**
+ * onPacketReceived
+ *
+ * This method is called when a client sends an UDP packet.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aMessage
+ * The message.
+ */
+ void onPacketReceived(in nsIUDPSocket aSocket,
+ in nsIUDPMessage aMessage);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The UDP socket is effectively dead after this notification.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aStatus
+ * The reason why the UDP socket stopped listening. If the
+ * UDP socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIUDPSocket aSocket, in nsresult aStatus);
+};
+
+/**
+ * nsIUDPMessage
+ *
+ * This interface is used to encapsulate an incomming UDP message
+ */
+[scriptable, builtinclass, uuid(afdc743f-9cc0-40d8-b442-695dc54bbb74)]
+interface nsIUDPMessage : nsISupports
+{
+ /**
+ * Address of the source of the message
+ */
+ readonly attribute nsINetAddr fromAddr;
+
+ /**
+ * Data of the message
+ */
+ readonly attribute ACString data;
+
+ /**
+ * Stream to send a response
+ */
+ readonly attribute nsIOutputStream outputStream;
+
+ /**
+ * Raw Data of the message
+ */
+ [implicit_jscontext] readonly attribute jsval rawData;
+ [noscript, notxpcom, nostdcall] Uint8TArrayRef getDataAsTArray();
+};
+
+[uuid(99f3d085-3d69-45da-a2c2-a6176af617cb)]
+interface nsIUDPSocketSyncListener : nsISupports
+{
+ /**
+ * onPacketReceived
+ *
+ * This method is called when a client sends an UDP packet.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aMessage
+ * The message.
+ */
+ void onPacketReceived(in nsIUDPSocket aSocket);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The UDP socket is effectively dead after this notification.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aStatus
+ * The reason why the UDP socket stopped listening. If the
+ * UDP socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIUDPSocket aSocket, in nsresult aStatus);
+};
diff --git a/netwerk/base/nsIURI.idl b/netwerk/base/nsIURI.idl
new file mode 100644
index 0000000000..993e0374a8
--- /dev/null
+++ b/netwerk/base/nsIURI.idl
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * URIs are essentially structured names for things -- anything. This interface
+ * provides accessors to get the most basic components of an URI.
+ * If you need to change some parts of the URI use nsIURIMutator.
+ * Subclasses, including nsIURL, impose greater structure on the URI.
+ *
+ * This interface follows Tim Berners-Lee's URI spec (RFC3986) [1], where the
+ * basic URI components are defined as such:
+ * <pre>
+ * ftp://username:password@hostname:portnumber/pathname?query#ref
+ * \ / \ / \ / \ /\ / \ / \ /
+ * - --------------- ------ -------- ------- --- -
+ * | | | | | | |
+ * | | | | FilePath Query Ref
+ * | | | Port \ /
+ * | | Host / ------------
+ * | UserPass / |
+ * Scheme / Path
+ * \ /
+ * --------------------------------
+ * |
+ * PrePath
+ * </pre>
+ * The definition of the URI components has been extended to allow for
+ * internationalized domain names [2] and the more generic IRI structure [3].
+ *
+ * [1] https://tools.ietf.org/html/rfc3986
+ * [2] https://tools.ietf.org/html/rfc5890
+ * [3] https://tools.ietf.org/html/rfc3987
+ */
+
+%{C++
+#include "nsString.h"
+
+#undef GetPort // XXX Windows!
+#undef SetPort // XXX Windows!
+
+namespace mozilla {
+class Encoding;
+namespace ipc {
+class URIParams;
+} // namespace ipc
+} // namespace mozilla
+%}
+
+[ptr] native Encoding(const mozilla::Encoding);
+[ref] native URIParams(mozilla::ipc::URIParams);
+interface nsIURIMutator;
+
+/**
+ * nsIURI - interface for an uniform resource identifier w/ i18n support.
+ *
+ * AUTF8String attributes may contain unescaped UTF-8 characters.
+ * Consumers should be careful to escape the UTF-8 strings as necessary, but
+ * should always try to "display" the UTF-8 version as provided by this
+ * interface.
+ *
+ * AUTF8String attributes may also contain escaped characters.
+ *
+ * Unescaping URI segments is unadvised unless there is intimate
+ * knowledge of the underlying charset or there is no plan to display (or
+ * otherwise enforce a charset on) the resulting URI substring.
+ *
+ * The correct way to create an nsIURI from a string is via
+ * nsIIOService.newURI.
+ *
+ * NOTE: nsBinaryInputStream::ReadObject contains a hackaround to intercept the
+ * old (pre-gecko6) nsIURI IID and swap in the current IID instead, in order
+ * for sessionstore to work after an upgrade. If this IID is revved further,
+ * we will need to add additional checks there for all intermediate IIDs, until
+ * ContentPrincipal is fixed to serialize its URIs as nsISupports (bug 662693).
+ */
+[scriptable, builtinclass, uuid(92073a54-6d78-4f30-913a-b871813208c6)]
+interface nsIURI : nsISupports
+{
+ /************************************************************************
+ * The URI is broken down into the following principal components:
+ */
+
+ /**
+ * Returns a string representation of the URI.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String spec;
+
+%{ C++
+ // An infallible wrapper for GetSpec() that returns a failure indication
+ // string if GetSpec() fails. It is most useful for creating
+ // logging/warning/error messages produced for human consumption, and when
+ // matching a URI spec against a fixed spec such as about:blank.
+ nsCString GetSpecOrDefault()
+ {
+ nsCString spec;
+ nsresult rv = GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ spec.AssignLiteral("[nsIURI::GetSpec failed]");
+ }
+ return spec;
+ }
+%}
+
+ /**
+ * The prePath (eg. scheme://user:password@host:port) returns the string
+ * before the path. This is useful for authentication or managing sessions.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String prePath;
+
+ /**
+ * The Scheme is the protocol to which this URI refers. The scheme is
+ * restricted to the US-ASCII charset per RFC3986.
+ */
+ readonly attribute ACString scheme;
+
+ /**
+ * The username:password (or username only if value doesn't contain a ':')
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String userPass;
+
+ /**
+ * The optional username and password, assuming the preHost consists of
+ * username:password.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String username;
+ readonly attribute AUTF8String password;
+
+ /**
+ * The host:port (or simply the host, if port == -1).
+ */
+ readonly attribute AUTF8String hostPort;
+
+ /**
+ * The host is the internet domain name to which this URI refers. It could
+ * be an IPv4 (or IPv6) address literal. Otherwise it is an ASCII or punycode
+ * encoded string.
+ */
+ readonly attribute AUTF8String host;
+
+ /**
+ * A port value of -1 corresponds to the protocol's default port (eg. -1
+ * implies port 80 for http URIs).
+ */
+ readonly attribute long port;
+
+ /**
+ * The path, typically including at least a leading '/' (but may also be
+ * empty, depending on the protocol).
+ *
+ * Some characters may be escaped.
+ *
+ * This attribute contains query and ref parts for historical reasons.
+ * Use the 'filePath' attribute if you do not want those parts included.
+ */
+ readonly attribute AUTF8String pathQueryRef;
+
+
+ /************************************************************************
+ * An URI supports the following methods:
+ */
+
+ /**
+ * URI equivalence test (not a strict string comparison).
+ *
+ * eg. http://foo.com:80/ == http://foo.com/
+ */
+ boolean equals(in nsIURI other);
+
+ /**
+ * An optimization to do scheme checks without requiring the users of nsIURI
+ * to GetScheme, thereby saving extra allocating and freeing. Returns true if
+ * the schemes match (case ignored).
+ */
+ [infallible] boolean schemeIs(in string scheme);
+
+ /**
+ * This method resolves a relative string into an absolute URI string,
+ * using this URI as the base.
+ *
+ * NOTE: some implementations may have no concept of a relative URI.
+ */
+ AUTF8String resolve(in AUTF8String relativePath);
+
+
+ /************************************************************************
+ * Additional attributes:
+ */
+
+ /**
+ * The URI spec with an ASCII compatible encoding. Host portion follows
+ * the IDNA draft spec. Other parts are URL-escaped per the rules of
+ * RFC2396. The result is strictly ASCII.
+ */
+ readonly attribute ACString asciiSpec;
+
+ /**
+ * The host:port (or simply the host, if port == -1), with an ASCII compatible
+ * encoding. Host portion follows the IDNA draft spec. The result is strictly
+ * ASCII.
+ */
+ readonly attribute ACString asciiHostPort;
+
+ /**
+ * The URI host with an ASCII compatible encoding. Follows the IDNA
+ * draft spec for converting internationalized domain names (UTF-8) to
+ * ASCII for compatibility with existing internet infrasture.
+ */
+ readonly attribute ACString asciiHost;
+
+ /************************************************************************
+ * Additional attribute & methods added for .ref support:
+ */
+
+ /**
+ * Returns the reference portion (the part after the "#") of the URI.
+ * If there isn't one, an empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String ref;
+
+ /**
+ * URI equivalence test (not a strict string comparison), ignoring
+ * the value of the .ref member.
+ *
+ * eg. http://foo.com/# == http://foo.com/
+ * http://foo.com/#aaa == http://foo.com/#bbb
+ */
+ boolean equalsExceptRef(in nsIURI other);
+
+ /**
+ * returns a string for the current URI with the ref element cleared.
+ */
+ readonly attribute AUTF8String specIgnoringRef;
+
+ /**
+ * Returns if there is a reference portion (the part after the "#") of the URI.
+ */
+ readonly attribute boolean hasRef;
+
+ /**
+ * Returns if there is user and pass in the URI.
+ */
+ readonly attribute boolean hasUserPass;
+
+ /************************************************************************
+ * Additional attributes added for .query support:
+ */
+
+ /**
+ * Returns a path including the directory and file portions of a
+ * URL. For example, the filePath of "http://host/foo/bar.html#baz"
+ * is "/foo/bar.html".
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String filePath;
+
+ /**
+ * Returns the query portion (the part after the "?") of the URL.
+ * If there isn't one, an empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String query;
+
+ /**
+ * Returns if there is a query portion (the part after the "?") of the URI.
+ */
+ readonly attribute boolean hasQuery;
+
+ /**
+ * If the URI has a punycode encoded hostname, this will hold the UTF8
+ * representation of that hostname (if that representation doesn't contain
+ * blacklisted characters, and the network.IDN_show_punycode pref is false)
+ * Otherwise, if the hostname is ASCII, it will return the same as .asciiHost
+ */
+ readonly attribute AUTF8String displayHost;
+
+ /**
+ * The displayHost:port (or simply the displayHost, if port == -1).
+ */
+ readonly attribute AUTF8String displayHostPort;
+
+ /**
+ * Returns the same as calling .spec, only with a UTF8 encoded hostname
+ * (if that hostname doesn't contain blacklisted characters, and
+ * the network.IDN_show_punycode pref is false)
+ */
+ readonly attribute AUTF8String displaySpec;
+
+ /**
+ * Returns the same as calling .prePath, only with a UTF8 encoded hostname
+ * (if that hostname doesn't contain blacklisted characters, and
+ * the network.IDN_show_punycode pref is false)
+ */
+ readonly attribute AUTF8String displayPrePath;
+
+ /**
+ * Returns an nsIURIMutator that can be used to make changes to the URI.
+ * After performing the setter operations on the mutator, one may call
+ * mutator.finalize() to get a new immutable URI with the desired
+ * properties.
+ */
+ nsIURIMutator mutate();
+
+ /**
+ * Serializes a URI object to a URIParams data structure in order for being
+ * passed over IPC. For deserialization, see nsIURIMutator.
+ */
+ [noscript, notxpcom] void serialize(in URIParams aParams);
+
+ %{C++
+ // MOZ_DBG support
+ friend std::ostream& operator<<(std::ostream& aOut, const nsIURI& aURI) {
+ nsIURI* uri = const_cast<nsIURI*>(&aURI);
+ return aOut << "nsIURI { " << uri->GetSpecOrDefault() << " }";
+ }
+ %}
+};
diff --git a/netwerk/base/nsIURIMutator.idl b/netwerk/base/nsIURIMutator.idl
new file mode 100644
index 0000000000..c4d9293313
--- /dev/null
+++ b/netwerk/base/nsIURIMutator.idl
@@ -0,0 +1,540 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIURI;
+interface nsIObjectInputStream;
+interface nsIURIMutator;
+
+%{C++
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include <utility>
+
+#undef SetPort // XXX Windows!
+
+namespace mozilla {
+class Encoding;
+}
+
+namespace mozilla {
+namespace ipc {
+class URIParams;
+} // namespace ipc
+} // namespace mozilla
+
+template <class T>
+class BaseURIMutator
+{
+// This is the base class that can be extended by implementors of nsIURIMutator
+// in order to avoid code duplication
+// Class type T should be the type of the class that implements nsIURI
+protected:
+ virtual T* Create()
+ {
+ return new T();
+ }
+
+ [[nodiscard]] nsresult InitFromURI(T* aURI)
+ {
+ nsCOMPtr<nsIURI> clone;
+ nsresult rv = aURI->Clone(getter_AddRefs(clone));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURI = static_cast<T*>(clone.get());
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult InitFromInputStream(nsIObjectInputStream* aStream)
+ {
+ RefPtr<T> uri = Create();
+ nsresult rv = uri->ReadPrivate(aStream);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURI = std::move(uri);
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult InitFromIPCParams(const mozilla::ipc::URIParams& aParams)
+ {
+ RefPtr<T> uri = Create();
+ bool ret = uri->Deserialize(aParams);
+ if (!ret) {
+ return NS_ERROR_FAILURE;
+ }
+ mURI = std::move(uri);
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult InitFromSpec(const nsACString& aSpec)
+ {
+ nsresult rv = NS_OK;
+ RefPtr<T> uri;
+ if (mURI) {
+ // This only works because all other Init methods create a new object
+ mURI.swap(uri);
+ } else {
+ uri = Create();
+ }
+
+ rv = uri->SetSpecInternal(aSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURI = std::move(uri);
+ return NS_OK;
+ }
+
+ RefPtr<T> mURI;
+};
+
+// Since most implementations of nsIURIMutator would extend BaseURIMutator,
+// some methods would have the same implementation. We provide a useful macro
+// to avoid code duplication.
+#define NS_DEFINE_NSIMUTATOR_COMMON \
+ [[nodiscard]] NS_IMETHOD \
+ Deserialize(const mozilla::ipc::URIParams& aParams) override \
+ { \
+ return InitFromIPCParams(aParams); \
+ } \
+ \
+ [[nodiscard]] NS_IMETHOD \
+ Finalize(nsIURI** aURI) override \
+ { \
+ mURI.forget(aURI); return NS_OK; \
+ } \
+ \
+ [[nodiscard]] NS_IMETHOD \
+ SetSpec(const nsACString& aSpec, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return InitFromSpec(aSpec); \
+ } \
+
+// Implements AddRef, Release and QueryInterface for the mutator
+#define NS_IMPL_NSIURIMUTATOR_ISUPPORTS(aClass, ...) \
+ NS_IMPL_ADDREF(aClass) \
+ NS_IMPL_RELEASE(aClass) \
+ NS_IMPL_NSIURIMUTATOR_QUERY_INTERFACE(aClass, __VA_ARGS__) \
+
+// The list of interfaces is queried and an AddRef-ed pointer is returned if
+// there is a match. Otherwise, we call QueryInterface on mURI and return.
+// The reason for this specialized QueryInterface implementation is that we
+// we want to be able to instantiate the mutator for a given CID of a
+// nsIURI implementation, call nsISerializable.Read() on the mutator to
+// deserialize the URI then QueryInterface the mutator to an nsIURI interface.
+// See bug 1442239.
+// If you QueryInterface a mutator to an interface of the URI
+// implementation this is similar to calling Finalize.
+#define NS_IMPL_NSIURIMUTATOR_QUERY_INTERFACE(aClass, ...) \
+ static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \
+ "Need more arguments"); \
+ NS_INTERFACE_MAP_BEGIN(aClass) \
+ nsCOMPtr<nsIURI> uri; \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIMutator) \
+ MOZ_FOR_EACH(NS_INTERFACE_MAP_ENTRY, (), (__VA_ARGS__)) \
+ if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { \
+ foundInterface = nullptr; \
+ } else \
+ if (mURI && \
+ NS_SUCCEEDED(mURI->QueryInterface(aIID, getter_AddRefs(uri)))) { \
+ mURI = nullptr; \
+ foundInterface = uri.get(); \
+ } else \
+ NS_INTERFACE_MAP_END \
+
+%}
+
+[ptr] native Encoding(const mozilla::Encoding);
+[ref] native const_URIParams_ref(const mozilla::ipc::URIParams);
+
+[scriptable, builtinclass, uuid(1fc53257-898b-4c5e-b69c-05bc84b4cd8f)]
+interface nsIURISetSpec : nsISupports
+{
+ /**
+ * This setter is different from all other setters because it may be used to
+ * initialize the object. We define it separately allowing mutator implementors
+ * to define it separately, while the rest of the setters may be simply
+ * forwarded to the mutable URI.
+ */
+ [must_use] nsIURIMutator setSpec(in AUTF8String aSpec);
+};
+
+/**
+ * These methods allow the mutator to change various parts of the URI.
+ * They return the same nsIURIMutator so that we may chain setter operations:
+ * Example:
+ * let newURI = uri.mutate()
+ * .setSpec("http://example.com")
+ * .setQuery("hello")
+ * .finalize();
+ */
+[scriptable, builtinclass, uuid(5403a6ec-99d7-405e-8b45-9f805bbdfcef)]
+interface nsIURISetters : nsIURISetSpec
+{
+ /**
+ * Setting the scheme outside of a protocol handler implementation is highly
+ * discouraged since that will generally lead to incorrect results.
+ */
+ [must_use] nsIURIMutator setScheme(in AUTF8String aScheme);
+ [must_use] nsIURIMutator setUserPass(in AUTF8String aUserPass);
+ [must_use] nsIURIMutator setUsername(in AUTF8String aUsername);
+ [must_use] nsIURIMutator setPassword(in AUTF8String aPassword);
+
+ /**
+ * If you setHostPort to a value that only has a host part, the port
+ * will not be reset. To reset the port set it to -1 beforehand.
+ * If setting the host succeeds, this method will return NS_OK, even if
+ * setting the port fails (error in parsing the port, or value out of range)
+ */
+ [must_use] nsIURIMutator setHostPort(in AUTF8String aHostPort);
+ [must_use] nsIURIMutator setHost(in AUTF8String aHost);
+ [must_use] nsIURIMutator setPort(in long aPort);
+ [must_use] nsIURIMutator setPathQueryRef(in AUTF8String aPathQueryRef);
+ [must_use] nsIURIMutator setRef(in AUTF8String aRef);
+ [must_use] nsIURIMutator setFilePath(in AUTF8String aFilePath);
+ [must_use] nsIURIMutator setQuery(in AUTF8String aQuery);
+ [must_use, noscript] nsIURIMutator setQueryWithEncoding(in AUTF8String query, in Encoding encoding);
+};
+
+%{C++
+
+// Using this macro instead of NS_FORWARD_SAFE_NSIURISETTERS makes chaining
+// setter operations possible.
+#define NS_FORWARD_SAFE_NSIURISETTERS_RET(_to) \
+ [[nodiscard]] NS_IMETHOD \
+ SetScheme(const nsACString& aScheme, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetScheme(aScheme); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetUserPass(const nsACString& aUserPass, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetUserPass(aUserPass); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetUsername(const nsACString& aUsername, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetUsername(aUsername); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetPassword(const nsACString& aPassword, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetPassword(aPassword); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetHostPort(const nsACString& aHostPort, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetHostPort(aHostPort); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetHost(const nsACString& aHost, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetHost(aHost); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetPort(int32_t aPort, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetPort(aPort); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetPathQueryRef(const nsACString& aPathQueryRef, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetPathQueryRef(aPathQueryRef); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetRef(const nsACString& aRef, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetRef(aRef); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetFilePath(const nsACString& aFilePath, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetFilePath(aFilePath); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetQuery(const nsACString& aQuery, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetQuery(aQuery); \
+ } \
+ [[nodiscard]] NS_IMETHOD \
+ SetQueryWithEncoding(const nsACString& query, const mozilla::Encoding *encoding, nsIURIMutator** aMutator) override \
+ { \
+ if (aMutator) NS_ADDREF(*aMutator = this); \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->SetQueryWithEncoding(query, encoding); \
+ } \
+
+%}
+
+[scriptable, builtinclass, uuid(4d1f3103-1c44-4dcd-b717-5d22a697a7d9)]
+interface nsIURIMutator : nsIURISetters
+{
+ /**
+ * Initalizes the URI by reading IPC URIParams.
+ * See nsIURI.
+ */
+ [noscript, notxpcom, must_use]
+ nsresult deserialize(in const_URIParams_ref aParams);
+
+ /**
+ * Finishes changing or constructing the URI and returns an immutable URI.
+ */
+ [must_use]
+ nsIURI finalize();
+};
+
+%{C++
+
+// This templated struct is used to extract the class type of the method
+template <typename Method>
+struct nsMethodTypeTraits;
+
+template <class C, typename R, typename... As>
+struct nsMethodTypeTraits<R(C::*)(As...)>
+{
+ typedef C class_type;
+};
+
+#ifdef NS_HAVE_STDCALL
+template <class C, typename R, typename... As>
+struct nsMethodTypeTraits<R(__stdcall C::*)(As...)>
+{
+ typedef C class_type;
+};
+#endif
+
+
+// This class provides a useful helper that allows chaining of setter operations
+class MOZ_STACK_CLASS NS_MutateURI
+{
+public:
+ explicit NS_MutateURI(nsIURI* aURI);
+ explicit NS_MutateURI(const char * aContractID);
+
+ explicit NS_MutateURI(nsIURIMutator* m)
+ {
+ mStatus = m ? NS_OK : NS_ERROR_NULL_POINTER;
+ mMutator = m;
+ NS_ENSURE_SUCCESS_VOID(mStatus);
+ }
+
+ NS_MutateURI& SetSpec(const nsACString& aSpec)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetSpec(aSpec, nullptr);
+ return *this;
+ }
+ NS_MutateURI& SetScheme(const nsACString& aScheme)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetScheme(aScheme, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetUserPass(const nsACString& aUserPass)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetUserPass(aUserPass, nullptr);
+ return *this;
+ }
+ NS_MutateURI& SetUsername(const nsACString& aUsername)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetUsername(aUsername, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetPassword(const nsACString& aPassword)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetPassword(aPassword, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetHostPort(const nsACString& aHostPort)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetHostPort(aHostPort, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetHost(const nsACString& aHost)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetHost(aHost, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetPort(int32_t aPort)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetPort(aPort, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetPathQueryRef(const nsACString& aPathQueryRef)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetPathQueryRef(aPathQueryRef, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetRef(const nsACString& aRef)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetRef(aRef, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetFilePath(const nsACString& aFilePath)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetFilePath(aFilePath, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetQuery(const nsACString& aQuery)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetQuery(aQuery, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+ NS_MutateURI& SetQueryWithEncoding(const nsACString& query, const mozilla::Encoding *encoding)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = mMutator->SetQueryWithEncoding(query, encoding, nullptr);
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ return *this;
+ }
+
+ /**
+ * This method allows consumers to call the methods declared in other
+ * interfaces implemented by the mutator object.
+ *
+ * Example:
+ * nsCOMPtr<nsIURI> uri;
+ * nsresult rv = NS_MutateURI(new URIClass::Mutator())
+ * .SetSpec(aSpec)
+ * .Apply(&SomeInterface::Method, arg1, arg2)
+ * .Finalize(uri);
+ *
+ * If mMutator does not implement SomeInterface, do_QueryInterface will fail
+ * and the method will not be called.
+ * If aMethod does not exist, or if there is a mismatch between argument
+ * types, or the number of arguments, then there will be a compile error.
+ */
+ template <typename Method, typename... Args>
+ NS_MutateURI& Apply(Method aMethod, Args&&... aArgs)
+ {
+ typedef typename nsMethodTypeTraits<Method>::class_type Interface;
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ nsCOMPtr<Interface> target = do_QueryInterface(mMutator, &mStatus);
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus), "URL object must implement interface");
+ NS_ENSURE_SUCCESS(mStatus, *this);
+ mStatus = (target->*aMethod)(std::forward<Args>(aArgs)...);
+ return *this;
+ }
+
+ template <class C>
+ [[nodiscard]] nsresult Finalize(nsCOMPtr<C>& aURI)
+ {
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ nsCOMPtr<nsIURI> uri;
+ mStatus = mMutator->Finalize(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ aURI = do_QueryInterface(uri, &mStatus);
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail.
+ return NS_OK;
+ }
+
+ // Overload for nsIURI to avoid query interface.
+ [[nodiscard]] nsresult Finalize(nsCOMPtr<nsIURI>& aURI)
+ {
+ if (NS_FAILED(mStatus)) return mStatus;
+ mStatus = mMutator->Finalize(getter_AddRefs(aURI));
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail.
+ return NS_OK;
+ }
+
+ template <class C>
+ [[nodiscard]] nsresult Finalize(C** aURI)
+ {
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ nsCOMPtr<nsIURI> uri;
+ mStatus = mMutator->Finalize(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ nsCOMPtr<C> result = do_QueryInterface(uri, &mStatus);
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ result.forget(aURI);
+ mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail.
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult Finalize(nsIURI** aURI)
+ {
+ if (NS_FAILED(mStatus)) return mStatus;
+ mStatus = mMutator->Finalize(aURI);
+ NS_ENSURE_SUCCESS(mStatus, mStatus);
+
+ mStatus = NS_ERROR_NOT_AVAILABLE; // Second call to Finalize should fail.
+ return NS_OK;
+ }
+
+ nsresult GetStatus() { return mStatus; }
+private:
+ nsresult mStatus;
+ nsCOMPtr<nsIURIMutator> mMutator;
+};
+
+%}
diff --git a/netwerk/base/nsIURIMutatorUtils.cpp b/netwerk/base/nsIURIMutatorUtils.cpp
new file mode 100644
index 0000000000..6c7fd7c92b
--- /dev/null
+++ b/netwerk/base/nsIURIMutatorUtils.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURIMutator.h"
+#include "nsIURI.h"
+#include "nsComponentManagerUtils.h"
+
+static nsresult GetURIMutator(nsIURI* aURI, nsIURIMutator** aMutator) {
+ if (NS_WARN_IF(!aURI)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return aURI->Mutate(aMutator);
+}
+
+NS_MutateURI::NS_MutateURI(nsIURI* aURI) {
+ mStatus = GetURIMutator(aURI, getter_AddRefs(mMutator));
+ NS_ENSURE_SUCCESS_VOID(mStatus);
+}
+
+NS_MutateURI::NS_MutateURI(const char* aContractID)
+ : mStatus(NS_ERROR_NOT_INITIALIZED) {
+ mMutator = do_CreateInstance(aContractID, &mStatus);
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus), "Called with wrong aContractID");
+}
diff --git a/netwerk/base/nsIURIWithSpecialOrigin.idl b/netwerk/base/nsIURIWithSpecialOrigin.idl
new file mode 100644
index 0000000000..3b8e2e0c97
--- /dev/null
+++ b/netwerk/base/nsIURIWithSpecialOrigin.idl
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsIURIWithSpecialOrigin is implemented by URIs need to supply an origin that
+ * does not match the spec. This is exclusively used in comm-central's Mailnews module.
+ */
+[scriptable, builtinclass, uuid(4f65569b-d6fc-4580-94d9-21e775658a2a)]
+interface nsIURIWithSpecialOrigin : nsISupports
+{
+ /**
+ * Special origin.
+ */
+ readonly attribute nsIURI origin;
+};
diff --git a/netwerk/base/nsIURL.idl b/netwerk/base/nsIURL.idl
new file mode 100644
index 0000000000..11a9fa0f47
--- /dev/null
+++ b/netwerk/base/nsIURL.idl
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURI.idl"
+interface nsIURIMutator;
+
+/**
+ * The nsIURL interface provides convenience methods that further
+ * break down the path portion of nsIURI:
+ *
+ * http://host/directory/fileBaseName.fileExtension?query
+ * http://host/directory/fileBaseName.fileExtension#ref
+ * \ \ /
+ * \ -----------------------
+ * \ | /
+ * \ fileName /
+ * ----------------------------
+ * |
+ * filePath
+ */
+[scriptable, builtinclass, uuid(86adcd89-0b70-47a2-b0fe-5bb2c5f37e31)]
+interface nsIURL : nsIURI
+{
+ /*************************************************************************
+ * The URL path is broken down into the following principal components:
+ *
+ * attribute AUTF8String filePath;
+ * attribute AUTF8String query;
+ *
+ * These are inherited from nsIURI.
+ */
+
+ /*************************************************************************
+ * The URL filepath is broken down into the following sub-components:
+ */
+
+ /**
+ * Returns the directory portion of a URL. If the URL denotes a path to a
+ * directory and not a file, e.g. http://host/foo/bar/, then the Directory
+ * attribute accesses the complete /foo/bar/ portion, and the FileName is
+ * the empty string. If the trailing slash is omitted, then the Directory
+ * is /foo/ and the file is bar (i.e. this is a syntactic, not a semantic
+ * breakdown of the Path). And hence don't rely on this for something to
+ * be a definitely be a file. But you can get just the leading directory
+ * portion for sure.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String directory;
+
+ /**
+ * Returns the file name portion of a URL. If the URL denotes a path to a
+ * directory and not a file, e.g. http://host/foo/bar/, then the Directory
+ * attribute accesses the complete /foo/bar/ portion, and the FileName is
+ * the empty string. Note that this is purely based on searching for the
+ * last trailing slash. And hence don't rely on this to be a definite file.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String fileName;
+
+ /*************************************************************************
+ * The URL filename is broken down even further:
+ */
+
+ /**
+ * Returns the file basename portion of a filename in a url.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String fileBaseName;
+
+ /**
+ * Returns the file extension portion of a filename in a url. If a file
+ * extension does not exist, the empty string is returned.
+ *
+ * Some characters may be escaped.
+ */
+ readonly attribute AUTF8String fileExtension;
+
+ /**
+ * This method takes a uri and compares the two. The common uri portion
+ * is returned as a string. The minimum common uri portion is the
+ * protocol, and any of these if present: login, password, host and port
+ * If no commonality is found, "" is returned. If they are identical, the
+ * whole path with file/ref/etc. is returned. For file uris, it is
+ * expected that the common spec would be at least "file:///" since '/' is
+ * a shared common root.
+ *
+ * Examples:
+ * this.spec aURIToCompare.spec result
+ * 1) http://mozilla.org/ http://www.mozilla.org/ ""
+ * 2) http://foo.com/bar/ ftp://foo.com/bar/ ""
+ * 3) http://foo.com:8080/ http://foo.com/bar/ ""
+ * 4) ftp://user@foo.com/ ftp://user:pw@foo.com/ ""
+ * 5) ftp://foo.com/bar/ ftp://foo.com/bar ftp://foo.com/
+ * 6) ftp://foo.com/bar/ ftp://foo.com/bar/b.html ftp://foo.com/bar/
+ * 7) http://foo.com/a.htm#i http://foo.com/b.htm http://foo.com/
+ * 8) ftp://foo.com/c.htm#i ftp://foo.com/c.htm ftp://foo.com/c.htm
+ * 9) file:///a/b/c.html file:///d/e/c.html file:///
+ */
+ AUTF8String getCommonBaseSpec(in nsIURI aURIToCompare);
+
+ /**
+ * This method tries to create a string which specifies the location of the
+ * argument relative to |this|. If the argument and |this| are equal, the
+ * method returns "". If any of the URIs' scheme, host, userpass, or port
+ * don't match, the method returns the full spec of the argument.
+ *
+ * Examples:
+ * this.spec aURIToCompare.spec result
+ * 1) http://mozilla.org/ http://www.mozilla.org/ http://www.mozilla.org/
+ * 2) http://mozilla.org/ http://www.mozilla.org http://www.mozilla.org/
+ * 3) http://foo.com/bar/ http://foo.com:80/bar/ ""
+ * 4) http://foo.com/ http://foo.com/a.htm#b a.html#b
+ * 5) http://foo.com/a/b/ http://foo.com/c ../../c
+ */
+ AUTF8String getRelativeSpec(in nsIURI aURIToCompare);
+
+};
+
+[scriptable, builtinclass, uuid(25072eb8-f1e6-482f-9ca9-eddd3d65169a)]
+interface nsIURLMutator : nsISupports
+{
+ [must_use] nsIURIMutator setFileName(in AUTF8String aFileName);
+ [must_use] nsIURIMutator setFileBaseName(in AUTF8String aFileBaseName);
+ [must_use] nsIURIMutator setFileExtension(in AUTF8String aFileExtension);
+};
diff --git a/netwerk/base/nsIURLParser.idl b/netwerk/base/nsIURLParser.idl
new file mode 100644
index 0000000000..3d6ac19b8c
--- /dev/null
+++ b/netwerk/base/nsIURLParser.idl
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIURLParser specifies the interface to an URL parser that attempts to
+ * follow the definitions of RFC 2396.
+ */
+[scriptable, uuid(78c5d19f-f5d2-4732-8d3d-d5a7d7133bc0)]
+interface nsIURLParser : nsISupports
+{
+ /**
+ * The string to parse in the following methods may be given as a null
+ * terminated string, in which case the length argument should be -1.
+ *
+ * Out parameters of the following methods are all optional (ie. the caller
+ * may pass-in a NULL value if the corresponding results are not needed).
+ * Signed out parameters may hold a value of -1 if the corresponding result
+ * is not part of the string being parsed.
+ *
+ * The parsing routines attempt to be as forgiving as possible.
+ */
+
+ /**
+ * ParseSpec breaks the URL string up into its 3 major components: a scheme,
+ * an authority section (hostname, etc.), and a path.
+ *
+ * spec = <scheme>://<authority><path>
+ */
+ void parseURL (in string spec, in long specLen,
+ out unsigned long schemePos, out long schemeLen,
+ out unsigned long authorityPos, out long authorityLen,
+ out unsigned long pathPos, out long pathLen);
+
+ /**
+ * ParseAuthority breaks the authority string up into its 4 components:
+ * username, password, hostname, and hostport.
+ *
+ * auth = <username>:<password>@<hostname>:<port>
+ */
+ void parseAuthority (in string authority, in long authorityLen,
+ out unsigned long usernamePos, out long usernameLen,
+ out unsigned long passwordPos, out long passwordLen,
+ out unsigned long hostnamePos, out long hostnameLen,
+ out long port);
+
+ /**
+ * userinfo = <username>:<password>
+ */
+ void parseUserInfo (in string userinfo, in long userinfoLen,
+ out unsigned long usernamePos, out long usernameLen,
+ out unsigned long passwordPos, out long passwordLen);
+
+ /**
+ * serverinfo = <hostname>:<port>
+ */
+ void parseServerInfo (in string serverinfo, in long serverinfoLen,
+ out unsigned long hostnamePos, out long hostnameLen,
+ out long port);
+
+ /**
+ * ParsePath breaks the path string up into its 3 major components: a file path,
+ * a query string, and a reference string.
+ *
+ * path = <filepath>?<query>#<ref>
+ */
+ void parsePath (in string path, in long pathLen,
+ out unsigned long filepathPos, out long filepathLen,
+ out unsigned long queryPos, out long queryLen,
+ out unsigned long refPos, out long refLen);
+
+ /**
+ * ParseFilePath breaks the file path string up into: the directory portion,
+ * file base name, and file extension.
+ *
+ * filepath = <directory><basename>.<extension>
+ */
+ void parseFilePath (in string filepath, in long filepathLen,
+ out unsigned long directoryPos, out long directoryLen,
+ out unsigned long basenamePos, out long basenameLen,
+ out unsigned long extensionPos, out long extensionLen);
+
+ /**
+ * filename = <basename>.<extension>
+ */
+ void parseFileName (in string filename, in long filenameLen,
+ out unsigned long basenamePos, out long basenameLen,
+ out unsigned long extensionPos, out long extensionLen);
+};
+
+%{C++
+// url parser key for use with the category manager
+// mapping from scheme to url parser.
+#define NS_IURLPARSER_KEY "@mozilla.org/urlparser;1"
+%}
diff --git a/netwerk/base/nsIUploadChannel.idl b/netwerk/base/nsIUploadChannel.idl
new file mode 100644
index 0000000000..1c059e8869
--- /dev/null
+++ b/netwerk/base/nsIUploadChannel.idl
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+
+/**
+ * nsIUploadChannel
+ *
+ * A channel may optionally implement this interface if it supports the
+ * notion of uploading a data stream. The upload stream may only be set
+ * prior to the invocation of asyncOpen on the channel.
+ */
+[scriptable, uuid(5cfe15bd-5adb-4a7f-9e55-4f5a67d15794)]
+interface nsIUploadChannel : nsISupports
+{
+ /**
+ * Sets a stream to be uploaded by this channel.
+ *
+ * Most implementations of this interface require that the stream:
+ * (1) implement threadsafe addRef and release
+ * (2) implement nsIInputStream::readSegments
+ * (3) implement nsISeekableStream::seek
+ *
+ * History here is that we need to support both streams that already have
+ * headers (e.g., Content-Type and Content-Length) information prepended to
+ * the stream (by plugins) as well as clients (composer, uploading
+ * application) that want to upload data streams without any knowledge of
+ * protocol specifications. For this reason, we have a special meaning
+ * for the aContentType parameter (see below).
+ *
+ * @param aStream
+ * The stream to be uploaded by this channel.
+ * @param aContentType
+ * If aContentType is empty, the protocol will assume that no
+ * content headers are to be added to the uploaded stream and that
+ * any required headers are already encoded in the stream. In the
+ * case of HTTP, if this parameter is non-empty, then its value will
+ * replace any existing Content-Type header on the HTTP request.
+ * In the case of FTP and FILE, this parameter is ignored.
+ * @param aContentLength
+ * A value of -1 indicates that the length of the stream should be
+ * determined by calling the stream's |available| method.
+ */
+ void setUploadStream(in nsIInputStream aStream,
+ in ACString aContentType,
+ in long long aContentLength);
+
+ /**
+ * Get the stream (to be) uploaded by this channel.
+ */
+ readonly attribute nsIInputStream uploadStream;
+};
diff --git a/netwerk/base/nsIUploadChannel2.idl b/netwerk/base/nsIUploadChannel2.idl
new file mode 100644
index 0000000000..45f1d76682
--- /dev/null
+++ b/netwerk/base/nsIUploadChannel2.idl
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIRunnable;
+
+[scriptable, uuid(2f712b52-19c5-4e0c-9e8f-b5c7c3b67049)]
+interface nsIUploadChannel2 : nsISupports
+{
+ /**
+ * Sets a stream to be uploaded by this channel with the specified
+ * Content-Type and Content-Length header values.
+ *
+ * Most implementations of this interface require that the stream:
+ * (1) implement threadsafe addRef and release
+ * (2) implement nsIInputStream::readSegments
+ * (3) implement nsISeekableStream::seek
+ *
+ * @param aStream
+ * The stream to be uploaded by this channel.
+ * @param aContentType
+ * This value will replace any existing Content-Type
+ * header on the HTTP request, regardless of whether
+ * or not its empty.
+ * @param aContentLength
+ * A value of -1 indicates that the length of the stream should be
+ * determined by calling the stream's |available| method.
+ * @param aMethod
+ * The HTTP request method to set on the stream.
+ * @param aStreamHasHeaders
+ * True if the stream already contains headers for the HTTP request.
+ */
+ void explicitSetUploadStream(in nsIInputStream aStream,
+ in ACString aContentType,
+ in long long aContentLength,
+ in ACString aMethod,
+ in boolean aStreamHasHeaders);
+
+ /**
+ * Value of aStreamHasHeaders from the last successful call to
+ * explicitSetUploadStream. TRUE indicates the attached upload stream
+ * contains request headers.
+ */
+ readonly attribute boolean uploadStreamHasHeaders;
+
+ /**
+ * Clones the upload stream. May only be called in the parent process.
+ * aContentLength could be -1 in case the size of the stream is unknown,
+ * otherwise it will contain the known size of the stream.
+ */
+ [noscript]
+ nsIInputStream cloneUploadStream(out long long aContentLength);
+};
diff --git a/netwerk/base/nsIncrementalDownload.cpp b/netwerk/base/nsIncrementalDownload.cpp
new file mode 100644
index 0000000000..d7cb658092
--- /dev/null
+++ b/netwerk/base/nsIncrementalDownload.cpp
@@ -0,0 +1,878 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsIIncrementalDownload.h"
+#include "nsIRequestObserver.h"
+#include "nsIProgressEventSink.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIFile.h"
+#include "nsIHttpChannel.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsWeakReference.h"
+#include "prio.h"
+#include "prprf.h"
+#include <algorithm>
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/UniquePtr.h"
+
+// Default values used to initialize a nsIncrementalDownload object.
+#define DEFAULT_CHUNK_SIZE (4096 * 16) // bytes
+#define DEFAULT_INTERVAL 60 // seconds
+
+#define UPDATE_PROGRESS_INTERVAL PRTime(100 * PR_USEC_PER_MSEC) // 100ms
+
+// Number of times to retry a failed byte-range request.
+#define MAX_RETRY_COUNT 20
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+static LazyLogModule gIDLog("IncrementalDownload");
+#undef LOG
+#define LOG(args) MOZ_LOG(gIDLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+
+static nsresult WriteToFile(nsIFile* lf, const char* data, uint32_t len,
+ int32_t flags) {
+ PRFileDesc* fd;
+ int32_t mode = 0600;
+ nsresult rv;
+ rv = lf->OpenNSPRFileDesc(flags, mode, &fd);
+ if (NS_FAILED(rv)) return rv;
+
+ if (len) {
+ rv = PR_Write(fd, data, len) == int32_t(len) ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ PR_Close(fd);
+ return rv;
+}
+
+static nsresult AppendToFile(nsIFile* lf, const char* data, uint32_t len) {
+ int32_t flags = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
+ return WriteToFile(lf, data, len, flags);
+}
+
+// maxSize may be -1 if unknown
+static void MakeRangeSpec(const int64_t& size, const int64_t& maxSize,
+ int32_t chunkSize, bool fetchRemaining,
+ nsCString& rangeSpec) {
+ rangeSpec.AssignLiteral("bytes=");
+ rangeSpec.AppendInt(int64_t(size));
+ rangeSpec.Append('-');
+
+ if (fetchRemaining) return;
+
+ int64_t end = size + int64_t(chunkSize);
+ if (maxSize != int64_t(-1) && end > maxSize) end = maxSize;
+ end -= 1;
+
+ rangeSpec.AppendInt(int64_t(end));
+}
+
+//-----------------------------------------------------------------------------
+
+class nsIncrementalDownload final : public nsIIncrementalDownload,
+ public nsIStreamListener,
+ public nsIObserver,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsSupportsWeakReference,
+ public nsIAsyncVerifyRedirectCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIINCREMENTALDOWNLOAD
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+ nsIncrementalDownload() = default;
+
+ private:
+ ~nsIncrementalDownload() = default;
+ nsresult FlushChunk();
+ void UpdateProgress();
+ nsresult CallOnStartRequest();
+ void CallOnStopRequest();
+ nsresult StartTimer(int32_t interval);
+ nsresult ProcessTimeout();
+ nsresult ReadCurrentSize();
+ nsresult ClearRequestHeader(nsIHttpChannel* channel);
+
+ nsCOMPtr<nsIRequestObserver> mObserver;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mFinalURI;
+ nsCOMPtr<nsIFile> mDest;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsITimer> mTimer;
+ mozilla::UniquePtr<char[]> mChunk;
+ int32_t mChunkLen{0};
+ int32_t mChunkSize{DEFAULT_CHUNK_SIZE};
+ int32_t mInterval{DEFAULT_INTERVAL};
+ int64_t mTotalSize{-1};
+ int64_t mCurrentSize{-1};
+ uint32_t mLoadFlags{LOAD_NORMAL};
+ int32_t mNonPartialCount{0};
+ nsresult mStatus{NS_OK};
+ bool mIsPending{false};
+ bool mDidOnStartRequest{false};
+ PRTime mLastProgressUpdate{0};
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ nsCOMPtr<nsIChannel> mNewRedirectChannel;
+ nsCString mPartialValidator;
+ bool mCacheBust{false};
+
+ // nsITimerCallback is implemented on a subclass so that the name attribute
+ // doesn't conflict with the name attribute of the nsIRequest interface.
+ class TimerCallback final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ explicit TimerCallback(nsIncrementalDownload* aIncrementalDownload);
+
+ private:
+ ~TimerCallback() = default;
+
+ RefPtr<nsIncrementalDownload> mIncrementalDownload;
+ };
+};
+
+nsresult nsIncrementalDownload::FlushChunk() {
+ NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known");
+
+ if (mChunkLen == 0) return NS_OK;
+
+ nsresult rv = AppendToFile(mDest, mChunk.get(), mChunkLen);
+ if (NS_FAILED(rv)) return rv;
+
+ mCurrentSize += int64_t(mChunkLen);
+ mChunkLen = 0;
+
+ return NS_OK;
+}
+
+void nsIncrementalDownload::UpdateProgress() {
+ mLastProgressUpdate = PR_Now();
+
+ if (mProgressSink) {
+ mProgressSink->OnProgress(this, mCurrentSize + mChunkLen, mTotalSize);
+ }
+}
+
+nsresult nsIncrementalDownload::CallOnStartRequest() {
+ if (!mObserver || mDidOnStartRequest) return NS_OK;
+
+ mDidOnStartRequest = true;
+ return mObserver->OnStartRequest(this);
+}
+
+void nsIncrementalDownload::CallOnStopRequest() {
+ if (!mObserver) return;
+
+ // Ensure that OnStartRequest is always called once before OnStopRequest.
+ nsresult rv = CallOnStartRequest();
+ if (NS_SUCCEEDED(mStatus)) mStatus = rv;
+
+ mIsPending = false;
+
+ mObserver->OnStopRequest(this, mStatus);
+ mObserver = nullptr;
+}
+
+nsresult nsIncrementalDownload::StartTimer(int32_t interval) {
+ auto callback = MakeRefPtr<TimerCallback>(this);
+ return NS_NewTimerWithCallback(getter_AddRefs(mTimer), callback,
+ interval * 1000, nsITimer::TYPE_ONE_SHOT);
+}
+
+nsresult nsIncrementalDownload::ProcessTimeout() {
+ NS_ASSERTION(!mChannel, "how can we have a channel?");
+
+ // Handle existing error conditions
+ if (NS_FAILED(mStatus)) {
+ CallOnStopRequest();
+ return NS_OK;
+ }
+
+ // Fetch next chunk
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(
+ getter_AddRefs(channel), mFinalURI, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // nsICookieJarSettings
+ nullptr, // PerformanceStorage
+ nullptr, // loadGroup
+ this, // aCallbacks
+ mLoadFlags);
+
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(mCurrentSize != int64_t(-1),
+ "we should know the current file size by now");
+
+ rv = ClearRequestHeader(http);
+ if (NS_FAILED(rv)) return rv;
+
+ // Don't bother making a range request if we are just going to fetch the
+ // entire document.
+ if (mInterval || mCurrentSize != int64_t(0)) {
+ nsAutoCString range;
+ MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
+
+ rv = http->SetRequestHeader("Range"_ns, range, false);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mPartialValidator.IsEmpty()) {
+ rv = http->SetRequestHeader("If-Range"_ns, mPartialValidator, false);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::ProcessTimeout\n"
+ " failed to set request header: If-Range\n"));
+ }
+ }
+
+ if (mCacheBust) {
+ rv = http->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns, false);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::ProcessTimeout\n"
+ " failed to set request header: If-Range\n"));
+ }
+ rv = http->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::ProcessTimeout\n"
+ " failed to set request header: If-Range\n"));
+ }
+ }
+ }
+
+ rv = channel->AsyncOpen(this);
+ if (NS_FAILED(rv)) return rv;
+
+ // Wait to assign mChannel when we know we are going to succeed. This is
+ // important because we don't want to introduce a reference cycle between
+ // mChannel and this until we know for a fact that AsyncOpen has succeeded,
+ // thus ensuring that our stream listener methods will be invoked.
+ mChannel = channel;
+ return NS_OK;
+}
+
+// Reads the current file size and validates it.
+nsresult nsIncrementalDownload::ReadCurrentSize() {
+ int64_t size;
+ nsresult rv = mDest->GetFileSize((int64_t*)&size);
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ mCurrentSize = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ mCurrentSize = size;
+ return NS_OK;
+}
+
+// nsISupports
+
+NS_IMPL_ISUPPORTS(nsIncrementalDownload, nsIIncrementalDownload, nsIRequest,
+ nsIStreamListener, nsIRequestObserver, nsIObserver,
+ nsIInterfaceRequestor, nsIChannelEventSink,
+ nsISupportsWeakReference, nsIAsyncVerifyRedirectCallback)
+
+// nsIRequest
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetName(nsACString& name) {
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+
+ return mURI->GetSpec(name);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::IsPending(bool* isPending) {
+ *isPending = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetStatus(nsresult* status) {
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIncrementalDownload::SetCanceledReason(
+ const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsIncrementalDownload::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsIncrementalDownload::CancelWithReason(
+ nsresult aStatus, const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Cancel(nsresult status) {
+ NS_ENSURE_ARG(NS_FAILED(status));
+
+ // Ignore this cancelation if we're already canceled.
+ if (NS_FAILED(mStatus)) return NS_OK;
+
+ mStatus = status;
+
+ // Nothing more to do if callbacks aren't pending.
+ if (!mIsPending) return NS_OK;
+
+ if (mChannel) {
+ mChannel->Cancel(mStatus);
+ NS_ASSERTION(!mTimer, "what is this timer object doing here?");
+ } else {
+ // dispatch a timer callback event to drive invoking our listener's
+ // OnStopRequest.
+ if (mTimer) mTimer->Cancel();
+ StartTimer(0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsIncrementalDownload::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetLoadFlags(nsLoadFlags* loadFlags) {
+ *loadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags) {
+ mLoadFlags = loadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetLoadGroup(nsILoadGroup** loadGroup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetLoadGroup(nsILoadGroup* loadGroup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIIncrementalDownload
+
+NS_IMETHODIMP
+nsIncrementalDownload::Init(nsIURI* uri, nsIFile* dest, int32_t chunkSize,
+ int32_t interval) {
+ // Keep it simple: only allow initialization once
+ NS_ENSURE_FALSE(mURI, NS_ERROR_ALREADY_INITIALIZED);
+
+ mDest = dest;
+ NS_ENSURE_ARG(mDest);
+
+ mURI = uri;
+ mFinalURI = uri;
+
+ if (chunkSize > 0) mChunkSize = chunkSize;
+ if (interval >= 0) mInterval = interval;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetURI(nsIURI** result) {
+ nsCOMPtr<nsIURI> uri = mURI;
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetFinalURI(nsIURI** result) {
+ nsCOMPtr<nsIURI> uri = mFinalURI;
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetDestination(nsIFile** result) {
+ if (!mDest) {
+ *result = nullptr;
+ return NS_OK;
+ }
+ // Return a clone of mDest so that callers may modify the resulting nsIFile
+ // without corrupting our internal object. This also works around the fact
+ // that some nsIFile impls may cache the result of stat'ing the filesystem.
+ return mDest->Clone(result);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetTotalSize(int64_t* result) {
+ *result = mTotalSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetCurrentSize(int64_t* result) {
+ *result = mCurrentSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Start(nsIRequestObserver* observer,
+ nsISupports* context) {
+ NS_ENSURE_ARG(observer);
+ NS_ENSURE_FALSE(mIsPending, NS_ERROR_IN_PROGRESS);
+
+ // Observe system shutdown so we can be sure to release any reference held
+ // between ourselves and the timer. We have the observer service hold a weak
+ // reference to us, so that we don't have to worry about calling
+ // RemoveObserver. XXX(darin): The timer code should do this for us.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+
+ nsresult rv = ReadCurrentSize();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = StartTimer(0);
+ if (NS_FAILED(rv)) return rv;
+
+ mObserver = observer;
+ mProgressSink = do_QueryInterface(observer); // ok if null
+
+ mIsPending = true;
+ return NS_OK;
+}
+
+// nsIRequestObserver
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnStartRequest(nsIRequest* request) {
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(request, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // Ensure that we are receiving a 206 response.
+ uint32_t code;
+ rv = http->GetResponseStatus(&code);
+ if (NS_FAILED(rv)) return rv;
+ if (code != 206) {
+ // We may already have the entire file downloaded, in which case
+ // our request for a range beyond the end of the file would have
+ // been met with an error response code.
+ if (code == 416 && mTotalSize == int64_t(-1)) {
+ mTotalSize = mCurrentSize;
+ // Return an error code here to suppress OnDataAvailable.
+ return NS_ERROR_DOWNLOAD_COMPLETE;
+ }
+ // The server may have decided to give us all of the data in one chunk. If
+ // we requested a partial range, then we don't want to download all of the
+ // data at once. So, we'll just try again, but if this keeps happening then
+ // we'll eventually give up.
+ if (code == 200) {
+ if (mInterval) {
+ mChannel = nullptr;
+ if (++mNonPartialCount > MAX_RETRY_COUNT) {
+ NS_WARNING("unable to fetch a byte range; giving up");
+ return NS_ERROR_FAILURE;
+ }
+ // Increase delay with each failure.
+ StartTimer(mInterval * mNonPartialCount);
+ return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
+ }
+ // Since we have been asked to download the rest of the file, we can deal
+ // with a 200 response. This may result in downloading the beginning of
+ // the file again, but that can't really be helped.
+ } else {
+ NS_WARNING("server response was unexpected");
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ // We got a partial response, so clear this counter in case the next chunk
+ // results in a 200 response.
+ mNonPartialCount = 0;
+
+ // confirm that the content-range response header is consistent with
+ // expectations on each 206. If it is not then drop this response and
+ // retry with no-cache set.
+ if (!mCacheBust) {
+ nsAutoCString buf;
+ int64_t startByte = 0;
+ bool confirmedOK = false;
+
+ rv = http->GetResponseHeader("Content-Range"_ns, buf);
+ if (NS_FAILED(rv)) {
+ return rv; // it isn't a useful 206 without a CONTENT-RANGE of some
+ }
+ // sort
+
+ // Content-Range: bytes 0-299999/25604694
+ int32_t p = buf.Find("bytes ");
+
+ // first look for the starting point of the content-range
+ // to make sure it is what we expect
+ if (p != -1) {
+ char* endptr = nullptr;
+ const char* s = buf.get() + p + 6;
+ while (*s && *s == ' ') s++;
+ startByte = strtol(s, &endptr, 10);
+
+ if (*s && endptr && (endptr != s) && (mCurrentSize == startByte)) {
+ // ok the starting point is confirmed. We still need to check the
+ // total size of the range for consistency if this isn't
+ // the first chunk
+ if (mTotalSize == int64_t(-1)) {
+ // first chunk
+ confirmedOK = true;
+ } else {
+ int32_t slash = buf.FindChar('/');
+ int64_t rangeSize = 0;
+ if (slash != kNotFound &&
+ (PR_sscanf(buf.get() + slash + 1, "%lld",
+ (int64_t*)&rangeSize) == 1) &&
+ rangeSize == mTotalSize) {
+ confirmedOK = true;
+ }
+ }
+ }
+ }
+
+ if (!confirmedOK) {
+ NS_WARNING("unexpected content-range");
+ mCacheBust = true;
+ mChannel = nullptr;
+ if (++mNonPartialCount > MAX_RETRY_COUNT) {
+ NS_WARNING("unable to fetch a byte range; giving up");
+ return NS_ERROR_FAILURE;
+ }
+ // Increase delay with each failure.
+ StartTimer(mInterval * mNonPartialCount);
+ return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
+ }
+ }
+ }
+
+ // Do special processing after the first response.
+ if (mTotalSize == int64_t(-1)) {
+ // Update knowledge of mFinalURI
+ rv = http->GetURI(getter_AddRefs(mFinalURI));
+ if (NS_FAILED(rv)) return rv;
+ Unused << http->GetResponseHeader("Etag"_ns, mPartialValidator);
+ if (StringBeginsWith(mPartialValidator, "W/"_ns)) {
+ mPartialValidator.Truncate(); // don't use weak validators
+ }
+ if (mPartialValidator.IsEmpty()) {
+ rv = http->GetResponseHeader("Last-Modified"_ns, mPartialValidator);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::OnStartRequest\n"
+ " empty validator\n"));
+ }
+ }
+
+ if (code == 206) {
+ // OK, read the Content-Range header to determine the total size of this
+ // download file.
+ nsAutoCString buf;
+ rv = http->GetResponseHeader("Content-Range"_ns, buf);
+ if (NS_FAILED(rv)) return rv;
+ int32_t slash = buf.FindChar('/');
+ if (slash == kNotFound) {
+ NS_WARNING("server returned invalid Content-Range header!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t*)&mTotalSize) !=
+ 1) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ rv = http->GetContentLength(&mTotalSize);
+ if (NS_FAILED(rv)) return rv;
+ // We need to know the total size of the thing we're trying to download.
+ if (mTotalSize == int64_t(-1)) {
+ NS_WARNING("server returned no content-length header!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // Need to truncate (or create, if it doesn't exist) the file since we
+ // are downloading the whole thing.
+ WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
+ mCurrentSize = 0;
+ }
+
+ // Notify observer that we are starting...
+ rv = CallOnStartRequest();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
+ int64_t diff = mTotalSize - mCurrentSize;
+ if (diff <= int64_t(0)) {
+ NS_WARNING("about to set a bogus chunk size; giving up");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (diff < int64_t(mChunkSize)) mChunkSize = uint32_t(diff);
+
+ mChunk = mozilla::MakeUniqueFallible<char[]>(mChunkSize);
+ if (!mChunk) rv = NS_ERROR_OUT_OF_MEMORY;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnStopRequest(nsIRequest* request, nsresult status) {
+ // Not a real error; just a trick to kill off the channel without our
+ // listener having to care.
+ if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL) return NS_OK;
+
+ // Not a real error; just a trick used to suppress OnDataAvailable calls.
+ if (status == NS_ERROR_DOWNLOAD_COMPLETE) status = NS_OK;
+
+ if (NS_SUCCEEDED(mStatus)) mStatus = status;
+
+ if (mChunk) {
+ if (NS_SUCCEEDED(mStatus)) mStatus = FlushChunk();
+
+ mChunk = nullptr; // deletes memory
+ mChunkLen = 0;
+ UpdateProgress();
+ }
+
+ mChannel = nullptr;
+
+ // Notify listener if we hit an error or finished
+ if (NS_FAILED(mStatus) || mCurrentSize == mTotalSize) {
+ CallOnStopRequest();
+ return NS_OK;
+ }
+
+ return StartTimer(mInterval); // Do next chunk
+}
+
+// nsIStreamListener
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* input, uint64_t offset,
+ uint32_t count) {
+ while (count) {
+ uint32_t space = mChunkSize - mChunkLen;
+ uint32_t n, len = std::min(space, count);
+
+ nsresult rv = input->Read(&mChunk[mChunkLen], len, &n);
+ if (NS_FAILED(rv)) return rv;
+ if (n != len) return NS_ERROR_UNEXPECTED;
+
+ count -= n;
+ mChunkLen += n;
+
+ if (mChunkLen == mChunkSize) {
+ rv = FlushChunk();
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ if (PR_Now() > mLastProgressUpdate + UPDATE_PROGRESS_INTERVAL) {
+ UpdateProgress();
+ }
+
+ return NS_OK;
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+nsIncrementalDownload::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ Cancel(NS_ERROR_ABORT);
+
+ // Since the app is shutting down, we need to go ahead and notify our
+ // observer here. Otherwise, we would notify them after XPCOM has been
+ // shutdown or not at all.
+ CallOnStopRequest();
+ }
+ return NS_OK;
+}
+
+// nsITimerCallback
+
+nsIncrementalDownload::TimerCallback::TimerCallback(
+ nsIncrementalDownload* aIncrementalDownload)
+ : mIncrementalDownload(aIncrementalDownload) {}
+
+NS_IMPL_ISUPPORTS(nsIncrementalDownload::TimerCallback, nsITimerCallback,
+ nsINamed)
+
+NS_IMETHODIMP
+nsIncrementalDownload::TimerCallback::Notify(nsITimer* aTimer) {
+ mIncrementalDownload->mTimer = nullptr;
+
+ nsresult rv = mIncrementalDownload->ProcessTimeout();
+ if (NS_FAILED(rv)) mIncrementalDownload->Cancel(rv);
+
+ return NS_OK;
+}
+
+// nsINamed
+
+NS_IMETHODIMP
+nsIncrementalDownload::TimerCallback::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsIncrementalDownload");
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetInterface(const nsIID& iid, void** result) {
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *result = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
+ if (ir) return ir->GetInterface(iid, result);
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+nsresult nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel* channel) {
+ NS_ENSURE_ARG(channel);
+
+ // We don't support encodings -- they make the Content-Length not equal
+ // to the actual size of the data.
+ return channel->SetRequestHeader("Accept-Encoding"_ns, ""_ns, false);
+}
+
+// nsIChannelEventSink
+
+NS_IMETHODIMP
+nsIncrementalDownload::AsyncOnChannelRedirect(
+ nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
+ nsIAsyncVerifyRedirectCallback* cb) {
+ // In response to a redirect, we need to propagate the Range header. See bug
+ // 311595. Any failure code returned from this function aborts the redirect.
+
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(oldChannel);
+ NS_ENSURE_STATE(http);
+
+ nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
+ NS_ENSURE_STATE(newHttpChannel);
+
+ constexpr auto rangeHdr = "Range"_ns;
+
+ nsresult rv = ClearRequestHeader(newHttpChannel);
+ if (NS_FAILED(rv)) return rv;
+
+ // If we didn't have a Range header, then we must be doing a full download.
+ nsAutoCString rangeVal;
+ Unused << http->GetRequestHeader(rangeHdr, rangeVal);
+ if (!rangeVal.IsEmpty()) {
+ rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // A redirection changes the validator
+ mPartialValidator.Truncate();
+
+ if (mCacheBust) {
+ rv = newHttpChannel->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns,
+ false);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
+ " failed to set request header: Cache-Control\n"));
+ }
+ rv = newHttpChannel->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsIncrementalDownload::AsyncOnChannelRedirect\n"
+ " failed to set request header: Pragma\n"));
+ }
+ }
+
+ // Prepare to receive callback
+ mRedirectCallback = cb;
+ mNewRedirectChannel = newChannel;
+
+ // Give the observer a chance to see this redirect notification.
+ nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver);
+ if (sink) {
+ rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
+ if (NS_FAILED(rv)) {
+ mRedirectCallback = nullptr;
+ mNewRedirectChannel = nullptr;
+ }
+ return rv;
+ }
+ (void)OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result) {
+ NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
+ NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
+
+ // Update mChannel, so we can Cancel the new channel.
+ if (NS_SUCCEEDED(result)) mChannel = mNewRedirectChannel;
+
+ mRedirectCallback->OnRedirectVerifyCallback(result);
+ mRedirectCallback = nullptr;
+ mNewRedirectChannel = nullptr;
+ return NS_OK;
+}
+
+extern nsresult net_NewIncrementalDownload(const nsIID& iid, void** result) {
+ RefPtr<nsIncrementalDownload> d = new nsIncrementalDownload();
+ return d->QueryInterface(iid, result);
+}
diff --git a/netwerk/base/nsIncrementalStreamLoader.cpp b/netwerk/base/nsIncrementalStreamLoader.cpp
new file mode 100644
index 0000000000..f4437b5269
--- /dev/null
+++ b/netwerk/base/nsIncrementalStreamLoader.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIncrementalStreamLoader.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsError.h"
+#include "mozilla/ProfilerLabels.h"
+
+#include <limits>
+
+nsIncrementalStreamLoader::nsIncrementalStreamLoader() = default;
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::Init(nsIIncrementalStreamLoaderObserver* observer) {
+ NS_ENSURE_ARG_POINTER(observer);
+ mObserver = observer;
+ return NS_OK;
+}
+
+nsresult nsIncrementalStreamLoader::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsIncrementalStreamLoader> it = new nsIncrementalStreamLoader();
+ return it->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS(nsIncrementalStreamLoader, nsIIncrementalStreamLoader,
+ nsIRequestObserver, nsIStreamListener,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::GetNumBytesRead(uint32_t* aNumBytes) {
+ *aNumBytes = mBytesRead;
+ return NS_OK;
+}
+
+/* readonly attribute nsIRequest request; */
+NS_IMETHODIMP
+nsIncrementalStreamLoader::GetRequest(nsIRequest** aRequest) {
+ *aRequest = do_AddRef(mRequest).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnStartRequest(nsIRequest* request) {
+ nsCOMPtr<nsIChannel> chan(do_QueryInterface(request));
+ if (chan) {
+ int64_t contentLength = -1;
+ chan->GetContentLength(&contentLength);
+ if (contentLength >= 0) {
+ // On 64bit platforms size of uint64_t coincides with the size of size_t,
+ // so we want to compare with the minimum from size_t and int64_t.
+ if (static_cast<uint64_t>(contentLength) >
+ std::min(std::numeric_limits<size_t>::max(),
+ static_cast<size_t>(std::numeric_limits<int64_t>::max()))) {
+ // Too big to fit into size_t, so let's bail.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // preallocate buffer
+ if (!mData.initCapacity(contentLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnStopRequest(nsIRequest* request,
+ nsresult aStatus) {
+ AUTO_PROFILER_LABEL("nsIncrementalStreamLoader::OnStopRequest", NETWORK);
+
+ if (mObserver) {
+ // provide nsIIncrementalStreamLoader::request during call to
+ // OnStreamComplete
+ mRequest = request;
+ size_t length = mData.length();
+ uint8_t* elems = mData.extractOrCopyRawBuffer();
+ nsresult rv =
+ mObserver->OnStreamComplete(this, mContext, aStatus, length, elems);
+ if (rv != NS_SUCCESS_ADOPTED_DATA) {
+ // The observer didn't take ownership of the extracted data buffer, so
+ // put it back into mData.
+ mData.replaceRawBuffer(elems, length);
+ }
+ // done.. cleanup
+ ReleaseData();
+ mRequest = nullptr;
+ mObserver = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult nsIncrementalStreamLoader::WriteSegmentFun(
+ nsIInputStream* inStr, void* closure, const char* fromSegment,
+ uint32_t toOffset, uint32_t count, uint32_t* writeCount) {
+ nsIncrementalStreamLoader* self = (nsIncrementalStreamLoader*)closure;
+
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(fromSegment);
+ uint32_t consumedCount = 0;
+ nsresult rv;
+ if (self->mData.empty()) {
+ // Shortcut when observer wants to keep the listener's buffer empty.
+ rv = self->mObserver->OnIncrementalData(self, self->mContext, count, data,
+ &consumedCount);
+
+ if (rv != NS_OK) {
+ return rv;
+ }
+
+ if (consumedCount > count) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (consumedCount < count) {
+ if (!self->mData.append(fromSegment + consumedCount,
+ count - consumedCount)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ } else {
+ // We have some non-consumed data from previous OnIncrementalData call,
+ // appending new data and reporting combined data.
+ if (!self->mData.append(fromSegment, count)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ size_t length = self->mData.length();
+ uint32_t reportCount = length > UINT32_MAX ? UINT32_MAX : (uint32_t)length;
+ uint8_t* elems = self->mData.extractOrCopyRawBuffer();
+
+ rv = self->mObserver->OnIncrementalData(self, self->mContext, reportCount,
+ elems, &consumedCount);
+
+ // We still own elems, freeing its memory when exiting scope.
+ if (rv != NS_OK) {
+ free(elems);
+ return rv;
+ }
+
+ if (consumedCount > reportCount) {
+ free(elems);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (consumedCount == length) {
+ free(elems); // good case -- fully consumed data
+ } else {
+ // Adopting elems back (at least its portion).
+ self->mData.replaceRawBuffer(elems, length);
+ if (consumedCount > 0) {
+ self->mData.erase(self->mData.begin() + consumedCount);
+ }
+ }
+ }
+
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* inStr,
+ uint64_t sourceOffset,
+ uint32_t count) {
+ if (mObserver) {
+ // provide nsIIncrementalStreamLoader::request during call to
+ // OnStreamComplete
+ mRequest = request;
+ }
+ uint32_t countRead;
+ nsresult rv = inStr->ReadSegments(WriteSegmentFun, this, count, &countRead);
+ mRequest = nullptr;
+ NS_ENSURE_SUCCESS(rv, rv);
+ mBytesRead += countRead;
+ return rv;
+}
+
+void nsIncrementalStreamLoader::ReleaseData() { mData.clearAndFree(); }
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::CheckListenerChain() { return NS_OK; }
+
+NS_IMETHODIMP
+nsIncrementalStreamLoader::OnDataFinished(nsresult aStatus) { return NS_OK; }
diff --git a/netwerk/base/nsIncrementalStreamLoader.h b/netwerk/base/nsIncrementalStreamLoader.h
new file mode 100644
index 0000000000..97d4277c28
--- /dev/null
+++ b/netwerk/base/nsIncrementalStreamLoader.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIncrementalStreamLoader_h__
+#define nsIncrementalStreamLoader_h__
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Vector.h"
+
+class nsIRequest;
+
+class nsIncrementalStreamLoader final : public nsIIncrementalStreamLoader {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINCREMENTALSTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsIncrementalStreamLoader();
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ protected:
+ ~nsIncrementalStreamLoader() = default;
+
+ static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*, uint32_t,
+ uint32_t, uint32_t*);
+
+ // Utility method to free mData, if present, and update other state to
+ // reflect that no data has been allocated.
+ void ReleaseData();
+
+ nsCOMPtr<nsIIncrementalStreamLoaderObserver> mObserver;
+ nsCOMPtr<nsISupports> mContext; // the observer's context
+ nsCOMPtr<nsIRequest> mRequest;
+
+ // Buffer to accumulate incoming data. We preallocate if contentSize is
+ // available.
+ mozilla::Vector<uint8_t, 0> mData;
+
+ // Number of bytes read, which may not match the number of bytes in mData at
+ // all, as we incrementally remove from there.
+ mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed> mBytesRead;
+};
+
+#endif // nsIncrementalStreamLoader_h__
diff --git a/netwerk/base/nsInputStreamChannel.cpp b/netwerk/base/nsInputStreamChannel.cpp
new file mode 100644
index 0000000000..e00bc85ad5
--- /dev/null
+++ b/netwerk/base/nsInputStreamChannel.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsInputStreamChannel.h"
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel
+
+namespace mozilla {
+namespace net {
+
+nsresult nsInputStreamChannel::OpenContentStream(bool async,
+ nsIInputStream** result,
+ nsIChannel** channel) {
+ NS_ENSURE_TRUE(mContentStream, NS_ERROR_NOT_INITIALIZED);
+
+ // If content length is unknown, then we must guess. In this case, we assume
+ // the stream can tell us. If the stream is a pipe, then this will not work.
+
+ if (mContentLength < 0) {
+ uint64_t avail;
+ nsresult rv = mContentStream->Available(&avail);
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ // This just means there's nothing in the stream
+ avail = 0;
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mContentLength = avail;
+ }
+
+ EnableSynthesizedProgressEvents(true);
+
+ *result = do_AddRef(mContentStream).take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsInputStreamChannel, nsBaseChannel,
+ nsIInputStreamChannel)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamChannel::nsIInputStreamChannel
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetURI(nsIURI* uri) {
+ NS_ENSURE_TRUE(!URI(), NS_ERROR_ALREADY_INITIALIZED);
+ nsBaseChannel::SetURI(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetContentStream(nsIInputStream** stream) {
+ *stream = do_AddRef(mContentStream).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetContentStream(nsIInputStream* stream) {
+ NS_ENSURE_TRUE(!mContentStream, NS_ERROR_ALREADY_INITIALIZED);
+ mContentStream = stream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetSrcdocData(nsAString& aSrcdocData) {
+ aSrcdocData = mSrcdocData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetSrcdocData(const nsAString& aSrcdocData) {
+ mSrcdocData = aSrcdocData;
+ mIsSrcdocChannel = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetIsSrcdocChannel(bool* aIsSrcdocChannel) {
+ *aIsSrcdocChannel = mIsSrcdocChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetBaseURI(nsIURI** aBaseURI) {
+ *aBaseURI = do_AddRef(mBaseURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetBaseURI(nsIURI* aBaseURI) {
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsInputStreamChannel.h b/netwerk/base/nsInputStreamChannel.h
new file mode 100644
index 0000000000..c8c926020b
--- /dev/null
+++ b/netwerk/base/nsInputStreamChannel.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInputStreamChannel_h__
+#define nsInputStreamChannel_h__
+
+#include "nsBaseChannel.h"
+#include "nsIInputStreamChannel.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsInputStreamChannel : public nsBaseChannel,
+ public nsIInputStreamChannel {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAMCHANNEL
+
+ nsInputStreamChannel() = default;
+
+ protected:
+ virtual ~nsInputStreamChannel() = default;
+
+ virtual nsresult OpenContentStream(bool async, nsIInputStream** result,
+ nsIChannel** channel) override;
+
+ virtual void OnChannelDone() override { mContentStream = nullptr; }
+
+ private:
+ nsCOMPtr<nsIInputStream> mContentStream;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsString mSrcdocData;
+ bool mIsSrcdocChannel{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsInputStreamChannel_h__
diff --git a/netwerk/base/nsInputStreamPump.cpp b/netwerk/base/nsInputStreamPump.cpp
new file mode 100644
index 0000000000..dd74aed23a
--- /dev/null
+++ b/netwerk/base/nsInputStreamPump.cpp
@@ -0,0 +1,795 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sts=2 sw=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsInputStreamPump.h"
+#include "nsIStreamTransportService.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Logging.h"
+#include "mozilla/NonBlockingAsyncInputStream.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/SlicedInputStream.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsIStreamListener.h"
+#include "nsILoadGroup.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include <algorithm>
+
+//
+// MOZ_LOG=nsStreamPump:5
+//
+static mozilla::LazyLogModule gStreamPumpLog("nsStreamPump");
+#undef LOG
+#define LOG(args) MOZ_LOG(gStreamPumpLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump methods
+//-----------------------------------------------------------------------------
+
+nsInputStreamPump::nsInputStreamPump() : mOffMainThread(!NS_IsMainThread()) {}
+
+nsresult nsInputStreamPump::Create(nsInputStreamPump** result,
+ nsIInputStream* stream, uint32_t segsize,
+ uint32_t segcount, bool closeWhenDone,
+ nsISerialEventTarget* mainThreadTarget) {
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ RefPtr<nsInputStreamPump> pump = new nsInputStreamPump();
+ if (pump) {
+ rv = pump->Init(stream, segsize, segcount, closeWhenDone, mainThreadTarget);
+ if (NS_SUCCEEDED(rv)) {
+ pump.forget(result);
+ }
+ }
+ return rv;
+}
+
+struct PeekData {
+ PeekData(nsInputStreamPump::PeekSegmentFun fun, void* closure)
+ : mFunc(fun), mClosure(closure) {}
+
+ nsInputStreamPump::PeekSegmentFun mFunc;
+ void* mClosure;
+};
+
+static nsresult CallPeekFunc(nsIInputStream* aInStream, void* aClosure,
+ const char* aFromSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount) {
+ NS_ASSERTION(aToOffset == 0, "Called more than once?");
+ NS_ASSERTION(aCount > 0, "Called without data?");
+
+ PeekData* data = static_cast<PeekData*>(aClosure);
+ data->mFunc(data->mClosure, reinterpret_cast<const uint8_t*>(aFromSegment),
+ aCount);
+ return NS_BINDING_ABORTED;
+}
+
+nsresult nsInputStreamPump::PeekStream(PeekSegmentFun callback, void* closure) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mAsyncStream, "PeekStream called without stream");
+
+ nsresult rv = CreateBufferedStreamIfNeeded();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // See if the pipe is closed by checking the return of Available.
+ uint64_t dummy64;
+ rv = mAsyncStream->Available(&dummy64);
+ if (NS_FAILED(rv)) return rv;
+ uint32_t dummy = (uint32_t)std::min(dummy64, (uint64_t)UINT32_MAX);
+
+ PeekData data(callback, closure);
+ return mAsyncStream->ReadSegments(
+ CallPeekFunc, &data, mozilla::net::nsIOService::gDefaultSegmentSize,
+ &dummy);
+}
+
+nsresult nsInputStreamPump::EnsureWaiting() {
+ mMutex.AssertCurrentThreadIn();
+
+ // no need to worry about multiple threads... an input stream pump lives
+ // on only one thread at a time.
+ MOZ_ASSERT(mAsyncStream);
+ if (!mWaitingForInputStreamReady && !mProcessingCallbacks) {
+ // Ensure OnStateStop is called on the main thread only when this pump is
+ // created on main thread.
+ if (mState == STATE_STOP && !mOffMainThread) {
+ nsCOMPtr<nsISerialEventTarget> mainThread =
+ mLabeledMainThreadTarget
+ ? mLabeledMainThreadTarget
+ : do_AddRef(mozilla::GetMainThreadSerialEventTarget());
+ if (mTargetThread != mainThread) {
+ mTargetThread = mainThread;
+ }
+ }
+ MOZ_ASSERT(mTargetThread);
+ nsresult rv = mAsyncStream->AsyncWait(this, 0, 0, mTargetThread);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("AsyncWait failed");
+ return rv;
+ }
+ // Any retargeting during STATE_START or START_TRANSFER is complete
+ // after the call to AsyncWait; next callback will be on mTargetThread.
+ mRetargeting = false;
+ mWaitingForInputStreamReady = true;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsISupports
+//-----------------------------------------------------------------------------
+
+// although this class can only be accessed from one thread at a time, we do
+// allow its ownership to move from thread to thread, assuming the consumer
+// understands the limitations of this.
+NS_IMPL_ADDREF(nsInputStreamPump)
+NS_IMPL_RELEASE(nsInputStreamPump)
+NS_INTERFACE_MAP_BEGIN(nsInputStreamPump)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamPump)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsInputStreamPump)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamPump)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::GetName(nsACString& result) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::IsPending(bool* result) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ *result = (mState != STATE_IDLE && mState != STATE_DEAD);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetStatus(nsresult* status) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsInputStreamPump::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsInputStreamPump::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsInputStreamPump::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Cancel(nsresult status) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ AssertOnThread();
+
+ LOG(("nsInputStreamPump::Cancel [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(status)));
+
+ if (NS_FAILED(mStatus)) {
+ LOG((" already canceled\n"));
+ return NS_OK;
+ }
+
+ NS_ASSERTION(NS_FAILED(status), "cancel with non-failure status code");
+ mStatus = status;
+
+ // close input stream
+ if (mAsyncStream) {
+ // If mSuspendCount != 0, EnsureWaiting will be called by Resume().
+ // Note that while suspended, OnInputStreamReady will
+ // not do anything, and also note that calling asyncWait
+ // on a closed stream works and will dispatch an event immediately.
+
+ nsCOMPtr<nsIEventTarget> currentTarget = NS_GetCurrentThread();
+ if (mTargetThread && currentTarget != mTargetThread) {
+ nsresult rv = mTargetThread->Dispatch(NS_NewRunnableFunction(
+ "nsInputStreamPump::Cancel", [self = RefPtr{this}, status] {
+ RecursiveMutexAutoLock lock(self->mMutex);
+ if (!self->mAsyncStream) {
+ return;
+ }
+ self->mAsyncStream->CloseWithStatus(status);
+ if (self->mSuspendCount == 0) {
+ self->EnsureWaiting();
+ }
+ }));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mAsyncStream->CloseWithStatus(status);
+ if (mSuspendCount == 0) {
+ EnsureWaiting();
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Suspend() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ LOG(("nsInputStreamPump::Suspend [this=%p]\n", this));
+ NS_ENSURE_TRUE(mState != STATE_IDLE && mState != STATE_DEAD,
+ NS_ERROR_UNEXPECTED);
+ ++mSuspendCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Resume() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ LOG(("nsInputStreamPump::Resume [this=%p]\n", this));
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(mState != STATE_IDLE && mState != STATE_DEAD,
+ NS_ERROR_UNEXPECTED);
+
+ // There is a brief in-between state when we null out mAsyncStream in
+ // OnStateStop() before calling OnStopRequest, and only afterwards set
+ // STATE_DEAD, which we need to handle gracefully.
+ if (--mSuspendCount == 0 && mAsyncStream) {
+ EnsureWaiting();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ *aLoadGroup = do_AddRef(mLoadGroup).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIInputStreamPump implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::Init(nsIInputStream* stream, uint32_t segsize,
+ uint32_t segcount, bool closeWhenDone,
+ nsISerialEventTarget* mainThreadTarget) {
+ // probably we can't be multithread-accessed yet
+ RecursiveMutexAutoLock lock(mMutex);
+ NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS);
+
+ mStream = stream;
+ mSegSize = segsize;
+ mSegCount = segcount;
+ mCloseWhenDone = closeWhenDone;
+ mLabeledMainThreadTarget = mainThreadTarget;
+ if (mOffMainThread && mLabeledMainThreadTarget) {
+ MOZ_ASSERT(
+ false,
+ "Init stream pump off main thread with a main thread event target.");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::AsyncRead(nsIStreamListener* listener) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ // This ensures only one thread can interact with a pump at a time
+ NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_ARG_POINTER(listener);
+ MOZ_ASSERT(NS_IsMainThread() || mOffMainThread,
+ "nsInputStreamPump should be read from the "
+ "main thread only.");
+
+ nsresult rv = NS_MakeAsyncNonBlockingInputStream(
+ mStream.forget(), getter_AddRefs(mAsyncStream), mCloseWhenDone, mSegSize,
+ mSegCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(mAsyncStream);
+
+ // mStreamOffset now holds the number of bytes currently read.
+ mStreamOffset = 0;
+
+ // grab event queue (we must do this here by contract, since all notifications
+ // must go to the thread which called AsyncRead)
+ if (NS_IsMainThread() && mLabeledMainThreadTarget) {
+ mTargetThread = mLabeledMainThreadTarget;
+ } else {
+ mTargetThread = mozilla::GetCurrentSerialEventTarget();
+ }
+ NS_ENSURE_STATE(mTargetThread);
+
+ rv = EnsureWaiting();
+ if (NS_FAILED(rv)) return rv;
+
+ if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
+
+ mState = STATE_START;
+ mListener = listener;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsIInputStreamCallback implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream* stream) {
+ LOG(("nsInputStreamPump::OnInputStreamReady [this=%p]\n", this));
+
+ AUTO_PROFILER_LABEL("nsInputStreamPump::OnInputStreamReady", NETWORK);
+
+ // this function has been called from a PLEvent, so we can safely call
+ // any listener or progress sink methods directly from here.
+
+ for (;;) {
+ // There should only be one iteration of this loop happening at a time.
+ // To prevent AsyncWait() (called during callbacks or on other threads)
+ // from creating a parallel OnInputStreamReady(), we use:
+ // -- a mutex; and
+ // -- a boolean mProcessingCallbacks to detect parallel loops
+ // when exiting the mutex for callbacks.
+ RecursiveMutexAutoLock lock(mMutex);
+
+ // Prevent parallel execution during callbacks, while out of mutex.
+ if (mProcessingCallbacks) {
+ MOZ_ASSERT(!mProcessingCallbacks);
+ break;
+ }
+ mProcessingCallbacks = true;
+ if (mSuspendCount || mState == STATE_IDLE || mState == STATE_DEAD) {
+ mWaitingForInputStreamReady = false;
+ mProcessingCallbacks = false;
+ break;
+ }
+
+ uint32_t nextState;
+ switch (mState) {
+ case STATE_START:
+ nextState = OnStateStart();
+ break;
+ case STATE_TRANSFER:
+ nextState = OnStateTransfer();
+ break;
+ case STATE_STOP:
+ mRetargeting = false;
+ nextState = OnStateStop();
+ break;
+ default:
+ nextState = 0;
+ MOZ_ASSERT_UNREACHABLE("Unknown enum value.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool stillTransferring =
+ (mState == STATE_TRANSFER && nextState == STATE_TRANSFER);
+ if (stillTransferring) {
+ NS_ASSERTION(NS_SUCCEEDED(mStatus),
+ "Should not have failed status for ongoing transfer");
+ } else {
+ NS_ASSERTION(mState != nextState,
+ "Only OnStateTransfer can be called more than once.");
+ }
+ if (mRetargeting) {
+ NS_ASSERTION(mState != STATE_STOP,
+ "Retargeting should not happen during OnStateStop.");
+ }
+
+ // Set mRetargeting so EnsureWaiting will be called. It ensures that
+ // OnStateStop is called on the main thread.
+ if (nextState == STATE_STOP && !NS_IsMainThread() && !mOffMainThread) {
+ mRetargeting = true;
+ }
+
+ // Unset mProcessingCallbacks here (while we have lock) so our own call to
+ // EnsureWaiting isn't blocked by it.
+ mProcessingCallbacks = false;
+
+ // We must break the loop if suspended during one of the previous
+ // operation.
+ if (mSuspendCount) {
+ mState = nextState;
+ mWaitingForInputStreamReady = false;
+ break;
+ }
+
+ // Wait asynchronously if there is still data to transfer, or we're
+ // switching event delivery to another thread.
+ if (stillTransferring || mRetargeting) {
+ mState = nextState;
+ mWaitingForInputStreamReady = false;
+ nsresult rv = EnsureWaiting();
+ if (NS_SUCCEEDED(rv)) break;
+
+ // Failure to start asynchronous wait: stop transfer.
+ // Do not set mStatus if it was previously set to report a failure.
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = rv;
+ }
+ nextState = STATE_STOP;
+ }
+
+ mState = nextState;
+ }
+ return NS_OK;
+}
+
+uint32_t nsInputStreamPump::OnStateStart() {
+ mMutex.AssertCurrentThreadIn();
+
+ AUTO_PROFILER_LABEL("nsInputStreamPump::OnStateStart", NETWORK);
+
+ LOG((" OnStateStart [this=%p]\n", this));
+
+ nsresult rv;
+
+ // need to check the reason why the stream is ready. this is required
+ // so our listener can check our status from OnStartRequest.
+ // XXX async streams should have a GetStatus method!
+ if (NS_SUCCEEDED(mStatus)) {
+ uint64_t avail;
+ rv = mAsyncStream->Available(&avail);
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_CLOSED) mStatus = rv;
+ }
+
+ {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ // We're on the writing thread
+ AssertOnThread();
+
+ // Note: Must exit mutex for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ RecursiveMutexAutoUnlock unlock(mMutex);
+ rv = listener->OnStartRequest(this);
+ }
+
+ // an error returned from OnStartRequest should cause us to abort; however,
+ // we must not stomp on mStatus if already canceled.
+ if (NS_FAILED(rv) && NS_SUCCEEDED(mStatus)) mStatus = rv;
+
+ return NS_SUCCEEDED(mStatus) ? STATE_TRANSFER : STATE_STOP;
+}
+
+uint32_t nsInputStreamPump::OnStateTransfer() {
+ mMutex.AssertCurrentThreadIn();
+
+ AUTO_PROFILER_LABEL("nsInputStreamPump::OnStateTransfer", NETWORK);
+
+ LOG((" OnStateTransfer [this=%p]\n", this));
+
+ // if canceled, go directly to STATE_STOP...
+ if (NS_FAILED(mStatus)) return STATE_STOP;
+
+ nsresult rv = CreateBufferedStreamIfNeeded();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return STATE_STOP;
+ }
+
+ uint64_t avail;
+ rv = mAsyncStream->Available(&avail);
+ LOG((" Available returned [stream=%p rv=%" PRIx32 " avail=%" PRIu64 "]\n",
+ mAsyncStream.get(), static_cast<uint32_t>(rv), avail));
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ rv = NS_OK;
+ avail = 0;
+ } else if (NS_SUCCEEDED(rv) && avail) {
+ // we used to limit avail to 16K - we were afraid some ODA handlers
+ // might assume they wouldn't get more than 16K at once
+ // we're removing that limit since it speeds up local file access.
+ // Now there's an implicit 64K limit of 4 16K segments
+ // NOTE: ok, so the story is as follows. OnDataAvailable impls
+ // are by contract supposed to consume exactly |avail| bytes.
+ // however, many do not... mailnews... stream converters...
+ // cough, cough. the input stream pump is fairly tolerant
+ // in this regard; however, if an ODA does not consume any
+ // data from the stream, then we could potentially end up in
+ // an infinite loop. we do our best here to try to catch
+ // such an error. (see bug 189672)
+
+ // in most cases this QI will succeed (mAsyncStream is almost always
+ // a nsPipeInputStream, which implements nsITellableStream::Tell).
+ int64_t offsetBefore;
+ nsCOMPtr<nsITellableStream> tellable = do_QueryInterface(mAsyncStream);
+ if (tellable && NS_FAILED(tellable->Tell(&offsetBefore))) {
+ MOZ_ASSERT_UNREACHABLE("Tell failed on readable stream");
+ offsetBefore = 0;
+ }
+
+ uint32_t odaAvail = avail > UINT32_MAX ? UINT32_MAX : uint32_t(avail);
+
+ LOG((" calling OnDataAvailable [offset=%" PRIu64 " count=%" PRIu64
+ "(%u)]\n",
+ mStreamOffset, avail, odaAvail));
+
+ {
+ // We may be called on non-MainThread even if mOffMainThread is
+ // false, due to RetargetDeliveryTo(), so don't use AssertOnThread()
+ if (mTargetThread) {
+ MOZ_ASSERT(mTargetThread->IsOnCurrentThread());
+ } else {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ // Note: Must exit mutex for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ RecursiveMutexAutoUnlock unlock(mMutex);
+ // We're on the writing thread for mListener and mAsyncStream.
+ // mStreamOffset is only touched in OnStateTransfer, and AsyncRead
+ // shouldn't be called during OnDataAvailable()
+
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ rv = listener->OnDataAvailable(this, mAsyncStream, mStreamOffset,
+ odaAvail);
+ MOZ_POP_THREAD_SAFETY
+ }
+
+ // don't enter this code if ODA failed or called Cancel
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(mStatus)) {
+ // test to see if this ODA failed to consume data
+ if (tellable) {
+ // NOTE: if Tell fails, which can happen if the stream is
+ // now closed, then we assume that everything was read.
+ int64_t offsetAfter;
+ if (NS_FAILED(tellable->Tell(&offsetAfter))) {
+ offsetAfter = offsetBefore + odaAvail;
+ }
+ if (offsetAfter > offsetBefore) {
+ mStreamOffset += (offsetAfter - offsetBefore);
+ } else if (mSuspendCount == 0) {
+ //
+ // possible infinite loop if we continue pumping data!
+ //
+ // NOTE: although not allowed by nsIStreamListener, we
+ // will allow the ODA impl to Suspend the pump. IMAP
+ // does this :-(
+ //
+ NS_ERROR("OnDataAvailable implementation consumed no data");
+ mStatus = NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ mStreamOffset += odaAvail; // assume ODA behaved well
+ }
+ }
+ }
+
+ // an error returned from Available or OnDataAvailable should cause us to
+ // abort; however, we must not stop on mStatus if already canceled.
+
+ if (NS_SUCCEEDED(mStatus)) {
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ } else if (avail) {
+ // if stream is now closed, advance to STATE_STOP right away.
+ // Available may return 0 bytes available at the moment; that
+ // would not mean that we are done.
+ // XXX async streams should have a GetStatus method!
+ rv = mAsyncStream->Available(&avail);
+ if (NS_SUCCEEDED(rv)) return STATE_TRANSFER;
+ if (rv != NS_BASE_STREAM_CLOSED) mStatus = rv;
+ }
+ }
+ return STATE_STOP;
+}
+
+nsresult nsInputStreamPump::CallOnStateStop() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(NS_IsMainThread(),
+ "CallOnStateStop should only be called on the main thread.");
+
+ mState = OnStateStop();
+ return NS_OK;
+}
+
+uint32_t nsInputStreamPump::OnStateStop() {
+ mMutex.AssertCurrentThreadIn();
+
+ if (!NS_IsMainThread() && !mOffMainThread) {
+ // This method can be called on a different thread if nsInputStreamPump
+ // is used off the main-thread.
+ if (NS_SUCCEEDED(mStatus) && mListener &&
+ mozilla::StaticPrefs::network_send_OnDataFinished_nsInputStreamPump()) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener);
+ if (retargetableListener) {
+ retargetableListener->OnDataFinished(mStatus);
+ }
+ }
+ nsresult rv = mLabeledMainThreadTarget->Dispatch(
+ mozilla::NewRunnableMethod("nsInputStreamPump::CallOnStateStop", this,
+ &nsInputStreamPump::CallOnStateStop));
+ NS_ENSURE_SUCCESS(rv, STATE_DEAD);
+ return STATE_DEAD;
+ }
+
+ AUTO_PROFILER_LABEL("nsInputStreamPump::OnStateStop", NETWORK);
+
+ LOG((" OnStateStop [this=%p status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(mStatus)));
+
+ // if an error occurred, we must be sure to pass the error onto the async
+ // stream. in some cases, this is redundant, but since close is idempotent,
+ // this is OK. otherwise, be sure to honor the "close-when-done" option.
+
+ if (!mAsyncStream || !mListener) {
+ MOZ_ASSERT(mAsyncStream, "null mAsyncStream: OnStateStop called twice?");
+ MOZ_ASSERT(mListener, "null mListener: OnStateStop called twice?");
+ return STATE_DEAD;
+ }
+
+ if (NS_FAILED(mStatus)) {
+ mAsyncStream->CloseWithStatus(mStatus);
+ } else if (mCloseWhenDone) {
+ mAsyncStream->Close();
+ }
+
+ mAsyncStream = nullptr;
+ mIsPending = false;
+ {
+ // We're on the writing thread.
+ // We believe that mStatus can't be changed on us here.
+ AssertOnThread();
+
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ nsresult status = mStatus;
+ // Note: Must exit mutex for call to OnStartRequest to avoid
+ // deadlocks when calls to RetargetDeliveryTo for multiple
+ // nsInputStreamPumps are needed (e.g. nsHttpChannel).
+ RecursiveMutexAutoUnlock unlock(mMutex);
+
+ listener->OnStopRequest(this, status);
+ }
+ mTargetThread = nullptr;
+ mListener = nullptr;
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return STATE_DEAD;
+}
+
+nsresult nsInputStreamPump::CreateBufferedStreamIfNeeded() {
+ if (mAsyncStreamIsBuffered) {
+ return NS_OK;
+ }
+
+ // ReadSegments is not available for any nsIAsyncInputStream. In order to use
+ // it, we wrap a nsIBufferedInputStream around it, if needed.
+
+ if (NS_InputStreamIsBuffered(mAsyncStream)) {
+ mAsyncStreamIsBuffered = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(stream),
+ mAsyncStream.forget(), 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // A buffered inputStream must implement nsIAsyncInputStream.
+ mAsyncStream = do_QueryInterface(stream);
+ MOZ_DIAGNOSTIC_ASSERT(mAsyncStream);
+ mAsyncStreamIsBuffered = true;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsInputStreamPump::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ NS_ENSURE_ARG(aNewTarget);
+ NS_ENSURE_TRUE(mState == STATE_START || mState == STATE_TRANSFER,
+ NS_ERROR_UNEXPECTED);
+
+ // If canceled, do not retarget. Return with canceled status.
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ if (aNewTarget == mTargetThread) {
+ NS_WARNING("Retargeting delivery to same thread");
+ return NS_OK;
+ }
+
+ if (mOffMainThread) {
+ // Don't support retargeting if this pump is already used off the main
+ // thread.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Ensure that |mListener| and any subsequent listeners can be retargeted
+ // to another thread.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (NS_SUCCEEDED(rv) && retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ if (NS_SUCCEEDED(rv)) {
+ mTargetThread = aNewTarget;
+ mRetargeting = true;
+ }
+ }
+ LOG(
+ ("nsInputStreamPump::RetargetDeliveryTo [this=%p aNewTarget=%p] "
+ "%s listener [%p] rv[%" PRIx32 "]",
+ this, aNewTarget, (mTargetThread == aNewTarget ? "success" : "failure"),
+ (nsIStreamListener*)mListener, static_cast<uint32_t>(rv)));
+ return rv;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetDeliveryTarget(nsISerialEventTarget** aNewTarget) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ nsCOMPtr<nsISerialEventTarget> target = mTargetThread;
+ target.forget(aNewTarget);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsInputStreamPump.h b/netwerk/base/nsInputStreamPump.h
new file mode 100644
index 0000000000..21e75c0276
--- /dev/null
+++ b/netwerk/base/nsInputStreamPump.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInputStreamPump_h__
+#define nsInputStreamPump_h__
+
+#include "nsIInputStreamPump.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RecursiveMutex.h"
+
+#ifdef DEBUG
+# include "MainThreadUtils.h"
+# include "nsISerialEventTarget.h"
+#endif
+
+class nsIInputStream;
+class nsILoadGroup;
+class nsIStreamListener;
+
+#define NS_INPUT_STREAM_PUMP_IID \
+ { \
+ 0x42f1cc9b, 0xdf5f, 0x4c9b, { \
+ 0xbd, 0x71, 0x8d, 0x4a, 0xe2, 0x27, 0xc1, 0x8a \
+ } \
+ }
+
+class nsInputStreamPump final : public nsIInputStreamPump,
+ public nsIInputStreamCallback,
+ public nsIThreadRetargetableRequest {
+ ~nsInputStreamPump() = default;
+
+ public:
+ using RecursiveMutexAutoLock = mozilla::RecursiveMutexAutoLock;
+ using RecursiveMutexAutoUnlock = mozilla::RecursiveMutexAutoUnlock;
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSIINPUTSTREAMPUMP
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_STREAM_PUMP_IID)
+
+ nsInputStreamPump();
+
+ static nsresult Create(nsInputStreamPump** result, nsIInputStream* stream,
+ uint32_t segsize = 0, uint32_t segcount = 0,
+ bool closeWhenDone = false,
+ nsISerialEventTarget* mainThreadTarget = nullptr);
+
+ using PeekSegmentFun = void (*)(void*, const uint8_t*, uint32_t);
+ /**
+ * Peek into the first chunk of data that's in the stream. Note that this
+ * method will not call the callback when there is no data in the stream.
+ * The callback will be called at most once.
+ *
+ * The data from the stream will not be consumed, i.e. the pump's listener
+ * can still read all the data
+ *
+ * Do not call before asyncRead. Do not call after onStopRequest.
+ */
+ nsresult PeekStream(PeekSegmentFun callback, void* closure);
+
+ /**
+ * Dispatched (to the main thread) by OnStateStop if it's called off main
+ * thread. Updates mState based on return value of OnStateStop.
+ */
+ nsresult CallOnStateStop();
+
+ protected:
+ enum { STATE_IDLE, STATE_START, STATE_TRANSFER, STATE_STOP, STATE_DEAD };
+
+ nsresult EnsureWaiting();
+ uint32_t OnStateStart();
+ uint32_t OnStateTransfer();
+ uint32_t OnStateStop();
+ nsresult CreateBufferedStreamIfNeeded() MOZ_REQUIRES(mMutex);
+
+ // This should optimize away in non-DEBUG builds
+ MOZ_ALWAYS_INLINE void AssertOnThread() const MOZ_REQUIRES(mMutex) {
+ if (mOffMainThread) {
+ MOZ_ASSERT(mTargetThread->IsOnCurrentThread());
+ } else {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+ }
+
+ uint32_t mState MOZ_GUARDED_BY(mMutex){STATE_IDLE};
+ nsCOMPtr<nsILoadGroup> mLoadGroup MOZ_GUARDED_BY(mMutex);
+ // mListener is written on a single thread (either MainThread or an
+ // off-MainThread thread), read from that thread and perhaps others (in
+ // RetargetDeliveryTo)
+ nsCOMPtr<nsIStreamListener> mListener MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsISerialEventTarget> mTargetThread MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsISerialEventTarget> mLabeledMainThreadTarget
+ MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIInputStream> mStream MOZ_GUARDED_BY(mMutex);
+ // mAsyncStream is written on a single thread (either MainThread or an
+ // off-MainThread thread), and lives from AsyncRead() to OnStateStop().
+ nsCOMPtr<nsIAsyncInputStream> mAsyncStream MOZ_GUARDED_BY(mMutex);
+ uint64_t mStreamOffset MOZ_GUARDED_BY(mMutex){0};
+ uint64_t mStreamLength MOZ_GUARDED_BY(mMutex){0};
+ uint32_t mSegSize MOZ_GUARDED_BY(mMutex){0};
+ uint32_t mSegCount MOZ_GUARDED_BY(mMutex){0};
+ nsresult mStatus MOZ_GUARDED_BY(mMutex){NS_OK};
+ uint32_t mSuspendCount MOZ_GUARDED_BY(mMutex){0};
+ uint32_t mLoadFlags MOZ_GUARDED_BY(mMutex){LOAD_NORMAL};
+ bool mIsPending MOZ_GUARDED_BY(mMutex){false};
+ // True while in OnInputStreamReady, calling OnStateStart, OnStateTransfer
+ // and OnStateStop. Used to prevent calls to AsyncWait during callbacks.
+ bool mProcessingCallbacks MOZ_GUARDED_BY(mMutex){false};
+ // True if waiting on the "input stream ready" callback.
+ bool mWaitingForInputStreamReady MOZ_GUARDED_BY(mMutex){false};
+ bool mCloseWhenDone MOZ_GUARDED_BY(mMutex){false};
+ bool mRetargeting MOZ_GUARDED_BY(mMutex){false};
+ bool mAsyncStreamIsBuffered MOZ_GUARDED_BY(mMutex){false};
+ // Indicate whether nsInputStreamPump is used completely off main thread.
+ // If true, OnStateStop() is executed off main thread. Set at creation.
+ const bool mOffMainThread;
+ // Protects state/member var accesses across multiple threads.
+ mozilla::RecursiveMutex mMutex{"nsInputStreamPump"};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsInputStreamPump, NS_INPUT_STREAM_PUMP_IID)
+
+#endif // !nsInputStreamChannel_h__
diff --git a/netwerk/base/nsLoadGroup.cpp b/netwerk/base/nsLoadGroup.cpp
new file mode 100644
index 0000000000..3e2445b6ae
--- /dev/null
+++ b/netwerk/base/nsLoadGroup.cpp
@@ -0,0 +1,1158 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=4 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsLoadGroup.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "mozilla/Logging.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/Telemetry.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsITimedChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIRequestObserver.h"
+#include "CacheObserver.h"
+#include "MainThreadUtils.h"
+#include "RequestContextService.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/Unused.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/StaticPrefs_network.h"
+
+namespace mozilla {
+namespace net {
+
+//
+// Log module for nsILoadGroup logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=LoadGroup:5
+// set MOZ_LOG_FILE=network.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file network.log.
+//
+static LazyLogModule gLoadGroupLog("LoadGroup");
+#undef LOG
+#define LOG(args) MOZ_LOG(gLoadGroupLog, mozilla::LogLevel::Debug, args)
+
+////////////////////////////////////////////////////////////////////////////////
+
+class RequestMapEntry : public PLDHashEntryHdr {
+ public:
+ explicit RequestMapEntry(nsIRequest* aRequest) : mKey(aRequest) {}
+
+ nsCOMPtr<nsIRequest> mKey;
+};
+
+static bool RequestHashMatchEntry(const PLDHashEntryHdr* entry,
+ const void* key) {
+ const RequestMapEntry* e = static_cast<const RequestMapEntry*>(entry);
+ const nsIRequest* request = static_cast<const nsIRequest*>(key);
+
+ return e->mKey == request;
+}
+
+static void RequestHashClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
+ RequestMapEntry* e = static_cast<RequestMapEntry*>(entry);
+
+ // An entry is being cleared, let the entry do its own cleanup.
+ e->~RequestMapEntry();
+}
+
+static void RequestHashInitEntry(PLDHashEntryHdr* entry, const void* key) {
+ const nsIRequest* const_request = static_cast<const nsIRequest*>(key);
+ nsIRequest* request = const_cast<nsIRequest*>(const_request);
+
+ // Initialize the entry with placement new
+ new (entry) RequestMapEntry(request);
+}
+
+static const PLDHashTableOps sRequestHashOps = {
+ PLDHashTable::HashVoidPtrKeyStub, RequestHashMatchEntry,
+ PLDHashTable::MoveEntryStub, RequestHashClearEntry, RequestHashInitEntry};
+
+static void RescheduleRequest(nsIRequest* aRequest, int32_t delta) {
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(aRequest);
+ if (p) p->AdjustPriority(delta);
+}
+
+nsLoadGroup::nsLoadGroup()
+ : mRequests(&sRequestHashOps, sizeof(RequestMapEntry)) {
+ LOG(("LOADGROUP [%p]: Created.\n", this));
+}
+
+nsLoadGroup::~nsLoadGroup() {
+ DebugOnly<nsresult> rv =
+ CancelWithReason(NS_BINDING_ABORTED, "nsLoadGroup::~nsLoadGroup"_ns);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed");
+
+ mDefaultLoadRequest = nullptr;
+
+ if (mRequestContext && !mExternalRequestContext) {
+ mRequestContextService->RemoveRequestContext(mRequestContext->GetID());
+ if (IsNeckoChild() && gNeckoChild) {
+ gNeckoChild->SendRemoveRequestContext(mRequestContext->GetID());
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ Unused << os->RemoveObserver(this, "last-pb-context-exited");
+ }
+
+ LOG(("LOADGROUP [%p]: Destroyed.\n", this));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports methods:
+
+NS_IMPL_ISUPPORTS(nsLoadGroup, nsILoadGroup, nsILoadGroupChild, nsIRequest,
+ nsISupportsPriority, nsISupportsWeakReference, nsIObserver)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetName(nsACString& result) {
+ // XXX is this the right "name" for a load group?
+
+ if (!mDefaultLoadRequest) {
+ result.Truncate();
+ return NS_OK;
+ }
+
+ return mDefaultLoadRequest->GetName(result);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::IsPending(bool* aResult) {
+ *aResult = mForegroundCount > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetStatus(nsresult* status) {
+ if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest) {
+ return mDefaultLoadRequest->GetStatus(status);
+ }
+
+ *status = mStatus;
+ return NS_OK;
+}
+
+static bool AppendRequestsToArray(PLDHashTable* aTable,
+ nsTArray<nsIRequest*>* aArray) {
+ for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
+ auto* e = static_cast<RequestMapEntry*>(iter.Get());
+ nsIRequest* request = e->mKey;
+ MOZ_DIAGNOSTIC_ASSERT(request, "Null key in mRequests PLDHashTable entry");
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ aArray->AppendElement(request);
+ NS_ADDREF(request);
+ }
+
+ if (aArray->Length() != aTable->EntryCount()) {
+ for (uint32_t i = 0, len = aArray->Length(); i < len; ++i) {
+ NS_RELEASE((*aArray)[i]);
+ }
+ return false;
+ }
+ return true;
+}
+
+NS_IMETHODIMP nsLoadGroup::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsLoadGroup::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsLoadGroup::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::Cancel(nsresult status) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code");
+ nsresult rv;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // set the load group status to our cancel status while we cancel
+ // all our requests...once the cancel is done, we'll reset it...
+ //
+ mStatus = status;
+
+ // Set the flag indicating that the loadgroup is being canceled... This
+ // prevents any new channels from being added during the operation.
+ //
+ mIsCanceling = true;
+
+ nsresult firstError = NS_OK;
+ while (count > 0) {
+ nsCOMPtr<nsIRequest> request = requests.ElementAt(--count);
+
+ NS_ASSERTION(request, "NULL request found in list.");
+
+ if (!mRequests.Search(request)) {
+ // |request| was removed already
+ // We need to null out the entry in the request array so we don't try
+ // to notify the observers for this request.
+ nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(count));
+ requests.ElementAt(count) = nullptr;
+
+ continue;
+ }
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%p]: Canceling request %p %s.\n", this, request.get(),
+ nameStr.get()));
+ }
+
+ // Cancel the request...
+ rv = request->CancelWithReason(status, mCanceledReason);
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv;
+
+ if (NS_FAILED(RemoveRequestFromHashtable(request, status))) {
+ // It's possible that request->Cancel causes the request to be removed
+ // from the loadgroup causing RemoveRequestFromHashtable to fail.
+ // In that case we shouldn't call NotifyRemovalObservers or decrement
+ // mForegroundCount since that has already happened.
+ nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(count));
+ requests.ElementAt(count) = nullptr;
+
+ continue;
+ }
+ }
+
+ for (count = requests.Length(); count > 0;) {
+ nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count));
+ (void)NotifyRemovalObservers(request, status);
+ }
+
+ if (mRequestContext) {
+ Unused << mRequestContext->CancelTailPendingRequests(status);
+ }
+
+#if defined(DEBUG)
+ NS_ASSERTION(mRequests.EntryCount() == 0, "Request list is not empty.");
+ NS_ASSERTION(mForegroundCount == 0, "Foreground URLs are active.");
+#endif
+
+ mStatus = NS_OK;
+ mIsCanceling = false;
+
+ return firstError;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::Suspend() {
+ nsresult rv, firstError;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ firstError = NS_OK;
+ //
+ // Operate the elements from back to front so that if items get
+ // get removed from the list it won't affect our iteration
+ //
+ while (count > 0) {
+ nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count));
+
+ NS_ASSERTION(request, "NULL request found in list.");
+ if (!request) continue;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%p]: Suspending request %p %s.\n", this, request.get(),
+ nameStr.get()));
+ }
+
+ // Suspend the request...
+ rv = request->Suspend();
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv;
+ }
+
+ return firstError;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::Resume() {
+ nsresult rv, firstError;
+ uint32_t count = mRequests.EntryCount();
+
+ AutoTArray<nsIRequest*, 8> requests;
+
+ if (!AppendRequestsToArray(&mRequests, &requests)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ firstError = NS_OK;
+ //
+ // Operate the elements from back to front so that if items get
+ // get removed from the list it won't affect our iteration
+ //
+ while (count > 0) {
+ nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count));
+
+ NS_ASSERTION(request, "NULL request found in list.");
+ if (!request) continue;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%p]: Resuming request %p %s.\n", this, request.get(),
+ nameStr.get()));
+ }
+
+ // Resume the request...
+ rv = request->Resume();
+
+ // Remember the first failure and return it...
+ if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv;
+ }
+
+ return firstError;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetLoadFlags(uint32_t* aLoadFlags) {
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetLoadFlags(uint32_t aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetLoadGroup(nsILoadGroup** loadGroup) {
+ nsCOMPtr<nsILoadGroup> result = mLoadGroup;
+ result.forget(loadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetLoadGroup(nsILoadGroup* loadGroup) {
+ mLoadGroup = loadGroup;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsILoadGroup methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetDefaultLoadRequest(nsIRequest** aRequest) {
+ nsCOMPtr<nsIRequest> result = mDefaultLoadRequest;
+ result.forget(aRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetDefaultLoadRequest(nsIRequest* aRequest) {
+ LOG(("nsLoadGroup::SetDefaultLoadRequest this=%p default-request=%p", this,
+ aRequest));
+
+ mDefaultLoadRequest = aRequest;
+ // Inherit the group load flags from the default load request
+ if (mDefaultLoadRequest) {
+ mDefaultLoadRequest->GetLoadFlags(&mLoadFlags);
+ //
+ // Mask off any bits that are not part of the nsIRequest flags.
+ // in particular, nsIChannel::LOAD_DOCUMENT_URI...
+ //
+ mLoadFlags &= nsIRequest::LOAD_REQUESTMASK;
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(aRequest);
+ mDefaultLoadIsTimed = timedChannel != nullptr;
+ if (mDefaultLoadIsTimed) {
+ timedChannel->GetChannelCreation(&mDefaultRequestCreationTime);
+ timedChannel->SetTimingEnabled(true);
+ }
+ }
+ // Else, do not change the group's load flags (see bug 95981)
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::AddRequest(nsIRequest* request, nsISupports* ctxt) {
+ nsresult rv;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%p]: Adding request %p %s (count=%d).\n", this, request,
+ nameStr.get(), mRequests.EntryCount()));
+ }
+
+ NS_ASSERTION(!mRequests.Search(request),
+ "Entry added to loadgroup twice, don't do that");
+
+ //
+ // Do not add the channel, if the loadgroup is being canceled...
+ //
+ if (mIsCanceling) {
+ LOG(
+ ("LOADGROUP [%p]: AddChannel() ABORTED because LoadGroup is"
+ " being canceled!!\n",
+ this));
+
+ return NS_BINDING_ABORTED;
+ }
+
+ nsLoadFlags flags;
+ // if the request is the default load request or if the default load
+ // request is null, then the load group should inherit its load flags from
+ // the request, but also we need to enforce defaultLoadFlags.
+ if (mDefaultLoadRequest == request || !mDefaultLoadRequest) {
+ rv = MergeDefaultLoadFlags(request, flags);
+ } else {
+ rv = MergeLoadFlags(request, flags);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ //
+ // Add the request to the list of active requests...
+ //
+
+ auto* entry = static_cast<RequestMapEntry*>(mRequests.Add(request, fallible));
+ if (!entry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mPriority != 0) RescheduleRequest(request, mPriority);
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request);
+ if (timedChannel) timedChannel->SetTimingEnabled(true);
+
+ bool foreground = !(flags & nsIRequest::LOAD_BACKGROUND);
+ if (foreground) {
+ // Update the count of foreground URIs..
+ mForegroundCount += 1;
+ }
+
+ if (foreground || mNotifyObserverAboutBackgroundRequests) {
+ //
+ // Fire the OnStartRequest notification out to the observer...
+ //
+ // If the notification fails then DO NOT add the request to
+ // the load group.
+ //
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ LOG(
+ ("LOADGROUP [%p]: Firing OnStartRequest for request %p."
+ "(foreground count=%d).\n",
+ this, request, mForegroundCount));
+
+ rv = observer->OnStartRequest(request);
+ if (NS_FAILED(rv)) {
+ LOG(("LOADGROUP [%p]: OnStartRequest for request %p FAILED.\n", this,
+ request));
+ //
+ // The URI load has been canceled by the observer. Clean up
+ // the damage...
+ //
+
+ mRequests.Remove(request);
+
+ rv = NS_OK;
+
+ if (foreground) {
+ mForegroundCount -= 1;
+ }
+ }
+ }
+
+ // Ensure that we're part of our loadgroup while pending
+ if (foreground && mForegroundCount == 1 && mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::RemoveRequest(nsIRequest* request, nsISupports* ctxt,
+ nsresult aStatus) {
+ // Make sure we have a owning reference to the request we're about
+ // to remove.
+ nsCOMPtr<nsIRequest> kungFuDeathGrip(request);
+
+ nsresult rv = RemoveRequestFromHashtable(request, aStatus);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NotifyRemovalObservers(request, aStatus);
+}
+
+nsresult nsLoadGroup::RemoveRequestFromHashtable(nsIRequest* request,
+ nsresult aStatus) {
+ NS_ENSURE_ARG_POINTER(request);
+ nsresult rv;
+
+ if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
+ nsAutoCString nameStr;
+ request->GetName(nameStr);
+ LOG(("LOADGROUP [%p]: Removing request %p %s status %" PRIx32
+ " (count=%d).\n",
+ this, request, nameStr.get(), static_cast<uint32_t>(aStatus),
+ mRequests.EntryCount() - 1));
+ }
+
+ //
+ // Remove the request from the group. If this fails, it means that
+ // the request was *not* in the group so do not update the foreground
+ // count or it will get messed up...
+ //
+ auto* entry = static_cast<RequestMapEntry*>(mRequests.Search(request));
+
+ if (!entry) {
+ LOG(("LOADGROUP [%p]: Unable to remove request %p. Not in group!\n", this,
+ request));
+
+ return NS_ERROR_FAILURE;
+ }
+
+ mRequests.RemoveEntry(entry);
+
+ // Collect telemetry stats only when default request is a timed channel.
+ // Don't include failed requests in the timing statistics.
+ if (mDefaultLoadIsTimed && NS_SUCCEEDED(aStatus)) {
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request);
+ if (timedChannel) {
+ // Figure out if this request was served from the cache
+ ++mTimedRequests;
+ TimeStamp timeStamp;
+ rv = timedChannel->GetCacheReadStart(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ ++mCachedRequests;
+ }
+
+ rv = timedChannel->GetAsyncOpen(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP_SUBITEM_OPEN_LATENCY_TIME,
+ mDefaultRequestCreationTime, timeStamp);
+ }
+
+ rv = timedChannel->GetResponseStart(&timeStamp);
+ if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP_SUBITEM_FIRST_BYTE_LATENCY_TIME,
+ mDefaultRequestCreationTime, timeStamp);
+ }
+
+ TelemetryReportChannel(timedChannel, false);
+ }
+ }
+
+ if (mRequests.EntryCount() == 0) {
+ TelemetryReport();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsLoadGroup::NotifyRemovalObservers(nsIRequest* request,
+ nsresult aStatus) {
+ NS_ENSURE_ARG_POINTER(request);
+ // Undo any group priority delta...
+ if (mPriority != 0) RescheduleRequest(request, -mPriority);
+
+ nsLoadFlags flags;
+ nsresult rv = request->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) return rv;
+
+ bool foreground = !(flags & nsIRequest::LOAD_BACKGROUND);
+ if (foreground) {
+ NS_ASSERTION(mForegroundCount > 0, "ForegroundCount messed up");
+ mForegroundCount -= 1;
+ }
+
+ if (foreground || mNotifyObserverAboutBackgroundRequests) {
+ // Fire the OnStopRequest out to the observer...
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ LOG(
+ ("LOADGROUP [%p]: Firing OnStopRequest for request %p."
+ "(foreground count=%d).\n",
+ this, request, mForegroundCount));
+
+ rv = observer->OnStopRequest(request, aStatus);
+
+ if (NS_FAILED(rv)) {
+ LOG(("LOADGROUP [%p]: OnStopRequest for request %p FAILED.\n", this,
+ request));
+ }
+ }
+
+ // If that was the last request -> remove ourselves from loadgroup
+ if (foreground && mForegroundCount == 0 && mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRequests(nsISimpleEnumerator** aRequests) {
+ nsCOMArray<nsIRequest> requests;
+ requests.SetCapacity(mRequests.EntryCount());
+
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ auto* e = static_cast<RequestMapEntry*>(iter.Get());
+ requests.AppendObject(e->mKey);
+ }
+
+ return NS_NewArrayEnumerator(aRequests, requests, NS_GET_IID(nsIRequest));
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver) {
+ SetGroupObserver(aObserver, false);
+ return NS_OK;
+}
+
+void nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver,
+ bool aIncludeBackgroundRequests) {
+ mObserver = do_GetWeakReference(aObserver);
+ mNotifyObserverAboutBackgroundRequests = aIncludeBackgroundRequests;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetGroupObserver(nsIRequestObserver** aResult) {
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ observer.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetActiveCount(uint32_t* aResult) {
+ *aResult = mForegroundCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ NS_ENSURE_ARG_POINTER(aCallbacks);
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = mCallbacks;
+ callbacks.forget(aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRequestContextID(uint64_t* aRCID) {
+ if (!mRequestContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aRCID = mRequestContext->GetID();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsILoadGroupChild methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetParentLoadGroup(nsILoadGroup** aParentLoadGroup) {
+ *aParentLoadGroup = nullptr;
+ nsCOMPtr<nsILoadGroup> parent = do_QueryReferent(mParentLoadGroup);
+ if (!parent) return NS_OK;
+ parent.forget(aParentLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetParentLoadGroup(nsILoadGroup* aParentLoadGroup) {
+ mParentLoadGroup = do_GetWeakReference(aParentLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetChildLoadGroup(nsILoadGroup** aChildLoadGroup) {
+ *aChildLoadGroup = do_AddRef(this).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetRootLoadGroup(nsILoadGroup** aRootLoadGroup) {
+ // first recursively try the root load group of our parent
+ nsCOMPtr<nsILoadGroupChild> ancestor = do_QueryReferent(mParentLoadGroup);
+ if (ancestor) return ancestor->GetRootLoadGroup(aRootLoadGroup);
+
+ // next recursively try the root load group of our own load grop
+ ancestor = do_QueryInterface(mLoadGroup);
+ if (ancestor) return ancestor->GetRootLoadGroup(aRootLoadGroup);
+
+ // finally just return this
+ *aRootLoadGroup = do_AddRef(this).take();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupportsPriority methods:
+
+NS_IMETHODIMP
+nsLoadGroup::GetPriority(int32_t* aValue) {
+ *aValue = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetPriority(int32_t aValue) {
+ return AdjustPriority(aValue - mPriority);
+}
+
+NS_IMETHODIMP
+nsLoadGroup::AdjustPriority(int32_t aDelta) {
+ // Update the priority for each request that supports nsISupportsPriority
+ if (aDelta != 0) {
+ mPriority += aDelta;
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ auto* e = static_cast<RequestMapEntry*>(iter.Get());
+ RescheduleRequest(e->mKey, aDelta);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetDefaultLoadFlags(uint32_t* aFlags) {
+ *aFlags = mDefaultLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::SetDefaultLoadFlags(uint32_t aFlags) {
+ mDefaultLoadFlags = aFlags;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void nsLoadGroup::TelemetryReport() {
+ nsresult defaultStatus = NS_ERROR_INVALID_ARG;
+ // We should only report HTTP_PAGE_* telemetry if the defaultRequest was
+ // actually successful.
+ if (mDefaultLoadRequest) {
+ mDefaultLoadRequest->GetStatus(&defaultStatus);
+ }
+ if (mDefaultLoadIsTimed && NS_SUCCEEDED(defaultStatus)) {
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE, mTimedRequests);
+ if (mTimedRequests) {
+ Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE_FROM_CACHE,
+ mCachedRequests * 100 / mTimedRequests);
+ }
+
+ nsCOMPtr<nsITimedChannel> timedChannel =
+ do_QueryInterface(mDefaultLoadRequest);
+ if (timedChannel) TelemetryReportChannel(timedChannel, true);
+ }
+
+ mTimedRequests = 0;
+ mCachedRequests = 0;
+ mDefaultLoadIsTimed = false;
+}
+
+void nsLoadGroup::TelemetryReportChannel(nsITimedChannel* aTimedChannel,
+ bool aDefaultRequest) {
+ nsresult rv;
+ bool timingEnabled;
+ rv = aTimedChannel->GetTimingEnabled(&timingEnabled);
+ if (NS_FAILED(rv) || !timingEnabled) return;
+
+ TimeStamp asyncOpen;
+ rv = aTimedChannel->GetAsyncOpen(&asyncOpen);
+ // We do not check !asyncOpen.IsNull() bellow, prevent ASSERTIONs this way
+ if (NS_FAILED(rv) || asyncOpen.IsNull()) return;
+
+ TimeStamp cacheReadStart;
+ rv = aTimedChannel->GetCacheReadStart(&cacheReadStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp cacheReadEnd;
+ rv = aTimedChannel->GetCacheReadEnd(&cacheReadEnd);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp domainLookupStart;
+ rv = aTimedChannel->GetDomainLookupStart(&domainLookupStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp domainLookupEnd;
+ rv = aTimedChannel->GetDomainLookupEnd(&domainLookupEnd);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp connectStart;
+ rv = aTimedChannel->GetConnectStart(&connectStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp secureConnectionStart;
+ rv = aTimedChannel->GetSecureConnectionStart(&secureConnectionStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp connectEnd;
+ rv = aTimedChannel->GetConnectEnd(&connectEnd);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp requestStart;
+ rv = aTimedChannel->GetRequestStart(&requestStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp responseStart;
+ rv = aTimedChannel->GetResponseStart(&responseStart);
+ if (NS_FAILED(rv)) return;
+
+ TimeStamp responseEnd;
+ rv = aTimedChannel->GetResponseEnd(&responseEnd);
+ if (NS_FAILED(rv)) return;
+
+ bool useHttp3 = false;
+ bool supportHttp3 = false;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel =
+ do_QueryInterface(aTimedChannel);
+ if (httpChannel) {
+ uint32_t major;
+ uint32_t minor;
+ if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
+ useHttp3 = major == 3;
+ if (major == 2) {
+ if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
+ supportHttp3 = false;
+ }
+ }
+ }
+ }
+
+#define HTTP_REQUEST_HISTOGRAMS(prefix) \
+ if (!domainLookupStart.IsNull()) { \
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_DNS_ISSUE_TIME, \
+ asyncOpen, domainLookupStart); \
+ } \
+ \
+ if (!domainLookupStart.IsNull() && !domainLookupEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_DNS_LOOKUP_TIME, \
+ domainLookupStart, domainLookupEnd); \
+ } \
+ \
+ if (!secureConnectionStart.IsNull() && !connectEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_TLS_HANDSHAKE, \
+ secureConnectionStart, connectEnd); \
+ } \
+ \
+ if (!connectStart.IsNull() && !connectEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_TCP_CONNECTION_2, connectStart, \
+ connectEnd); \
+ } \
+ \
+ if (!requestStart.IsNull() && !responseEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_SENT, asyncOpen, \
+ requestStart); \
+ \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_FIRST_SENT_TO_LAST_RECEIVED, requestStart, \
+ responseEnd); \
+ \
+ if (cacheReadStart.IsNull() && !responseStart.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_RECEIVED, asyncOpen, \
+ responseStart); \
+ } \
+ } \
+ \
+ if (!cacheReadStart.IsNull() && !cacheReadEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE_V2, asyncOpen, \
+ cacheReadStart); \
+ \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_CACHE_READ_TIME_V2, cacheReadStart, \
+ cacheReadEnd); \
+ \
+ if (!requestStart.IsNull() && !responseEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_REVALIDATION, \
+ requestStart, responseEnd); \
+ } \
+ } \
+ \
+ if (!cacheReadEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, asyncOpen, cacheReadEnd); \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED_V2, asyncOpen, \
+ cacheReadEnd); \
+ } else if (!responseEnd.IsNull()) { \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, asyncOpen, responseEnd); \
+ Telemetry::AccumulateTimeDelta( \
+ Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET_V2, asyncOpen, \
+ responseEnd); \
+ }
+
+ // Glean instrumentation of metrics previously collected via Geckoview
+ // Streaming.
+ if (aDefaultRequest) {
+ if (!cacheReadStart.IsNull() && !cacheReadEnd.IsNull()) {
+ mozilla::glean::network::first_from_cache.AccumulateRawDuration(
+ cacheReadStart - asyncOpen);
+ }
+ if (!connectEnd.IsNull()) {
+ if (!connectStart.IsNull()) {
+ mozilla::glean::network::tcp_connection.AccumulateRawDuration(
+ connectEnd - connectStart);
+ }
+ if (!secureConnectionStart.IsNull()) {
+ mozilla::glean::network::tls_handshake.AccumulateRawDuration(
+ connectEnd - secureConnectionStart);
+ }
+ }
+ if (!domainLookupStart.IsNull()) {
+ mozilla::glean::network::dns_start.AccumulateRawDuration(
+ domainLookupStart - asyncOpen);
+ if (!domainLookupEnd.IsNull()) {
+ mozilla::glean::network::dns_end.AccumulateRawDuration(
+ domainLookupEnd - domainLookupStart);
+ }
+ }
+ }
+
+ if (aDefaultRequest) {
+ HTTP_REQUEST_HISTOGRAMS(PAGE)
+ } else {
+ HTTP_REQUEST_HISTOGRAMS(SUB)
+ }
+
+ if ((useHttp3 || supportHttp3) && cacheReadStart.IsNull() &&
+ cacheReadEnd.IsNull()) {
+ nsCString key = (useHttp3) ? ((aDefaultRequest) ? "uses_http3_page"_ns
+ : "uses_http3_sub"_ns)
+ : ((aDefaultRequest) ? "supports_http3_page"_ns
+ : "supports_http3_sub"_ns);
+
+ if (!secureConnectionStart.IsNull() && !connectEnd.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_TLS_HANDSHAKE, key,
+ secureConnectionStart, connectEnd);
+ }
+
+ if (supportHttp3 && !connectStart.IsNull() && !connectEnd.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::SUP_HTTP3_TCP_CONNECTION, key,
+ connectStart, connectEnd);
+ }
+
+ if (!requestStart.IsNull() && !responseEnd.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_OPEN_TO_FIRST_SENT, key,
+ asyncOpen, requestStart);
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::HTTP3_FIRST_SENT_TO_LAST_RECEIVED, key, requestStart,
+ responseEnd);
+
+ if (!responseStart.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_OPEN_TO_FIRST_RECEIVED,
+ key, asyncOpen, responseStart);
+ }
+
+ if (!responseEnd.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_COMPLETE_LOAD, key,
+ asyncOpen, responseEnd);
+ }
+ }
+ }
+
+ bool hasHTTPSRR = false;
+ if (httpChannel && NS_SUCCEEDED(httpChannel->GetHasHTTPSRR(&hasHTTPSRR)) &&
+ cacheReadStart.IsNull() && cacheReadEnd.IsNull() &&
+ !requestStart.IsNull()) {
+ nsCString key = (hasHTTPSRR) ? ((aDefaultRequest) ? "uses_https_rr_page"_ns
+ : "uses_https_rr_sub"_ns)
+ : ((aDefaultRequest) ? "no_https_rr_page"_ns
+ : "no_https_rr_sub"_ns);
+ Telemetry::AccumulateTimeDelta(Telemetry::HTTPS_RR_OPEN_TO_FIRST_SENT, key,
+ asyncOpen, requestStart);
+ TimeDuration elapsed = requestStart - asyncOpen;
+ if (hasHTTPSRR) {
+ if (aDefaultRequest) {
+ glean::networking::http_channel_page_open_to_first_sent_https_rr
+ .AccumulateRawDuration(elapsed);
+ } else {
+ glean::networking::http_channel_sub_open_to_first_sent_https_rr
+ .AccumulateRawDuration(elapsed);
+ }
+ } else {
+ if (aDefaultRequest) {
+ glean::networking::http_channel_page_open_to_first_sent
+ .AccumulateRawDuration(elapsed);
+ } else {
+ glean::networking::http_channel_sub_open_to_first_sent
+ .AccumulateRawDuration(elapsed);
+ }
+ }
+ }
+
+#undef HTTP_REQUEST_HISTOGRAMS
+}
+
+nsresult nsLoadGroup::MergeLoadFlags(nsIRequest* aRequest,
+ nsLoadFlags& outFlags) {
+ nsresult rv;
+ nsLoadFlags flags, oldFlags;
+
+ rv = aRequest->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ oldFlags = flags;
+
+ // Inherit the following bits...
+ flags |= (mLoadFlags &
+ (LOAD_BACKGROUND | LOAD_BYPASS_CACHE | LOAD_FROM_CACHE |
+ VALIDATE_ALWAYS | VALIDATE_ONCE_PER_SESSION | VALIDATE_NEVER));
+
+ // ... and force the default flags.
+ flags |= mDefaultLoadFlags;
+
+ if (flags != oldFlags) {
+ rv = aRequest->SetLoadFlags(flags);
+ }
+
+ outFlags = flags;
+ return rv;
+}
+
+nsresult nsLoadGroup::MergeDefaultLoadFlags(nsIRequest* aRequest,
+ nsLoadFlags& outFlags) {
+ nsresult rv;
+ nsLoadFlags flags, oldFlags;
+
+ rv = aRequest->GetLoadFlags(&flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ oldFlags = flags;
+ // ... and force the default flags.
+ flags |= mDefaultLoadFlags;
+
+ if (flags != oldFlags) {
+ rv = aRequest->SetLoadFlags(flags);
+ }
+ outFlags = flags;
+ return rv;
+}
+
+nsresult nsLoadGroup::Init() {
+ mRequestContextService = RequestContextService::GetOrCreate();
+ if (mRequestContextService) {
+ Unused << mRequestContextService->NewRequestContext(
+ getter_AddRefs(mRequestContext));
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ Unused << os->AddObserver(this, "last-pb-context-exited", true);
+
+ return NS_OK;
+}
+
+nsresult nsLoadGroup::InitWithRequestContextId(
+ const uint64_t& aRequestContextId) {
+ mRequestContextService = RequestContextService::GetOrCreate();
+ if (mRequestContextService) {
+ Unused << mRequestContextService->GetRequestContext(
+ aRequestContextId, getter_AddRefs(mRequestContext));
+ }
+ mExternalRequestContext = true;
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ Unused << os->AddObserver(this, "last-pb-context-exited", true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(!strcmp(aTopic, "last-pb-context-exited"));
+
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
+ if (attrs.mPrivateBrowsingId == 0) {
+ return NS_OK;
+ }
+
+ mBrowsingContextDiscarded = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetIsBrowsingContextDiscarded(bool* aIsBrowsingContextDiscarded) {
+ *aIsBrowsingContextDiscarded = mBrowsingContextDiscarded;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef LOG
diff --git a/netwerk/base/nsLoadGroup.h b/netwerk/base/nsLoadGroup.h
new file mode 100644
index 0000000000..055e1804d6
--- /dev/null
+++ b/netwerk/base/nsLoadGroup.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLoadGroup_h__
+#define nsLoadGroup_h__
+
+#include "nsILoadGroup.h"
+#include "nsILoadGroupChild.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+#include "nsISupportsPriority.h"
+#include "PLDHashTable.h"
+#include "mozilla/TimeStamp.h"
+
+class nsIRequestContext;
+class nsIRequestContextService;
+class nsITimedChannel;
+
+namespace mozilla {
+namespace net {
+
+class nsLoadGroup : public nsILoadGroup,
+ public nsILoadGroupChild,
+ public nsIObserver,
+ public nsISupportsPriority,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsIRequest methods:
+ NS_DECL_NSIREQUEST
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsILoadGroup methods:
+ NS_DECL_NSILOADGROUP
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsILoadGroupChild methods:
+ NS_DECL_NSILOADGROUPCHILD
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsISupportsPriority methods:
+ NS_DECL_NSISUPPORTSPRIORITY
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsIObserver methods:
+ NS_DECL_NSIOBSERVER
+
+ ////////////////////////////////////////////////////////////////////////////
+ // nsLoadGroup methods:
+
+ nsLoadGroup();
+
+ nsresult Init();
+ nsresult InitWithRequestContextId(const uint64_t& aRequestContextId);
+
+ void SetGroupObserver(nsIRequestObserver* aObserver,
+ bool aIncludeBackgroundRequests);
+
+ protected:
+ virtual ~nsLoadGroup();
+
+ nsresult MergeLoadFlags(nsIRequest* aRequest, nsLoadFlags& flags);
+ nsresult MergeDefaultLoadFlags(nsIRequest* aRequest, nsLoadFlags& flags);
+
+ private:
+ void TelemetryReport();
+ void TelemetryReportChannel(nsITimedChannel* timedChannel,
+ bool defaultRequest);
+
+ nsresult RemoveRequestFromHashtable(nsIRequest* aRequest, nsresult aStatus);
+ nsresult NotifyRemovalObservers(nsIRequest* aRequest, nsresult aStatus);
+
+ protected:
+ uint32_t mForegroundCount{0};
+ uint32_t mLoadFlags{LOAD_NORMAL};
+ uint32_t mDefaultLoadFlags{0};
+ int32_t mPriority{PRIORITY_NORMAL};
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup; // load groups can contain load groups
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+ nsCOMPtr<nsIRequestContextService> mRequestContextService;
+
+ nsCOMPtr<nsIRequest> mDefaultLoadRequest;
+ PLDHashTable mRequests;
+
+ nsWeakPtr mObserver;
+ nsWeakPtr mParentLoadGroup;
+
+ nsresult mStatus{NS_OK};
+ bool mIsCanceling{false};
+ bool mDefaultLoadIsTimed{false};
+ bool mBrowsingContextDiscarded{false};
+ bool mExternalRequestContext{false};
+ bool mNotifyObserverAboutBackgroundRequests{false};
+
+ /* Telemetry */
+ mozilla::TimeStamp mDefaultRequestCreationTime;
+ uint32_t mTimedRequests{0};
+ uint32_t mCachedRequests{0};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsLoadGroup_h__
diff --git a/netwerk/base/nsMIMEInputStream.cpp b/netwerk/base/nsMIMEInputStream.cpp
new file mode 100644
index 0000000000..d0d9fe09a5
--- /dev/null
+++ b/netwerk/base/nsMIMEInputStream.cpp
@@ -0,0 +1,500 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+#include "nsMIMEInputStream.h"
+
+#include <utility>
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsIInputStreamLength.h"
+#include "nsIMIMEInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsString.h"
+
+using namespace mozilla::ipc;
+using mozilla::Maybe;
+
+class nsMIMEInputStream : public nsIMIMEInputStream,
+ public nsISeekableStream,
+ public nsIIPCSerializableInputStream,
+ public nsIAsyncInputStream,
+ public nsIInputStreamCallback,
+ public nsIInputStreamLength,
+ public nsIAsyncInputStreamLength,
+ public nsIInputStreamLengthCallback,
+ public nsICloneableInputStream {
+ virtual ~nsMIMEInputStream() = default;
+
+ public:
+ nsMIMEInputStream() = default;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIMIMEINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIINPUTSTREAMLENGTH
+ NS_DECL_NSIASYNCINPUTSTREAMLENGTH
+ NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+
+ private:
+ struct MOZ_STACK_CLASS ReadSegmentsState {
+ nsCOMPtr<nsIInputStream> mThisStream;
+ nsWriteSegmentFun mWriter{nullptr};
+ void* mClosure{nullptr};
+ };
+ static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount);
+
+ bool IsSeekableInputStream() const;
+ bool IsAsyncInputStream() const;
+ bool IsInputStreamLength() const;
+ bool IsAsyncInputStreamLength() const;
+ bool IsCloneableInputStream() const;
+
+ nsTArray<HeaderEntry> mHeaders;
+
+ nsCOMPtr<nsIInputStream> mStream;
+ mozilla::Atomic<bool, mozilla::Relaxed> mStartedReading{false};
+
+ mozilla::Mutex mMutex{"nsMIMEInputStream::mMutex"};
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback MOZ_GUARDED_BY(mMutex);
+
+ // This is protected by mutex.
+ nsCOMPtr<nsIInputStreamLengthCallback> mAsyncInputStreamLengthCallback;
+};
+
+NS_IMPL_ADDREF(nsMIMEInputStream)
+NS_IMPL_RELEASE(nsMIMEInputStream)
+
+NS_IMPL_CLASSINFO(nsMIMEInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_MIMEINPUTSTREAM_CID)
+
+NS_INTERFACE_MAP_BEGIN(nsMIMEInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIMIMEInputStream)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIInputStream, nsIMIMEInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsITellableStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, IsSeekableInputStream())
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, IsAsyncInputStream())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
+ IsAsyncInputStream())
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMIMEInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength,
+ IsInputStreamLength())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength,
+ IsAsyncInputStreamLength())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback,
+ IsAsyncInputStreamLength())
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
+ IsCloneableInputStream())
+ NS_IMPL_QUERY_CLASSINFO(nsMIMEInputStream)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream, nsIMIMEInputStream,
+ nsIAsyncInputStream, nsIInputStream,
+ nsISeekableStream, nsITellableStream)
+
+NS_IMETHODIMP
+nsMIMEInputStream::AddHeader(const char* aName, const char* aValue) {
+ NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+
+ HeaderEntry* entry = mHeaders.AppendElement();
+ entry->name().Append(aName);
+ entry->value().Append(aValue);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::VisitHeaders(nsIHttpHeaderVisitor* visitor) {
+ nsresult rv;
+
+ for (auto& header : mHeaders) {
+ rv = visitor->VisitHeader(header.name(), header.value());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::SetData(nsIInputStream* aStream) {
+ NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+
+ mStream = aStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::GetData(nsIInputStream** aStream) {
+ NS_ENSURE_ARG_POINTER(aStream);
+ *aStream = do_AddRef(mStream).take();
+ return NS_OK;
+}
+
+#define INITSTREAMS \
+ if (!mStartedReading) { \
+ NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); \
+ mStartedReading = true; \
+ }
+
+// Reset mStartedReading when Seek-ing to start
+NS_IMETHODIMP
+nsMIMEInputStream::Seek(int32_t whence, int64_t offset) {
+ NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+
+ if (whence == NS_SEEK_SET && offset == 0) {
+ rv = stream->Seek(whence, offset);
+ if (NS_SUCCEEDED(rv)) mStartedReading = false;
+ } else {
+ INITSTREAMS;
+ rv = stream->Seek(whence, offset);
+ }
+
+ return rv;
+}
+
+// Proxy ReadSegments since we need to be a good little nsIInputStream
+NS_IMETHODIMP nsMIMEInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ INITSTREAMS;
+ ReadSegmentsState state;
+ // Disambiguate ambiguous nsIInputStream.
+ state.mThisStream =
+ static_cast<nsIInputStream*>(static_cast<nsIMIMEInputStream*>(this));
+ state.mWriter = aWriter;
+ state.mClosure = aClosure;
+ return mStream->ReadSegments(ReadSegCb, &state, aCount, _retval);
+}
+
+nsresult nsMIMEInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ ReadSegmentsState* state = (ReadSegmentsState*)aClosure;
+ return (state->mWriter)(state->mThisStream, state->mClosure, aFromRawSegment,
+ aToOffset, aCount, aWriteCount);
+}
+
+/**
+ * Forward everything else to the mStream after calling INITSTREAMS
+ */
+
+// nsIInputStream
+NS_IMETHODIMP nsMIMEInputStream::Close(void) {
+ INITSTREAMS;
+ return mStream->Close();
+}
+NS_IMETHODIMP nsMIMEInputStream::Available(uint64_t* _retval) {
+ INITSTREAMS;
+ return mStream->Available(_retval);
+}
+NS_IMETHODIMP nsMIMEInputStream::StreamStatus() {
+ INITSTREAMS;
+ return mStream->StreamStatus();
+}
+NS_IMETHODIMP nsMIMEInputStream::Read(char* buf, uint32_t count,
+ uint32_t* _retval) {
+ INITSTREAMS;
+ return mStream->Read(buf, count, _retval);
+}
+NS_IMETHODIMP nsMIMEInputStream::IsNonBlocking(bool* aNonBlocking) {
+ INITSTREAMS;
+ return mStream->IsNonBlocking(aNonBlocking);
+}
+
+// nsIAsyncInputStream
+NS_IMETHODIMP
+nsMIMEInputStream::CloseWithStatus(nsresult aStatus) {
+ INITSTREAMS;
+ nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream);
+ return asyncStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ INITSTREAMS;
+ nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream);
+ if (NS_WARN_IF(!asyncStream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr;
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ if (NS_WARN_IF(mAsyncWaitCallback && aCallback &&
+ mAsyncWaitCallback != aCallback)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mAsyncWaitCallback = aCallback;
+ }
+
+ return asyncStream->AsyncWait(callback, aFlags, aRequestedCount,
+ aEventTarget);
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+nsMIMEInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+
+ // We have been canceled in the meanwhile.
+ if (!mAsyncWaitCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncWaitCallback);
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamReady(this);
+}
+
+// nsITellableStream
+NS_IMETHODIMP nsMIMEInputStream::Tell(int64_t* _retval) {
+ INITSTREAMS;
+ nsCOMPtr<nsITellableStream> stream = do_QueryInterface(mStream);
+ return stream->Tell(_retval);
+}
+
+// nsISeekableStream
+NS_IMETHODIMP nsMIMEInputStream::SetEOF(void) {
+ INITSTREAMS;
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
+ return stream->SetEOF();
+}
+
+/**
+ * Factory method used by do_CreateInstance
+ */
+
+nsresult nsMIMEInputStreamConstructor(REFNSIID iid, void** result) {
+ *result = nullptr;
+
+ RefPtr<nsMIMEInputStream> inst = new nsMIMEInputStream();
+ if (!inst) return NS_ERROR_OUT_OF_MEMORY;
+
+ return inst->QueryInterface(iid, result);
+}
+
+void nsMIMEInputStream::SerializedComplexity(uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ uint32_t* aPipes,
+ uint32_t* aTransferables) {
+ if (nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+ do_QueryInterface(mStream)) {
+ InputStreamHelper::SerializedComplexity(mStream, aMaxSize, aSizeUsed,
+ aPipes, aTransferables);
+ } else {
+ *aPipes = 1;
+ }
+}
+
+void nsMIMEInputStream::Serialize(InputStreamParams& aParams, uint32_t aMaxSize,
+ uint32_t* aSizeUsed) {
+ MOZ_ASSERT(aSizeUsed);
+ *aSizeUsed = 0;
+
+ MIMEInputStreamParams params;
+ params.headers() = mHeaders.Clone();
+ params.startedReading() = mStartedReading;
+
+ if (!mStream) {
+ aParams = params;
+ return;
+ }
+
+ InputStreamParams wrappedParams;
+
+ if (nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+ do_QueryInterface(mStream)) {
+ InputStreamHelper::SerializeInputStream(mStream, wrappedParams, aMaxSize,
+ aSizeUsed);
+ } else {
+ // Falling back to sending the underlying stream over a pipe when
+ // sending an nsMIMEInputStream over IPC is potentially wasteful
+ // if it is sent several times. This can possibly happen with
+ // fission. There are two ways to improve this, see bug 1648369
+ // and bug 1648370.
+ InputStreamHelper::SerializeInputStreamAsPipe(mStream, wrappedParams);
+ }
+
+ NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None,
+ "Wrapped stream failed to serialize!");
+
+ params.optionalStream().emplace(wrappedParams);
+ aParams = params;
+}
+
+bool nsMIMEInputStream::Deserialize(const InputStreamParams& aParams) {
+ if (aParams.type() != InputStreamParams::TMIMEInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const MIMEInputStreamParams& params = aParams.get_MIMEInputStreamParams();
+ const Maybe<InputStreamParams>& wrappedParams = params.optionalStream();
+
+ if (wrappedParams.isSome()) {
+ nsCOMPtr<nsIInputStream> stream;
+ stream = InputStreamHelper::DeserializeInputStream(wrappedParams.ref());
+ if (!stream) {
+ NS_WARNING("Failed to deserialize wrapped stream!");
+ return false;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(SetData(stream));
+ }
+
+ mHeaders = params.headers().Clone();
+ mStartedReading = params.startedReading();
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::Length(int64_t* aLength) {
+ nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream);
+ if (NS_WARN_IF(!stream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return stream->Length(aLength);
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback,
+ nsIEventTarget* aEventTarget) {
+ nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream);
+ if (NS_WARN_IF(!stream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback ? this : nullptr;
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ mAsyncInputStreamLengthCallback = aCallback;
+ }
+
+ return stream->AsyncLengthWait(callback, aEventTarget);
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream,
+ int64_t aLength) {
+ nsCOMPtr<nsIInputStreamLengthCallback> callback;
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ // We have been canceled in the meanwhile.
+ if (!mAsyncInputStreamLengthCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncInputStreamLengthCallback);
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamLengthReady(this, aLength);
+}
+
+bool nsMIMEInputStream::IsSeekableInputStream() const {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ return !!seekable;
+}
+
+bool nsMIMEInputStream::IsAsyncInputStream() const {
+ nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream);
+ return !!asyncStream;
+}
+
+bool nsMIMEInputStream::IsInputStreamLength() const {
+ nsCOMPtr<nsIInputStreamLength> stream = do_QueryInterface(mStream);
+ return !!stream;
+}
+
+bool nsMIMEInputStream::IsAsyncInputStreamLength() const {
+ nsCOMPtr<nsIAsyncInputStreamLength> stream = do_QueryInterface(mStream);
+ return !!stream;
+}
+
+bool nsMIMEInputStream::IsCloneableInputStream() const {
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+ return !!stream;
+}
+
+// nsICloneableInputStream interface
+
+NS_IMETHODIMP
+nsMIMEInputStream::GetCloneable(bool* aCloneable) {
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return stream->GetCloneable(aCloneable);
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::Clone(nsIInputStream** aResult) {
+ nsCOMPtr<nsICloneableInputStream> stream = do_QueryInterface(mStream);
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsresult rv = stream->Clone(getter_AddRefs(clonedStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIMIMEInputStream> mimeStream = new nsMIMEInputStream();
+
+ rv = mimeStream->SetData(clonedStream);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (const HeaderEntry& entry : mHeaders) {
+ rv = mimeStream->AddHeader(entry.name().get(), entry.value().get());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ static_cast<nsMIMEInputStream*>(mimeStream.get())->mStartedReading =
+ static_cast<bool>(mStartedReading);
+
+ mimeStream.forget(aResult);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsMIMEInputStream.h b/netwerk/base/nsMIMEInputStream.h
new file mode 100644
index 0000000000..adfc5532b1
--- /dev/null
+++ b/netwerk/base/nsMIMEInputStream.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The MIME stream separates headers and a datastream. It also allows
+ * automatic creation of the content-length header.
+ */
+
+#ifndef _nsMIMEInputStream_h_
+#define _nsMIMEInputStream_h_
+
+#include "ErrorList.h"
+#include "nsID.h"
+
+#define NS_MIMEINPUTSTREAM_CONTRACTID "@mozilla.org/network/mime-input-stream;1"
+#define NS_MIMEINPUTSTREAM_CID \
+ { /* 58a1c31c-1dd2-11b2-a3f6-d36949d48268 */ \
+ 0x58a1c31c, 0x1dd2, 0x11b2, { \
+ 0xa3, 0xf6, 0xd3, 0x69, 0x49, 0xd4, 0x82, 0x68 \
+ } \
+ }
+
+extern nsresult nsMIMEInputStreamConstructor(REFNSIID iid, void** result);
+
+#endif // _nsMIMEInputStream_h_
diff --git a/netwerk/base/nsMediaFragmentURIParser.cpp b/netwerk/base/nsMediaFragmentURIParser.cpp
new file mode 100644
index 0000000000..bfa82530c6
--- /dev/null
+++ b/netwerk/base/nsMediaFragmentURIParser.cpp
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTArray.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsEscape.h"
+#include "nsIURI.h"
+#include <utility>
+
+#include "nsMediaFragmentURIParser.h"
+
+using std::make_pair;
+using std::pair;
+
+namespace mozilla {
+namespace net {
+
+nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsIURI* aURI)
+ : mClipUnit(eClipUnit_Pixel) {
+ nsAutoCString ref;
+ aURI->GetRef(ref);
+ Parse(ref);
+}
+
+nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsCString& aRef)
+ : mClipUnit(eClipUnit_Pixel) {
+ Parse(aRef);
+}
+
+bool nsMediaFragmentURIParser::ParseNPT(nsDependentSubstring aString) {
+ nsDependentSubstring original(aString);
+ if (aString.Length() > 4 && aString[0] == 'n' && aString[1] == 'p' &&
+ aString[2] == 't' && aString[3] == ':') {
+ aString.Rebind(aString, 4);
+ }
+
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ double start = -1.0;
+ double end = -1.0;
+
+ ParseNPTTime(aString, start);
+
+ if (aString.Length() == 0) {
+ mStart.emplace(start);
+ return true;
+ }
+
+ if (aString[0] != ',') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+
+ if (aString.Length() == 0) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ ParseNPTTime(aString, end);
+
+ if (end <= start || aString.Length() != 0) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ mStart.emplace(start);
+ mEnd.emplace(end);
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTTime(nsDependentSubstring& aString,
+ double& aTime) {
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ return ParseNPTHHMMSS(aString, aTime) || ParseNPTMMSS(aString, aTime) ||
+ ParseNPTSec(aString, aTime);
+}
+
+// Return true if the given character is a numeric character
+static bool IsDigit(nsDependentSubstring::char_type aChar) {
+ return (aChar >= '0' && aChar <= '9');
+}
+
+// Return the index of the first character in the string that is not
+// a numerical digit, starting from 'aStart'.
+static uint32_t FirstNonDigit(nsDependentSubstring& aString, uint32_t aStart) {
+ while (aStart < aString.Length() && IsDigit(aString[aStart])) {
+ ++aStart;
+ }
+ return aStart;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTSec(nsDependentSubstring& aString,
+ double& aSec) {
+ nsDependentSubstring original(aString);
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t s = n.ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ double fraction = 0.0;
+ if (!ParseNPTFraction(aString, fraction)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aSec = s + fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTMMSS(nsDependentSubstring& aString,
+ double& aTime) {
+ nsDependentSubstring original(aString);
+ uint32_t mm = 0;
+ uint32_t ss = 0;
+ double fraction = 0.0;
+ if (!ParseNPTMM(aString, mm)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ if (aString.Length() < 2 || aString[0] != ':') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+ if (!ParseNPTSS(aString, ss)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ if (!ParseNPTFraction(aString, fraction)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+ aTime = mm * 60 + ss + fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTFraction(nsDependentSubstring& aString,
+ double& aFraction) {
+ double fraction = 0.0;
+
+ if (aString.Length() > 0 && aString[0] == '.') {
+ uint32_t index = FirstNonDigit(aString, 1);
+
+ if (index > 1) {
+ nsDependentSubstring number(aString, 0, index);
+ nsresult ec;
+ fraction = PromiseFlatString(number).ToDouble(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+ }
+ aString.Rebind(aString, index);
+ }
+
+ aFraction = fraction;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTHHMMSS(nsDependentSubstring& aString,
+ double& aTime) {
+ nsDependentSubstring original(aString);
+ uint32_t hh = 0;
+ double seconds = 0.0;
+ if (!ParseNPTHH(aString, hh)) {
+ return false;
+ }
+
+ if (aString.Length() < 2 || aString[0] != ':') {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aString.Rebind(aString, 1);
+ if (!ParseNPTMMSS(aString, seconds)) {
+ aString.Rebind(original, 0);
+ return false;
+ }
+
+ aTime = hh * 3600 + seconds;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTHH(nsDependentSubstring& aString,
+ uint32_t& aHour) {
+ if (aString.Length() == 0) {
+ return false;
+ }
+
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t u = n.ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ aHour = u;
+ return true;
+}
+
+bool nsMediaFragmentURIParser::ParseNPTMM(nsDependentSubstring& aString,
+ uint32_t& aMinute) {
+ return ParseNPTSS(aString, aMinute);
+}
+
+bool nsMediaFragmentURIParser::ParseNPTSS(nsDependentSubstring& aString,
+ uint32_t& aSecond) {
+ if (aString.Length() < 2) {
+ return false;
+ }
+
+ if (IsDigit(aString[0]) && IsDigit(aString[1])) {
+ nsDependentSubstring n(aString, 0, 2);
+ nsresult ec;
+ int32_t u = n.ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, 2);
+ if (u >= 60) return false;
+
+ aSecond = u;
+ return true;
+ }
+
+ return false;
+}
+
+static bool ParseInteger(nsDependentSubstring& aString, int32_t& aResult) {
+ uint32_t index = FirstNonDigit(aString, 0);
+ if (index == 0) {
+ return false;
+ }
+
+ nsDependentSubstring n(aString, 0, index);
+ nsresult ec;
+ int32_t s = n.ToInteger(&ec);
+ if (NS_FAILED(ec)) {
+ return false;
+ }
+
+ aString.Rebind(aString, index);
+ aResult = s;
+ return true;
+}
+
+static bool ParseCommaSeparator(nsDependentSubstring& aString) {
+ if (aString.Length() > 1 && aString[0] == ',') {
+ aString.Rebind(aString, 1);
+ return true;
+ }
+
+ return false;
+}
+
+bool nsMediaFragmentURIParser::ParseXYWH(nsDependentSubstring aString) {
+ int32_t x, y, w, h;
+ ClipUnit clipUnit;
+
+ // Determine units.
+ if (StringBeginsWith(aString, u"pixel:"_ns)) {
+ clipUnit = eClipUnit_Pixel;
+ aString.Rebind(aString, 6);
+ } else if (StringBeginsWith(aString, u"percent:"_ns)) {
+ clipUnit = eClipUnit_Percent;
+ aString.Rebind(aString, 8);
+ } else {
+ clipUnit = eClipUnit_Pixel;
+ }
+
+ // Read and validate coordinates.
+ if (ParseInteger(aString, x) && x >= 0 && ParseCommaSeparator(aString) &&
+ ParseInteger(aString, y) && y >= 0 && ParseCommaSeparator(aString) &&
+ ParseInteger(aString, w) && w > 0 && ParseCommaSeparator(aString) &&
+ ParseInteger(aString, h) && h > 0 && aString.Length() == 0) {
+ // Reject invalid percentage coordinates.
+ if (clipUnit == eClipUnit_Percent && (x + w > 100 || y + h > 100)) {
+ return false;
+ }
+
+ mClip.emplace(x, y, w, h);
+ mClipUnit = clipUnit;
+ return true;
+ }
+
+ return false;
+}
+
+void nsMediaFragmentURIParser::Parse(nsACString& aRef) {
+ // Create an array of possibly-invalid media fragments.
+ nsTArray<std::pair<nsCString, nsCString> > fragments;
+
+ for (const nsACString& nv : nsCCharSeparatedTokenizer(aRef, '&').ToRange()) {
+ int32_t index = nv.FindChar('=');
+ if (index >= 0) {
+ nsAutoCString name;
+ nsAutoCString value;
+ NS_UnescapeURL(StringHead(nv, index), esc_Ref | esc_AlwaysCopy, name);
+ NS_UnescapeURL(Substring(nv, index + 1, nv.Length()),
+ esc_Ref | esc_AlwaysCopy, value);
+ fragments.AppendElement(make_pair(name, value));
+ }
+ }
+
+ // Parse the media fragment values.
+ bool gotTemporal = false, gotSpatial = false;
+ for (int i = fragments.Length() - 1; i >= 0; --i) {
+ if (gotTemporal && gotSpatial) {
+ // We've got one of each possible type. No need to look at the rest.
+ break;
+ }
+ if (!gotTemporal && fragments[i].first.EqualsLiteral("t")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotTemporal = ParseNPT(nsDependentSubstring(value, 0));
+ } else if (!gotSpatial && fragments[i].first.EqualsLiteral("xywh")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotSpatial = ParseXYWH(nsDependentSubstring(value, 0));
+ }
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsMediaFragmentURIParser.h b/netwerk/base/nsMediaFragmentURIParser.h
new file mode 100644
index 0000000000..4c277fead8
--- /dev/null
+++ b/netwerk/base/nsMediaFragmentURIParser.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(nsMediaFragmentURIParser_h__)
+# define nsMediaFragmentURIParser_h__
+
+# include "mozilla/Maybe.h"
+# include "nsStringFwd.h"
+# include "nsRect.h"
+
+class nsIURI;
+
+// Class to handle parsing of a W3C media fragment URI as per
+// spec at: http://www.w3.org/TR/media-frags/
+// Only the temporaral URI portion of the spec is implemented.
+// To use:
+// a) Construct an instance with the URI containing the fragment
+// b) Check for the validity of the values you are interested in
+// using e.g. HasStartTime().
+// c) If the values are valid, obtain them using e.g. GetStartTime().
+
+namespace mozilla {
+namespace net {
+
+enum ClipUnit {
+ eClipUnit_Pixel,
+ eClipUnit_Percent,
+};
+
+class nsMediaFragmentURIParser {
+ public:
+ // Create a parser with the provided URI.
+ explicit nsMediaFragmentURIParser(nsIURI* aURI);
+
+ // Create a parser with the provided URI reference portion.
+ explicit nsMediaFragmentURIParser(nsCString& aRef);
+
+ // True if a valid temporal media fragment indicated a start time.
+ bool HasStartTime() const { return mStart.isSome(); }
+
+ // If a valid temporal media fragment indicated a start time, returns
+ // it in units of seconds. If not, defaults to 0.
+ double GetStartTime() const { return *mStart; }
+
+ // True if a valid temporal media fragment indicated an end time.
+ bool HasEndTime() const { return mEnd.isSome(); }
+
+ // If a valid temporal media fragment indicated an end time, returns
+ // it in units of seconds. If not, defaults to -1.
+ double GetEndTime() const { return *mEnd; }
+
+ // True if a valid spatial media fragment indicated a clipping region.
+ bool HasClip() const { return mClip.isSome(); }
+
+ // If a valid spatial media fragment indicated a clipping region,
+ // returns the region. If not, returns an empty region. The unit
+ // used depends on the value returned by GetClipUnit().
+ nsIntRect GetClip() const { return *mClip; }
+
+ // If a valid spatial media fragment indicated a clipping region,
+ // returns the unit used.
+ ClipUnit GetClipUnit() const { return mClipUnit; }
+
+ private:
+ // Parse the URI ref provided, looking for media fragments. This is
+ // the top-level parser the invokes the others below.
+ void Parse(nsACString& aRef);
+
+ // The following methods parse the fragment as per the media
+ // fragments specification. 'aString' contains the remaining
+ // fragment data to be parsed. The method returns true
+ // if the parse was successful and leaves the remaining unparsed
+ // data in 'aString'. If the parse fails then false is returned
+ // and 'aString' is left as it was when called.
+ bool ParseNPT(nsDependentSubstring aString);
+ bool ParseNPTTime(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTSec(nsDependentSubstring& aString, double& aSec);
+ bool ParseNPTFraction(nsDependentSubstring& aString, double& aFraction);
+ bool ParseNPTMMSS(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTHHMMSS(nsDependentSubstring& aString, double& aTime);
+ bool ParseNPTHH(nsDependentSubstring& aString, uint32_t& aHour);
+ bool ParseNPTMM(nsDependentSubstring& aString, uint32_t& aMinute);
+ bool ParseNPTSS(nsDependentSubstring& aString, uint32_t& aSecond);
+ bool ParseXYWH(nsDependentSubstring aString);
+ bool ParseMozResolution(nsDependentSubstring aString);
+
+ // Media fragment information.
+ Maybe<double> mStart;
+ Maybe<double> mEnd;
+ Maybe<nsIntRect> mClip;
+ ClipUnit mClipUnit;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsNetAddr.cpp b/netwerk/base/nsNetAddr.cpp
new file mode 100644
index 0000000000..d0cbe69bae
--- /dev/null
+++ b/netwerk/base/nsNetAddr.cpp
@@ -0,0 +1,138 @@
+/* vim: et ts=2 sw=2 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNetAddr.h"
+#include "nsString.h"
+#include "mozilla/net/DNS.h"
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(nsNetAddr, nsINetAddr)
+
+NS_IMETHODIMP nsNetAddr::GetFamily(uint16_t* aFamily) {
+ switch (mAddr.raw.family) {
+ case AF_INET:
+ *aFamily = nsINetAddr::FAMILY_INET;
+ break;
+ case AF_INET6:
+ *aFamily = nsINetAddr::FAMILY_INET6;
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ *aFamily = nsINetAddr::FAMILY_LOCAL;
+ break;
+#endif
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetAddress(nsACString& aAddress) {
+ switch (mAddr.raw.family) {
+ /* PR_NetAddrToString can handle INET and INET6, but not LOCAL. */
+ case AF_INET:
+ aAddress.SetLength(kIPv4CStrBufSize);
+ mAddr.ToStringBuffer(aAddress.BeginWriting(), kIPv4CStrBufSize);
+ aAddress.SetLength(strlen(aAddress.BeginReading()));
+ break;
+ case AF_INET6:
+ aAddress.SetLength(kIPv6CStrBufSize);
+ mAddr.ToStringBuffer(aAddress.BeginWriting(), kIPv6CStrBufSize);
+ aAddress.SetLength(strlen(aAddress.BeginReading()));
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ aAddress.Assign(mAddr.local.path);
+ break;
+#endif
+ // PR_AF_LOCAL falls through to default when not XP_UNIX
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetPort(uint16_t* aPort) {
+ switch (mAddr.raw.family) {
+ case AF_INET:
+ *aPort = ntohs(mAddr.inet.port);
+ break;
+ case AF_INET6:
+ *aPort = ntohs(mAddr.inet6.port);
+ break;
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+ // There is no port number for local / connections.
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetFlow(uint32_t* aFlow) {
+ switch (mAddr.raw.family) {
+ case AF_INET6:
+ *aFlow = ntohl(mAddr.inet6.flowinfo);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetScope(uint32_t* aScope) {
+ switch (mAddr.raw.family) {
+ case AF_INET6:
+ *aScope = ntohl(mAddr.inet6.scope_id);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetIsV4Mapped(bool* aIsV4Mapped) {
+ switch (mAddr.raw.family) {
+ case AF_INET6:
+ *aIsV4Mapped = IPv6ADDR_IS_V4MAPPED(&mAddr.inet6.ip);
+ break;
+ case AF_INET:
+#if defined(XP_UNIX)
+ case AF_LOCAL:
+#endif
+ // only for IPv6
+ return NS_ERROR_NOT_AVAILABLE;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNetAddr::GetNetAddr(NetAddr* aResult) {
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
diff --git a/netwerk/base/nsNetAddr.h b/netwerk/base/nsNetAddr.h
new file mode 100644
index 0000000000..49d43ccaee
--- /dev/null
+++ b/netwerk/base/nsNetAddr.h
@@ -0,0 +1,30 @@
+/* vim: et ts=2 sw=2 tw=80
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetAddr_h__
+#define nsNetAddr_h__
+
+#include "nsINetAddr.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Attributes.h"
+
+class nsNetAddr final : public nsINetAddr {
+ ~nsNetAddr() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETADDR
+
+ explicit nsNetAddr(const mozilla::net::NetAddr* addr) : mAddr(*addr) {}
+
+ private:
+ mozilla::net::NetAddr mAddr;
+
+ protected:
+ /* additional members */
+};
+
+#endif // !nsNetAddr_h__
diff --git a/netwerk/base/nsNetSegmentUtils.h b/netwerk/base/nsNetSegmentUtils.h
new file mode 100644
index 0000000000..a65defdb50
--- /dev/null
+++ b/netwerk/base/nsNetSegmentUtils.h
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetSegmentUtils_h__
+#define nsNetSegmentUtils_h__
+
+#include "nsIOService.h"
+
+/**
+ * applies defaults to segment params in a consistent way.
+ */
+static inline void net_ResolveSegmentParams(uint32_t& segsize,
+ uint32_t& segcount) {
+ if (!segsize) segsize = mozilla::net::nsIOService::gDefaultSegmentSize;
+
+ if (!segcount) segcount = mozilla::net::nsIOService::gDefaultSegmentCount;
+}
+
+#endif // !nsNetSegmentUtils_h__
diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp
new file mode 100644
index 0000000000..4c03cf63c3
--- /dev/null
+++ b/netwerk/base/nsNetUtil.cpp
@@ -0,0 +1,4088 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "DecoderDoctorDiagnostics.h"
+#include "HttpLog.h"
+
+#include "nsNetUtil.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Components.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Telemetry.h"
+#include "nsBufferedStreams.h"
+#include "nsCategoryCache.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsEscape.h"
+#include "nsFileStreams.h"
+#include "nsHashKeys.h"
+#include "nsHttp.h"
+#include "nsMimeTypes.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIAuthPromptAdapterFactory.h"
+#include "nsIBufferedStreams.h"
+#include "nsBufferedStreams.h"
+#include "nsIChannelEventSink.h"
+#include "nsIContentSniffer.h"
+#include "mozilla/dom/Document.h"
+#include "nsIDownloader.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsIFileStreams.h"
+#include "nsIFileURL.h"
+#include "nsIIDNService.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIInputStreamPump.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsINode.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsPersistentProperties.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIPropertyBag2.h"
+#include "nsIProtocolProxyService.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
+#include "nsRequestObserverProxy.h"
+#include "nsISensitiveInfoHiddenURI.h"
+#include "nsISimpleStreamListener.h"
+#include "nsISocketProvider.h"
+#include "nsIStandardURL.h"
+#include "nsIStreamLoader.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsStringStream.h"
+#include "nsSyncStreamListener.h"
+#include "nsITextToSubURI.h"
+#include "nsIURIWithSpecialOrigin.h"
+#include "nsIViewSourceChannel.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "nsINestedURI.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "nsIScriptError.h"
+#include "nsISiteSecurityService.h"
+#include "nsHttpHandler.h"
+#include "nsNSSComponent.h"
+#include "nsIRedirectHistoryEntry.h"
+#include "nsICertStorage.h"
+#include "nsICertOverrideService.h"
+#include "nsQueryObject.h"
+#include "mozIThirdPartyUtil.h"
+#include "../mime/nsMIMEHeaderParamImpl.h"
+#include "nsStandardURL.h"
+#include "DefaultURI.h"
+#include "nsChromeProtocolHandler.h"
+#include "nsJSProtocolHandler.h"
+#include "nsDataHandler.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "nsStreamUtils.h"
+#include "nsSocketTransportService2.h"
+#include "nsViewSourceHandler.h"
+#include "nsJARURI.h"
+#include "nsIconURI.h"
+#include "nsAboutProtocolHandler.h"
+#include "nsResProtocolHandler.h"
+#include "mozilla/net/ExtensionProtocolHandler.h"
+#include "mozilla/net/PageThumbProtocolHandler.h"
+#include "mozilla/net/SFVService.h"
+#include <limits>
+#include "nsIXPConnect.h"
+#include "nsParserConstants.h"
+#include "nsCRT.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/MediaList.h"
+#include "MediaContainerType.h"
+#include "DecoderTraits.h"
+#include "imgLoader.h"
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+# include "nsNewMailnewsURI.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::net;
+using mozilla::dom::BlobURLProtocolHandler;
+using mozilla::dom::ClientInfo;
+using mozilla::dom::PerformanceStorage;
+using mozilla::dom::ServiceWorkerDescriptor;
+
+#define MAX_RECURSION_COUNT 50
+
+already_AddRefed<nsIIOService> do_GetIOService(nsresult* error /* = 0 */) {
+ nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service();
+ if (error) *error = io ? NS_OK : NS_ERROR_FAILURE;
+ return io.forget();
+}
+
+nsresult NS_NewLocalFileInputStream(nsIInputStream** result, nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileInputStream> in =
+ do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = in->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) in.forget(result);
+ }
+ return rv;
+}
+
+Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewLocalFileInputStream(
+ nsIFile* file, int32_t ioFlags /* = -1 */, int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsCOMPtr<nsIInputStream> stream;
+ const nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file,
+ ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) {
+ return stream;
+ }
+ return Err(rv);
+}
+
+nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result, nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) out.forget(result);
+ }
+ return rv;
+}
+
+Result<nsCOMPtr<nsIOutputStream>, nsresult> NS_NewLocalFileOutputStream(
+ nsIFile* file, int32_t ioFlags /* = -1 */, int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsCOMPtr<nsIOutputStream> stream;
+ const nsresult rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), file,
+ ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) {
+ return stream;
+ }
+ return Err(rv);
+}
+
+nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result,
+ const mozilla::ipc::FileDescriptor& fd) {
+ nsCOMPtr<nsIFileOutputStream> out;
+ nsFileOutputStream::Create(NS_GET_IID(nsIFileOutputStream),
+ getter_AddRefs(out));
+
+ nsresult rv =
+ static_cast<nsFileOutputStream*>(out.get())->InitWithFileDescriptor(fd);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ out.forget(result);
+ return NS_OK;
+}
+
+nsresult net_EnsureIOService(nsIIOService** ios, nsCOMPtr<nsIIOService>& grip) {
+ nsresult rv = NS_OK;
+ if (!*ios) {
+ grip = do_GetIOService(&rv);
+ *ios = grip;
+ }
+ return rv;
+}
+
+nsresult NS_NewFileURI(
+ nsIURI** result, nsIFile* spec,
+ nsIIOService*
+ ioService /* = nullptr */) // pass in nsIIOService to optimize callers
+{
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService) rv = ioService->NewFileURI(spec, result);
+ return rv;
+}
+
+nsresult NS_GetURIWithNewRef(nsIURI* aInput, const nsACString& aRef,
+ nsIURI** aOutput) {
+ MOZ_DIAGNOSTIC_ASSERT(aRef.IsEmpty() || aRef[0] == '#');
+
+ if (NS_WARN_IF(!aInput || !aOutput)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool hasRef;
+ nsresult rv = aInput->GetHasRef(&hasRef);
+
+ nsAutoCString ref;
+ if (NS_SUCCEEDED(rv)) {
+ rv = aInput->GetRef(ref);
+ }
+
+ // If the ref is already equal to the new ref, we do not need to do anything.
+ // Also, if the GetRef failed (it could return NS_ERROR_NOT_IMPLEMENTED)
+ // we can assume SetRef would fail as well, so returning the original
+ // URI is OK.
+ //
+ // Note that aRef contains the hash, but ref doesn't, so need to account for
+ // that in the equality check.
+ if (NS_FAILED(rv) || (!hasRef && aRef.IsEmpty()) ||
+ (!aRef.IsEmpty() && hasRef &&
+ Substring(aRef.Data() + 1, aRef.Length() - 1) == ref)) {
+ nsCOMPtr<nsIURI> uri = aInput;
+ uri.forget(aOutput);
+ return NS_OK;
+ }
+
+ return NS_MutateURI(aInput).SetRef(aRef).Finalize(aOutput);
+}
+
+nsresult NS_GetURIWithoutRef(nsIURI* aInput, nsIURI** aOutput) {
+ return NS_GetURIWithNewRef(aInput, ""_ns, aOutput);
+}
+
+nsresult NS_NewChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, nsILoadInfo* aLoadInfo,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */) {
+ // NS_NewChannelInternal is mostly called for channel redirects. We should
+ // allow the creation of a channel even if the original channel did not have a
+ // loadinfo attached.
+ NS_ENSURE_ARG_POINTER(outChannel);
+
+ nsCOMPtr<nsIIOService> grip;
+ nsresult rv = net_EnsureIOService(&aIoService, grip);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = aIoService->NewChannelFromURIWithLoadInfo(aUri, aLoadInfo,
+ getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLoadGroup) {
+ rv = channel->SetLoadGroup(aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCallbacks) {
+ rv = channel->SetNotificationCallbacks(aCallbacks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+#ifdef DEBUG
+ nsLoadFlags channelLoadFlags = 0;
+ channel->GetLoadFlags(&channelLoadFlags);
+ // Will be removed when we remove LOAD_REPLACE altogether
+ // This check is trying to catch protocol handlers that still
+ // try to set the LOAD_REPLACE flag.
+ MOZ_DIAGNOSTIC_ASSERT(!(channelLoadFlags & nsIChannel::LOAD_REPLACE));
+#endif
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ rv = channel->SetLoadFlags(aLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aPerformanceStorage) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ loadInfo->SetPerformanceStorage(aPerformanceStorage);
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+namespace {
+
+void AssertLoadingPrincipalAndClientInfoMatch(
+ nsIPrincipal* aLoadingPrincipal, const ClientInfo& aLoadingClientInfo,
+ nsContentPolicyType aType) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // Verify that the provided loading ClientInfo matches the loading
+ // principal. Unfortunately we can't just use nsIPrincipal::Equals() here
+ // because of some corner cases:
+ //
+ // 1. Worker debugger scripts want to use a system loading principal for
+ // worker scripts with a content principal. We exempt these from this
+ // check.
+ // 2. Null principals currently require exact object identity for
+ // nsIPrincipal::Equals() to return true. This doesn't work here because
+ // ClientInfo::GetPrincipal() uses PrincipalInfoToPrincipal() to allocate
+ // a new object. To work around this we compare the principal origin
+ // string itself. If bug 1431771 is fixed then we could switch to
+ // Equals().
+
+ // Allow worker debugger to load with a system principal.
+ if (aLoadingPrincipal->IsSystemPrincipal() &&
+ (aType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE)) {
+ return;
+ }
+
+ // Perform a fast comparison for most principal checks.
+ auto clientPrincipalOrErr(aLoadingClientInfo.GetPrincipal());
+ if (clientPrincipalOrErr.isOk()) {
+ nsCOMPtr<nsIPrincipal> clientPrincipal = clientPrincipalOrErr.unwrap();
+ if (aLoadingPrincipal->Equals(clientPrincipal)) {
+ return;
+ }
+ // Fall back to a slower origin equality test to support null principals.
+ nsAutoCString loadingOriginNoSuffix;
+ MOZ_ALWAYS_SUCCEEDS(
+ aLoadingPrincipal->GetOriginNoSuffix(loadingOriginNoSuffix));
+
+ nsAutoCString clientOriginNoSuffix;
+ MOZ_ALWAYS_SUCCEEDS(
+ clientPrincipal->GetOriginNoSuffix(clientOriginNoSuffix));
+
+ // The client principal will have the partitionKey set if it's in a third
+ // party context, but the loading principal won't. So, we ignore he
+ // partitionKey when doing the verification here.
+ MOZ_DIAGNOSTIC_ASSERT(loadingOriginNoSuffix == clientOriginNoSuffix);
+ MOZ_DIAGNOSTIC_ASSERT(
+ aLoadingPrincipal->OriginAttributesRef().EqualsIgnoringPartitionKey(
+ clientPrincipal->OriginAttributesRef()));
+ }
+#endif
+}
+
+} // namespace
+
+nsresult NS_NewChannel(nsIChannel** outChannel, nsIURI* aUri,
+ nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings /* = nullptr */,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */,
+ uint32_t aSandboxFlags /* = 0 */,
+ bool aSkipCheckForBrokenURLOrZeroSized /* = false */) {
+ return NS_NewChannelInternal(
+ outChannel, aUri,
+ nullptr, // aLoadingNode,
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), aSecurityFlags,
+ aContentPolicyType, aCookieJarSettings, aPerformanceStorage, aLoadGroup,
+ aCallbacks, aLoadFlags, aIoService, aSandboxFlags,
+ aSkipCheckForBrokenURLOrZeroSized);
+}
+
+nsresult NS_NewChannel(nsIChannel** outChannel, nsIURI* aUri,
+ nsIPrincipal* aLoadingPrincipal,
+ const ClientInfo& aLoadingClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings /* = nullptr */,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */,
+ uint32_t aSandboxFlags /* = 0 */,
+ bool aSkipCheckForBrokenURLOrZeroSized /* = false */) {
+ AssertLoadingPrincipalAndClientInfoMatch(
+ aLoadingPrincipal, aLoadingClientInfo, aContentPolicyType);
+
+ Maybe<ClientInfo> loadingClientInfo;
+ loadingClientInfo.emplace(aLoadingClientInfo);
+
+ return NS_NewChannelInternal(
+ outChannel, aUri,
+ nullptr, // aLoadingNode,
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ loadingClientInfo, aController, aSecurityFlags, aContentPolicyType,
+ aCookieJarSettings, aPerformanceStorage, aLoadGroup, aCallbacks,
+ aLoadFlags, aIoService, aSandboxFlags, aSkipCheckForBrokenURLOrZeroSized);
+}
+
+nsresult NS_NewChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ const Maybe<ClientInfo>& aLoadingClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings /* = nullptr */,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */, uint32_t aSandboxFlags /* = 0 */,
+ bool aSkipCheckForBrokenURLOrZeroSized /* = false */) {
+ NS_ENSURE_ARG_POINTER(outChannel);
+
+ nsCOMPtr<nsIIOService> grip;
+ nsresult rv = net_EnsureIOService(&aIoService, grip);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = aIoService->NewChannelFromURIWithClientAndController(
+ aUri, aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal,
+ aLoadingClientInfo, aController, aSecurityFlags, aContentPolicyType,
+ aSandboxFlags, aSkipCheckForBrokenURLOrZeroSized,
+ getter_AddRefs(channel));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aLoadGroup) {
+ rv = channel->SetLoadGroup(aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCallbacks) {
+ rv = channel->SetNotificationCallbacks(aCallbacks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+#ifdef DEBUG
+ nsLoadFlags channelLoadFlags = 0;
+ channel->GetLoadFlags(&channelLoadFlags);
+ // Will be removed when we remove LOAD_REPLACE altogether
+ // This check is trying to catch protocol handlers that still
+ // try to set the LOAD_REPLACE flag.
+ MOZ_DIAGNOSTIC_ASSERT(!(channelLoadFlags & nsIChannel::LOAD_REPLACE));
+#endif
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ rv = channel->SetLoadFlags(aLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aPerformanceStorage || aCookieJarSettings) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+
+ if (aPerformanceStorage) {
+ loadInfo->SetPerformanceStorage(aPerformanceStorage);
+ }
+
+ if (aCookieJarSettings) {
+ loadInfo->SetCookieJarSettings(aCookieJarSettings);
+ }
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode,
+ nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */) {
+ MOZ_ASSERT(aLoadingNode);
+ NS_ASSERTION(aTriggeringPrincipal,
+ "Can not create channel without a triggering Principal!");
+ return NS_NewChannelInternal(
+ outChannel, aUri, aLoadingNode, aLoadingNode->NodePrincipal(),
+ aTriggeringPrincipal, Maybe<ClientInfo>(),
+ Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, aContentPolicyType,
+ aLoadingNode->OwnerDoc()->CookieJarSettings(), aPerformanceStorage,
+ aLoadGroup, aCallbacks, aLoadFlags, aIoService);
+}
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings /* = nullptr */,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */) {
+ NS_ASSERTION(aLoadingPrincipal,
+ "Can not create channel without a loading Principal!");
+ return NS_NewChannelInternal(
+ outChannel, aUri,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal, aTriggeringPrincipal, Maybe<ClientInfo>(),
+ Maybe<ServiceWorkerDescriptor>(), aSecurityFlags, aContentPolicyType,
+ aCookieJarSettings, aPerformanceStorage, aLoadGroup, aCallbacks,
+ aLoadFlags, aIoService);
+}
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal, const ClientInfo& aLoadingClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings /* = nullptr */,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */) {
+ AssertLoadingPrincipalAndClientInfoMatch(
+ aLoadingPrincipal, aLoadingClientInfo, aContentPolicyType);
+
+ Maybe<ClientInfo> loadingClientInfo;
+ loadingClientInfo.emplace(aLoadingClientInfo);
+
+ return NS_NewChannelInternal(
+ outChannel, aUri,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal, aTriggeringPrincipal, loadingClientInfo, aController,
+ aSecurityFlags, aContentPolicyType, aCookieJarSettings,
+ aPerformanceStorage, aLoadGroup, aCallbacks, aLoadFlags, aIoService);
+}
+
+nsresult NS_NewChannel(nsIChannel** outChannel, nsIURI* aUri,
+ nsINode* aLoadingNode, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ PerformanceStorage* aPerformanceStorage /* = nullptr */,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
+ nsIIOService* aIoService /* = nullptr */,
+ uint32_t aSandboxFlags /* = 0 */,
+ bool aSkipCheckForBrokenURLOrZeroSized /* = false */) {
+ NS_ASSERTION(aLoadingNode, "Can not create channel without a loading Node!");
+ return NS_NewChannelInternal(
+ outChannel, aUri, aLoadingNode, aLoadingNode->NodePrincipal(),
+ nullptr, // aTriggeringPrincipal
+ Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), aSecurityFlags,
+ aContentPolicyType, aLoadingNode->OwnerDoc()->CookieJarSettings(),
+ aPerformanceStorage, aLoadGroup, aCallbacks, aLoadFlags, aIoService,
+ aSandboxFlags, aSkipCheckForBrokenURLOrZeroSized);
+}
+
+nsresult NS_GetIsDocumentChannel(nsIChannel* aChannel, bool* aIsDocument) {
+ // Check if this channel is going to be used to create a document. If it has
+ // LOAD_DOCUMENT_URI set it is trivially creating a document. If
+ // LOAD_HTML_OBJECT_DATA is set it may or may not be used to create a
+ // document, depending on its MIME type.
+
+ if (!aChannel || !aIsDocument) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aIsDocument = false;
+ nsLoadFlags loadFlags;
+ nsresult rv = aChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
+ *aIsDocument = true;
+ return NS_OK;
+ }
+ if (!(loadFlags & nsIRequest::LOAD_HTML_OBJECT_DATA)) {
+ *aIsDocument = false;
+ return NS_OK;
+ }
+ nsAutoCString mimeType;
+ rv = aChannel->GetContentType(mimeType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (nsContentUtils::HtmlObjectContentTypeForMIMEType(mimeType) ==
+ nsIObjectLoadingContent::TYPE_DOCUMENT) {
+ *aIsDocument = true;
+ return NS_OK;
+ }
+ *aIsDocument = false;
+ return NS_OK;
+}
+
+nsresult NS_MakeAbsoluteURI(nsACString& result, const nsACString& spec,
+ nsIURI* baseURI) {
+ nsresult rv;
+ if (!baseURI) {
+ NS_WARNING("It doesn't make sense to not supply a base URI");
+ result = spec;
+ rv = NS_OK;
+ } else if (spec.IsEmpty()) {
+ rv = baseURI->GetSpec(result);
+ } else {
+ rv = baseURI->Resolve(spec, result);
+ }
+ return rv;
+}
+
+nsresult NS_MakeAbsoluteURI(char** result, const char* spec, nsIURI* baseURI) {
+ nsresult rv;
+ nsAutoCString resultBuf;
+ rv = NS_MakeAbsoluteURI(resultBuf, nsDependentCString(spec), baseURI);
+ if (NS_SUCCEEDED(rv)) {
+ *result = ToNewCString(resultBuf, mozilla::fallible);
+ if (!*result) rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return rv;
+}
+
+nsresult NS_MakeAbsoluteURI(nsAString& result, const nsAString& spec,
+ nsIURI* baseURI) {
+ nsresult rv;
+ if (!baseURI) {
+ NS_WARNING("It doesn't make sense to not supply a base URI");
+ result = spec;
+ rv = NS_OK;
+ } else {
+ nsAutoCString resultBuf;
+ if (spec.IsEmpty()) {
+ rv = baseURI->GetSpec(resultBuf);
+ } else {
+ rv = baseURI->Resolve(NS_ConvertUTF16toUTF8(spec), resultBuf);
+ }
+ if (NS_SUCCEEDED(rv)) CopyUTF8toUTF16(resultBuf, result);
+ }
+ return rv;
+}
+
+int32_t NS_GetDefaultPort(const char* scheme,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+
+ // Getting the default port through the protocol handler previously had a lot
+ // of XPCOM overhead involved. We optimize the protocols that matter for Web
+ // pages (HTTP and HTTPS) by hardcoding their default ports here.
+ //
+ // XXX: This might not be necessary for performance anymore.
+ if (strncmp(scheme, "http", 4) == 0) {
+ if (scheme[4] == 's' && scheme[5] == '\0') {
+ return 443;
+ }
+ if (scheme[4] == '\0') {
+ return 80;
+ }
+ }
+
+ nsCOMPtr<nsIIOService> grip;
+ net_EnsureIOService(&ioService, grip);
+ if (!ioService) return -1;
+
+ int32_t port;
+ rv = ioService->GetDefaultPort(scheme, &port);
+ return NS_SUCCEEDED(rv) ? port : -1;
+}
+
+/**
+ * This function is a helper function to apply the ToAscii conversion
+ * to a string
+ */
+bool NS_StringToACE(const nsACString& idn, nsACString& result) {
+ nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (!idnSrv) return false;
+ nsresult rv = idnSrv->ConvertUTF8toACE(idn, result);
+ return NS_SUCCEEDED(rv);
+}
+
+int32_t NS_GetRealPort(nsIURI* aURI) {
+ int32_t port;
+ nsresult rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv)) return -1;
+
+ if (port != -1) return port; // explicitly specified
+
+ // Otherwise, we have to get the default port from the protocol handler
+
+ // Need the scheme first
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return -1;
+
+ return NS_GetDefaultPort(scheme.get());
+}
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType,
+ const nsACString& aContentCharset, nsILoadInfo* aLoadInfo) {
+ nsresult rv;
+ nsCOMPtr<nsIInputStreamChannel> isc =
+ do_CreateInstance(NS_INPUTSTREAMCHANNEL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = isc->SetURI(aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+ rv = isc->SetContentStream(stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(isc, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aContentType.IsEmpty()) {
+ rv = channel->SetContentType(aContentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aContentCharset.IsEmpty()) {
+ rv = channel->SetContentCharset(aContentCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_ASSERT(aLoadInfo, "need a loadinfo to create a inputstreamchannel");
+ channel->SetLoadInfo(aLoadInfo);
+
+ // If we're sandboxed, make sure to clear any owner the channel
+ // might already have.
+ if (aLoadInfo && aLoadInfo->GetLoadingSandboxed()) {
+ channel->SetOwner(nullptr);
+ }
+
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType,
+ const nsACString& aContentCharset, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType) {
+ nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
+ aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags,
+ aContentPolicyType);
+ if (!loadInfo) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+
+ return NS_NewInputStreamChannelInternal(outChannel, aUri, stream.forget(),
+ aContentType, aContentCharset,
+ loadInfo);
+}
+
+nsresult NS_NewInputStreamChannel(
+ nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream, nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ const nsACString& aContentType /* = ""_ns */,
+ const nsACString& aContentCharset /* = ""_ns */) {
+ nsCOMPtr<nsIInputStream> stream = aStream;
+ return NS_NewInputStreamChannelInternal(outChannel, aUri, stream.forget(),
+ aContentType, aContentCharset,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags, aContentPolicyType);
+}
+
+nsresult NS_NewInputStreamChannelInternal(nsIChannel** outChannel, nsIURI* aUri,
+ const nsAString& aData,
+ const nsACString& aContentType,
+ nsILoadInfo* aLoadInfo,
+ bool aIsSrcdocChannel /* = false */) {
+ nsresult rv;
+ nsCOMPtr<nsIStringInputStream> stream;
+ stream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t len;
+ char* utf8Bytes = ToNewUTF8String(aData, &len);
+ rv = stream->AdoptData(utf8Bytes, len);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aUri,
+ stream.forget(), aContentType,
+ "UTF-8"_ns, aLoadInfo);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aIsSrcdocChannel) {
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(channel);
+ NS_ENSURE_TRUE(inStrmChan, NS_ERROR_FAILURE);
+ inStrmChan->SetSrcdocData(aData);
+ }
+ channel.forget(outChannel);
+ return NS_OK;
+}
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, const nsAString& aData,
+ const nsACString& aContentType, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel /* = false */) {
+ nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
+ aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags,
+ aContentPolicyType);
+ return NS_NewInputStreamChannelInternal(outChannel, aUri, aData, aContentType,
+ loadInfo, aIsSrcdocChannel);
+}
+
+nsresult NS_NewInputStreamChannel(nsIChannel** outChannel, nsIURI* aUri,
+ const nsAString& aData,
+ const nsACString& aContentType,
+ nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel /* = false */) {
+ return NS_NewInputStreamChannelInternal(outChannel, aUri, aData, aContentType,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ aSecurityFlags, aContentPolicyType,
+ aIsSrcdocChannel);
+}
+
+nsresult NS_NewInputStreamPump(
+ nsIInputStreamPump** aResult, already_AddRefed<nsIInputStream> aStream,
+ uint32_t aSegsize /* = 0 */, uint32_t aSegcount /* = 0 */,
+ bool aCloseWhenDone /* = false */,
+ nsISerialEventTarget* aMainThreadTarget /* = nullptr */) {
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+
+ nsresult rv;
+ nsCOMPtr<nsIInputStreamPump> pump =
+ do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = pump->Init(stream, aSegsize, aSegcount, aCloseWhenDone,
+ aMainThreadTarget);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = nullptr;
+ pump.swap(*aResult);
+ }
+ }
+ return rv;
+}
+
+nsresult NS_NewLoadGroup(nsILoadGroup** result, nsIRequestObserver* obs) {
+ nsresult rv;
+ nsCOMPtr<nsILoadGroup> group =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = group->SetGroupObserver(obs);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ group.swap(*result);
+ }
+ }
+ return rv;
+}
+
+bool NS_IsReasonableHTTPHeaderValue(const nsACString& aValue) {
+ return mozilla::net::nsHttp::IsReasonableHeaderValue(aValue);
+}
+
+bool NS_IsValidHTTPToken(const nsACString& aToken) {
+ return mozilla::net::nsHttp::IsValidToken(aToken);
+}
+
+void NS_TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest) {
+ mozilla::net::nsHttp::TrimHTTPWhitespace(aSource, aDest);
+}
+
+nsresult NS_NewLoadGroup(nsILoadGroup** aResult, nsIPrincipal* aPrincipal) {
+ using mozilla::LoadContext;
+ nsresult rv;
+
+ nsCOMPtr<nsILoadGroup> group =
+ do_CreateInstance(NS_LOADGROUP_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<LoadContext> loadContext = new LoadContext(aPrincipal);
+ rv = group->SetNotificationCallbacks(loadContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ group.forget(aResult);
+ return rv;
+}
+
+bool NS_LoadGroupMatchesPrincipal(nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal) {
+ if (!aPrincipal) {
+ return false;
+ }
+
+ // If this is a null principal then the load group doesn't really matter.
+ // The principal will not be allowed to perform any actions that actually
+ // use the load group. Unconditionally treat null principals as a match.
+ if (aPrincipal->GetIsNullPrincipal()) {
+ return true;
+ }
+
+ if (!aLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(nullptr, aLoadGroup, NS_GET_IID(nsILoadContext),
+ getter_AddRefs(loadContext));
+ NS_ENSURE_TRUE(loadContext, false);
+
+ return true;
+}
+
+nsresult NS_NewDownloader(nsIStreamListener** result,
+ nsIDownloadObserver* observer,
+ nsIFile* downloadLocation /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIDownloader> downloader =
+ do_CreateInstance(NS_DOWNLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = downloader->Init(observer, downloadLocation);
+ if (NS_SUCCEEDED(rv)) {
+ downloader.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult NS_NewIncrementalStreamLoader(
+ nsIIncrementalStreamLoader** result,
+ nsIIncrementalStreamLoaderObserver* observer) {
+ nsresult rv;
+ nsCOMPtr<nsIIncrementalStreamLoader> loader =
+ do_CreateInstance(NS_INCREMENTALSTREAMLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = loader->Init(observer);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ loader.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult NS_NewStreamLoader(
+ nsIStreamLoader** result, nsIStreamLoaderObserver* observer,
+ nsIRequestObserver* requestObserver /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIStreamLoader> loader =
+ do_CreateInstance(NS_STREAMLOADER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = loader->Init(observer, requestObserver);
+ if (NS_SUCCEEDED(rv)) {
+ *result = nullptr;
+ loader.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult NS_NewStreamLoaderInternal(
+ nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */) {
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannelInternal(
+ getter_AddRefs(channel), aUri, aLoadingNode, aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), aSecurityFlags,
+ aContentPolicyType,
+ nullptr, // nsICookieJarSettings
+ nullptr, // PerformanceStorage
+ aLoadGroup, aCallbacks, aLoadFlags);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewStreamLoader(outStream, aObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return channel->AsyncOpen(*outStream);
+}
+
+nsresult NS_NewStreamLoader(
+ nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver, nsINode* aLoadingNode,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */) {
+ NS_ASSERTION(aLoadingNode,
+ "Can not create stream loader without a loading Node!");
+ return NS_NewStreamLoaderInternal(
+ outStream, aUri, aObserver, aLoadingNode, aLoadingNode->NodePrincipal(),
+ aSecurityFlags, aContentPolicyType, aLoadGroup, aCallbacks, aLoadFlags);
+}
+
+nsresult NS_NewStreamLoader(
+ nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver, nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsILoadGroup* aLoadGroup /* = nullptr */,
+ nsIInterfaceRequestor* aCallbacks /* = nullptr */,
+ nsLoadFlags aLoadFlags /* = nsIRequest::LOAD_NORMAL */) {
+ return NS_NewStreamLoaderInternal(outStream, aUri, aObserver,
+ nullptr, // aLoadingNode
+ aLoadingPrincipal, aSecurityFlags,
+ aContentPolicyType, aLoadGroup, aCallbacks,
+ aLoadFlags);
+}
+
+nsresult NS_NewSyncStreamListener(nsIStreamListener** result,
+ nsIInputStream** stream) {
+ nsCOMPtr<nsISyncStreamListener> listener = new nsSyncStreamListener();
+ nsresult rv = listener->GetInputStream(stream);
+ if (NS_SUCCEEDED(rv)) {
+ listener.forget(result);
+ }
+ return rv;
+}
+
+nsresult NS_ImplementChannelOpen(nsIChannel* channel, nsIInputStream** result) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewSyncStreamListener(getter_AddRefs(listener),
+ getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = channel->AsyncOpen(listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t n;
+ // block until the initial response is received or an error occurs.
+ rv = stream->Available(&n);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *result = nullptr;
+ stream.swap(*result);
+
+ return NS_OK;
+}
+
+nsresult NS_NewRequestObserverProxy(nsIRequestObserver** result,
+ nsIRequestObserver* observer,
+ nsISupports* context) {
+ nsCOMPtr<nsIRequestObserverProxy> proxy = new nsRequestObserverProxy();
+ nsresult rv = proxy->Init(observer, context);
+ if (NS_SUCCEEDED(rv)) {
+ proxy.forget(result);
+ }
+ return rv;
+}
+
+nsresult NS_NewSimpleStreamListener(
+ nsIStreamListener** result, nsIOutputStream* sink,
+ nsIRequestObserver* observer /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsISimpleStreamListener> listener =
+ do_CreateInstance(NS_SIMPLESTREAMLISTENER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = listener->Init(sink, observer);
+ if (NS_SUCCEEDED(rv)) {
+ listener.forget(result);
+ }
+ }
+ return rv;
+}
+
+nsresult NS_CheckPortSafety(int32_t port, const char* scheme,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService) {
+ bool allow;
+ rv = ioService->AllowPort(port, scheme, &allow);
+ if (NS_SUCCEEDED(rv) && !allow) {
+ NS_WARNING("port blocked");
+ rv = NS_ERROR_PORT_ACCESS_NOT_ALLOWED;
+ }
+ }
+ return rv;
+}
+
+nsresult NS_CheckPortSafety(nsIURI* uri) {
+ int32_t port;
+ nsresult rv = uri->GetPort(&port);
+ if (NS_FAILED(rv) || port == -1) { // port undefined or default-valued
+ return NS_OK;
+ }
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ return NS_CheckPortSafety(port, scheme.get());
+}
+
+nsresult NS_NewProxyInfo(const nsACString& type, const nsACString& host,
+ int32_t port, uint32_t flags, nsIProxyInfo** result) {
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = pps->NewProxyInfo(type, host, port, ""_ns, ""_ns, flags, UINT32_MAX,
+ nullptr, result);
+ }
+ return rv;
+}
+
+nsresult NS_GetFileProtocolHandler(nsIFileProtocolHandler** result,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> grip;
+ rv = net_EnsureIOService(&ioService, grip);
+ if (ioService) {
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler("file", getter_AddRefs(handler));
+ if (NS_SUCCEEDED(rv)) rv = CallQueryInterface(handler, result);
+ }
+ return rv;
+}
+
+nsresult NS_GetFileFromURLSpec(const nsACString& inURL, nsIFile** result,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv)) rv = fileHandler->GetFileFromURLSpec(inURL, result);
+ return rv;
+}
+
+nsresult NS_GetURLSpecFromFile(nsIFile* file, nsACString& url,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv)) rv = fileHandler->GetURLSpecFromFile(file, url);
+ return rv;
+}
+
+nsresult NS_GetURLSpecFromActualFile(nsIFile* file, nsACString& url,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv)) rv = fileHandler->GetURLSpecFromActualFile(file, url);
+ return rv;
+}
+
+nsresult NS_GetURLSpecFromDir(nsIFile* file, nsACString& url,
+ nsIIOService* ioService /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler), ioService);
+ if (NS_SUCCEEDED(rv)) rv = fileHandler->GetURLSpecFromDir(file, url);
+ return rv;
+}
+
+void NS_GetReferrerFromChannel(nsIChannel* channel, nsIURI** referrer) {
+ *referrer = nullptr;
+
+ if (nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(channel)) {
+ // We have to check for a property on a property bag because the
+ // referrer may be empty for security reasons (for example, when loading
+ // an http page with an https referrer).
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri(
+ do_GetProperty(props, u"docshell.internalReferrer"_ns, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ uri.forget(referrer);
+ return;
+ }
+ }
+
+ // if that didn't work, we can still try to get the referrer from the
+ // nsIHttpChannel (if we can QI to it)
+ nsCOMPtr<nsIHttpChannel> chan(do_QueryInterface(channel));
+ if (!chan) {
+ return;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = chan->GetReferrerInfo();
+ if (!referrerInfo) {
+ return;
+ }
+
+ referrerInfo->GetOriginalReferrer(referrer);
+}
+
+already_AddRefed<nsINetUtil> do_GetNetUtil(nsresult* error /* = 0 */) {
+ nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service();
+ nsCOMPtr<nsINetUtil> util;
+ if (io) util = do_QueryInterface(io);
+
+ if (error) *error = !!util ? NS_OK : NS_ERROR_FAILURE;
+ return util.forget();
+}
+
+nsresult NS_ParseRequestContentType(const nsACString& rawContentType,
+ nsCString& contentType,
+ nsCString& contentCharset) {
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString charset;
+ bool hadCharset;
+ rv = util->ParseRequestContentType(rawContentType, charset, &hadCharset,
+ contentType);
+ if (NS_SUCCEEDED(rv) && hadCharset) contentCharset = charset;
+ return rv;
+}
+
+nsresult NS_ParseResponseContentType(const nsACString& rawContentType,
+ nsCString& contentType,
+ nsCString& contentCharset) {
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString charset;
+ bool hadCharset;
+ rv = util->ParseResponseContentType(rawContentType, charset, &hadCharset,
+ contentType);
+ if (NS_SUCCEEDED(rv) && hadCharset) contentCharset = charset;
+ return rv;
+}
+
+nsresult NS_ExtractCharsetFromContentType(const nsACString& rawContentType,
+ nsCString& contentCharset,
+ bool* hadCharset,
+ int32_t* charsetStart,
+ int32_t* charsetEnd) {
+ // contentCharset is left untouched if not present in rawContentType
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return util->ExtractCharsetFromContentType(
+ rawContentType, contentCharset, charsetStart, charsetEnd, hadCharset);
+}
+
+nsresult NS_NewAtomicFileOutputStream(nsIOutputStream** result, nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_ATOMICLOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) out.forget(result);
+ }
+ return rv;
+}
+
+nsresult NS_NewSafeLocalFileOutputStream(nsIOutputStream** result,
+ nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsresult rv;
+ nsCOMPtr<nsIFileOutputStream> out =
+ do_CreateInstance(NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) out.forget(result);
+ }
+ return rv;
+}
+
+nsresult NS_NewLocalFileRandomAccessStream(nsIRandomAccessStream** result,
+ nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsCOMPtr<nsIFileRandomAccessStream> stream = new nsFileRandomAccessStream();
+ nsresult rv = stream->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) {
+ stream.forget(result);
+ }
+ return rv;
+}
+
+mozilla::Result<nsCOMPtr<nsIRandomAccessStream>, nsresult>
+NS_NewLocalFileRandomAccessStream(nsIFile* file, int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsCOMPtr<nsIRandomAccessStream> stream;
+ const nsresult rv = NS_NewLocalFileRandomAccessStream(
+ getter_AddRefs(stream), file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) {
+ return stream;
+ }
+ return Err(rv);
+}
+
+nsresult NS_NewBufferedOutputStream(
+ nsIOutputStream** aResult, already_AddRefed<nsIOutputStream> aOutputStream,
+ uint32_t aBufferSize) {
+ nsCOMPtr<nsIOutputStream> outputStream = std::move(aOutputStream);
+
+ nsresult rv;
+ nsCOMPtr<nsIBufferedOutputStream> out =
+ do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = out->Init(outputStream, aBufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ out.forget(aResult);
+ }
+ }
+ return rv;
+}
+
+[[nodiscard]] nsresult NS_NewBufferedInputStream(
+ nsIInputStream** aResult, already_AddRefed<nsIInputStream> aInputStream,
+ uint32_t aBufferSize) {
+ nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream);
+
+ nsCOMPtr<nsIBufferedInputStream> in;
+ nsresult rv = nsBufferedInputStream::Create(
+ NS_GET_IID(nsIBufferedInputStream), getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv)) {
+ rv = in->Init(inputStream, aBufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = static_cast<nsBufferedInputStream*>(in.get())
+ ->GetInputStream()
+ .take();
+ }
+ }
+ return rv;
+}
+
+Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewBufferedInputStream(
+ already_AddRefed<nsIInputStream> aInputStream, uint32_t aBufferSize) {
+ nsCOMPtr<nsIInputStream> stream;
+ const nsresult rv = NS_NewBufferedInputStream(
+ getter_AddRefs(stream), std::move(aInputStream), aBufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ return stream;
+ }
+ return Err(rv);
+}
+
+namespace {
+
+#define BUFFER_SIZE 8192
+
+class BufferWriter final : public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ BufferWriter(nsIInputStream* aInputStream, void* aBuffer, int64_t aCount)
+ : mMonitor("BufferWriter.mMonitor"),
+ mInputStream(aInputStream),
+ mBuffer(aBuffer),
+ mCount(aCount),
+ mWrittenData(0),
+ mBufferType(aBuffer ? eExternal : eInternal),
+ mBufferSize(0) {
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(aCount == -1 || aCount > 0);
+ MOZ_ASSERT_IF(mBuffer, aCount > 0);
+ }
+
+ nsresult Write() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+
+ // Let's make the inputStream buffered if it's not.
+ if (!NS_InputStreamIsBuffered(mInputStream)) {
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ nsresult rv = NS_NewBufferedInputStream(
+ getter_AddRefs(bufferedStream), mInputStream.forget(), BUFFER_SIZE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInputStream = bufferedStream;
+ }
+
+ mAsyncInputStream = do_QueryInterface(mInputStream);
+
+ if (!mAsyncInputStream) {
+ return WriteSync();
+ }
+
+ // Let's use mAsyncInputStream only.
+ mInputStream = nullptr;
+
+ return WriteAsync();
+ }
+
+ uint64_t WrittenData() const {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+ return mWrittenData;
+ }
+
+ void* StealBuffer() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+ MOZ_ASSERT(mBufferType == eInternal);
+
+ void* buffer = mBuffer;
+
+ mBuffer = nullptr;
+ mBufferSize = 0;
+
+ return buffer;
+ }
+
+ private:
+ ~BufferWriter() {
+ if (mBuffer && mBufferType == eInternal) {
+ free(mBuffer);
+ }
+
+ if (mTaskQueue) {
+ mTaskQueue->BeginShutdown();
+ }
+ }
+
+ nsresult WriteSync() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+
+ uint64_t length = (uint64_t)mCount;
+
+ if (mCount == -1) {
+ nsresult rv = mInputStream->Available(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (length == 0) {
+ // nothing to read.
+ return NS_OK;
+ }
+ }
+
+ if (mBufferType == eInternal) {
+ mBuffer = malloc(length);
+ if (NS_WARN_IF(!mBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ uint32_t writtenData;
+ nsresult rv = mInputStream->ReadSegments(NS_CopySegmentToBuffer, mBuffer,
+ length, &writtenData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mWrittenData = writtenData;
+ return NS_OK;
+ }
+
+ nsresult WriteAsync() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+
+ if (mCount > 0 && mBufferType == eInternal) {
+ mBuffer = malloc(mCount);
+ if (NS_WARN_IF(!mBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ while (true) {
+ if (mCount == -1 && !MaybeExpandBufferSize()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint64_t offset = mWrittenData;
+ uint64_t length = mCount == -1 ? BUFFER_SIZE : mCount;
+
+ // Let's try to read data directly.
+ uint32_t writtenData;
+ nsresult rv = mAsyncInputStream->ReadSegments(
+ NS_CopySegmentToBuffer, static_cast<char*>(mBuffer) + offset, length,
+ &writtenData);
+
+ // Operation completed. Nothing more to read.
+ if (NS_SUCCEEDED(rv) && writtenData == 0) {
+ return NS_OK;
+ }
+
+ // If we succeeded, let's try to read again.
+ if (NS_SUCCEEDED(rv)) {
+ mWrittenData += writtenData;
+ if (mCount != -1) {
+ MOZ_ASSERT(mCount >= writtenData);
+ mCount -= writtenData;
+
+ // Is this the end of the reading?
+ if (mCount == 0) {
+ return NS_OK;
+ }
+ }
+
+ continue;
+ }
+
+ // Async wait...
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = MaybeCreateTaskQueue();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MonitorAutoLock lock(mMonitor);
+
+ rv = mAsyncInputStream->AsyncWait(this, 0, length, mTaskQueue);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ lock.Wait();
+ continue;
+ }
+
+ // Otherwise, let's propagate the error.
+ return rv;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("We should not be here");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult MaybeCreateTaskQueue() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+
+ if (!mTaskQueue) {
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ if (!target) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mTaskQueue = TaskQueue::Create(target.forget(), "nsNetUtil:BufferWriter");
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnInputStreamReady(nsIAsyncInputStream* aStream) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // We have something to read. Let's unlock the main-thread.
+ MonitorAutoLock lock(mMonitor);
+ lock.Notify();
+ return NS_OK;
+ }
+
+ bool MaybeExpandBufferSize() {
+ NS_ASSERT_OWNINGTHREAD(BufferWriter);
+
+ MOZ_ASSERT(mCount == -1);
+
+ if (mBufferSize >= mWrittenData + BUFFER_SIZE) {
+ // The buffer is big enough.
+ return true;
+ }
+
+ CheckedUint32 bufferSize =
+ std::max<uint32_t>(static_cast<uint32_t>(mWrittenData), BUFFER_SIZE);
+ while (bufferSize.isValid() &&
+ bufferSize.value() < mWrittenData + BUFFER_SIZE) {
+ bufferSize *= 2;
+ }
+
+ if (!bufferSize.isValid()) {
+ return false;
+ }
+
+ void* buffer = realloc(mBuffer, bufferSize.value());
+ if (!buffer) {
+ return false;
+ }
+
+ mBuffer = buffer;
+ mBufferSize = bufferSize.value();
+ return true;
+ }
+
+ // All the members of this class are touched on the owning thread only. The
+ // monitor is only used to communicate when there is more data to read.
+ Monitor mMonitor MOZ_UNANNOTATED;
+
+ nsCOMPtr<nsIInputStream> mInputStream;
+ nsCOMPtr<nsIAsyncInputStream> mAsyncInputStream;
+
+ RefPtr<TaskQueue> mTaskQueue;
+
+ void* mBuffer;
+ int64_t mCount;
+ uint64_t mWrittenData;
+
+ enum {
+ // The buffer is allocated internally and this object must release it
+ // in the DTOR if not stolen. The buffer can be reallocated.
+ eInternal,
+
+ // The buffer is not owned by this object and it cannot be reallocated.
+ eExternal,
+ } mBufferType;
+
+ // The following set if needed for the async read.
+ uint64_t mBufferSize;
+};
+
+NS_IMPL_ISUPPORTS(BufferWriter, nsIInputStreamCallback)
+
+} // anonymous namespace
+
+nsresult NS_ReadInputStreamToBuffer(nsIInputStream* aInputStream, void** aDest,
+ int64_t aCount, uint64_t* aWritten) {
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(aCount >= -1);
+
+ uint64_t dummyWritten;
+ if (!aWritten) {
+ aWritten = &dummyWritten;
+ }
+
+ if (aCount == 0) {
+ *aWritten = 0;
+ return NS_OK;
+ }
+
+ // This will take care of allocating and reallocating aDest.
+ RefPtr<BufferWriter> writer = new BufferWriter(aInputStream, *aDest, aCount);
+
+ nsresult rv = writer->Write();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aWritten = writer->WrittenData();
+
+ if (!*aDest) {
+ *aDest = writer->StealBuffer();
+ }
+
+ return NS_OK;
+}
+
+nsresult NS_ReadInputStreamToString(nsIInputStream* aInputStream,
+ nsACString& aDest, int64_t aCount,
+ uint64_t* aWritten) {
+ uint64_t dummyWritten;
+ if (!aWritten) {
+ aWritten = &dummyWritten;
+ }
+
+ // Nothing to do if aCount is 0.
+ if (aCount == 0) {
+ aDest.Truncate();
+ *aWritten = 0;
+ return NS_OK;
+ }
+
+ // If we have the size, we can pre-allocate the buffer.
+ if (aCount > 0) {
+ if (NS_WARN_IF(aCount >= INT32_MAX) ||
+ NS_WARN_IF(!aDest.SetLength(aCount, mozilla::fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ void* dest = aDest.BeginWriting();
+ nsresult rv =
+ NS_ReadInputStreamToBuffer(aInputStream, &dest, aCount, aWritten);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((uint64_t)aCount > *aWritten) {
+ aDest.Truncate(*aWritten);
+ }
+
+ return NS_OK;
+ }
+
+ // If the size is unknown, BufferWriter will allocate the buffer.
+ void* dest = nullptr;
+ nsresult rv =
+ NS_ReadInputStreamToBuffer(aInputStream, &dest, aCount, aWritten);
+ MOZ_ASSERT_IF(NS_FAILED(rv), dest == nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!dest) {
+ MOZ_ASSERT(*aWritten == 0);
+ aDest.Truncate();
+ return NS_OK;
+ }
+
+ aDest.Adopt(reinterpret_cast<char*>(dest), *aWritten);
+ return NS_OK;
+}
+
+nsresult NS_NewURI(nsIURI** result, const nsACString& spec,
+ NotNull<const Encoding*> encoding,
+ nsIURI* baseURI /* = nullptr */) {
+ nsAutoCString charset;
+ encoding->Name(charset);
+ return NS_NewURI(result, spec, charset.get(), baseURI);
+}
+
+nsresult NS_NewURI(nsIURI** result, const nsAString& aSpec,
+ const char* charset /* = nullptr */,
+ nsIURI* baseURI /* = nullptr */) {
+ nsAutoCString spec;
+ if (!AppendUTF16toUTF8(aSpec, spec, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_NewURI(result, spec, charset, baseURI);
+}
+
+nsresult NS_NewURI(nsIURI** result, const nsAString& aSpec,
+ NotNull<const Encoding*> encoding,
+ nsIURI* baseURI /* = nullptr */) {
+ nsAutoCString spec;
+ if (!AppendUTF16toUTF8(aSpec, spec, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_NewURI(result, spec, encoding, baseURI);
+}
+
+nsresult NS_NewURI(nsIURI** result, const char* spec,
+ nsIURI* baseURI /* = nullptr */) {
+ return NS_NewURI(result, nsDependentCString(spec), nullptr, baseURI);
+}
+
+static nsresult NewStandardURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, int32_t aDefaultPort,
+ nsIURI** aURI) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_AUTHORITY,
+ aDefaultPort, aSpec, aCharset, aBaseURI, nullptr)
+ .Finalize(aURI);
+}
+
+nsresult NS_GetSpecWithNSURLEncoding(nsACString& aResult,
+ const nsACString& aSpec) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURIWithNSURLEncoding(getter_AddRefs(uri), aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return uri->GetAsciiSpec(aResult);
+}
+
+nsresult NS_NewURIWithNSURLEncoding(nsIURI** aResult, const nsACString& aSpec) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Escape the ref portion of the URL. NSURL is more strict about which
+ // characters in the URL must be % encoded. For example, an unescaped '#'
+ // to indicate the beginning of the ref component is accepted by NSURL, but
+ // '#' characters in the ref must be escaped. Also adds encoding for other
+ // characters not accepted by NSURL in the ref such as '{', '|', '}', and '^'.
+ // The ref returned from GetRef() does not include the leading '#'.
+ nsAutoCString ref, escapedRef;
+ if (NS_SUCCEEDED(uri->GetRef(ref)) && !ref.IsEmpty()) {
+ if (!NS_Escape(ref, escapedRef, url_NSURLRef)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = NS_MutateURI(uri).SetRef(escapedRef).Finalize(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uri.forget(aResult);
+ return NS_OK;
+}
+
+extern MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount;
+
+template <typename T>
+class TlsAutoIncrement {
+ public:
+ explicit TlsAutoIncrement(T& var) : mVar(var) {
+ mValue = mVar.get();
+ mVar.set(mValue + 1);
+ }
+ ~TlsAutoIncrement() {
+ typename T::Type value = mVar.get();
+ MOZ_ASSERT(value == mValue + 1);
+ mVar.set(value - 1);
+ }
+
+ typename T::Type value() { return mValue; }
+
+ private:
+ typename T::Type mValue;
+ T& mVar;
+};
+
+nsresult NS_NewURI(nsIURI** aURI, const nsACString& aSpec,
+ const char* aCharset /* = nullptr */,
+ nsIURI* aBaseURI /* = nullptr */) {
+ TlsAutoIncrement<decltype(gTlsURLRecursionCount)> inc(gTlsURLRecursionCount);
+ if (inc.value() >= MAX_RECURSION_COUNT) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService();
+ if (!ioService) {
+ // Individual protocol handlers unfortunately rely on the ioservice, let's
+ // return an error here instead of causing unpredictable crashes later.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (StaticPrefs::network_url_max_length() &&
+ aSpec.Length() > StaticPrefs::network_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString scheme;
+ nsresult rv = net_ExtractURLScheme(aSpec, scheme);
+ if (NS_FAILED(rv)) {
+ // then aSpec is relative
+ if (!aBaseURI) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (!aSpec.IsEmpty() && aSpec[0] == '#') {
+ // Looks like a reference instead of a fully-specified URI.
+ // --> initialize |uri| as a clone of |aBaseURI|, with ref appended.
+ return NS_GetURIWithNewRef(aBaseURI, aSpec, aURI);
+ }
+
+ rv = aBaseURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("ws")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, NS_HTTP_DEFAULT_PORT,
+ aURI);
+ }
+ if (scheme.EqualsLiteral("https") || scheme.EqualsLiteral("wss")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, NS_HTTPS_DEFAULT_PORT,
+ aURI);
+ }
+ if (scheme.EqualsLiteral("ftp")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, 21, aURI);
+ }
+
+ if (scheme.EqualsLiteral("file")) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(&nsIFileURLMutator::MarkFileURL)
+ .Apply(&nsIStandardURLMutator::Init,
+ nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, aSpec, aCharset,
+ aBaseURI, nullptr)
+ .Finalize(aURI);
+ }
+
+ if (scheme.EqualsLiteral("data")) {
+ return nsDataHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("moz-safe-about") ||
+ scheme.EqualsLiteral("page-icon") || scheme.EqualsLiteral("moz") ||
+ scheme.EqualsLiteral("cached-favicon")) {
+ return NS_MutateURI(new nsSimpleURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(aURI);
+ }
+
+ if (scheme.EqualsLiteral("chrome")) {
+ return nsChromeProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI,
+ aURI);
+ }
+
+ if (scheme.EqualsLiteral("javascript")) {
+ return nsJSProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("blob")) {
+ return BlobURLProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI,
+ aURI);
+ }
+
+ if (scheme.EqualsLiteral("view-source")) {
+ return nsViewSourceHandler::CreateNewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("resource")) {
+ RefPtr<nsResProtocolHandler> handler = nsResProtocolHandler::GetSingleton();
+ if (!handler) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return handler->NewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("indexeddb") || scheme.EqualsLiteral("uuid")) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_AUTHORITY,
+ 0, aSpec, aCharset, aBaseURI, nullptr)
+ .Finalize(aURI);
+ }
+
+ if (scheme.EqualsLiteral("moz-extension")) {
+ RefPtr<mozilla::net::ExtensionProtocolHandler> handler =
+ mozilla::net::ExtensionProtocolHandler::GetSingleton();
+ if (!handler) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return handler->NewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("moz-page-thumb")) {
+ // The moz-page-thumb service runs JS to resolve a URI to a
+ // storage location, so this should only ever run on the main
+ // thread.
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<mozilla::net::PageThumbProtocolHandler> handler =
+ mozilla::net::PageThumbProtocolHandler::GetSingleton();
+ if (!handler) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return handler->NewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+
+ if (scheme.EqualsLiteral("about")) {
+ return nsAboutProtocolHandler::CreateNewURI(aSpec, aCharset, aBaseURI,
+ aURI);
+ }
+
+ if (scheme.EqualsLiteral("jar")) {
+ return NS_MutateURI(new nsJARURI::Mutator())
+ .Apply(&nsIJARURIMutator::SetSpecBaseCharset, aSpec, aBaseURI, aCharset)
+ .Finalize(aURI);
+ }
+
+ if (scheme.EqualsLiteral("moz-icon")) {
+ return NS_MutateURI(new nsMozIconURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(aURI);
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ if (scheme.EqualsLiteral("smb") || scheme.EqualsLiteral("sftp")) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD,
+ -1, aSpec, aCharset, aBaseURI, nullptr)
+ .Finalize(aURI);
+ }
+#endif
+
+ if (scheme.EqualsLiteral("android")) {
+ return NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .Apply(&nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_STANDARD,
+ -1, aSpec, aCharset, aBaseURI, nullptr)
+ .Finalize(aURI);
+ }
+
+ // web-extensions can add custom protocol implementations with standard URLs
+ // that have notion of hostname, authority and relative URLs. Below we
+ // manually check agains set of known protocols schemes until more general
+ // solution is in place (See Bug 1569733)
+ if (!StaticPrefs::network_url_useDefaultURI()) {
+ if (scheme.EqualsLiteral("ssh")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, 22, aURI);
+ }
+
+ if (scheme.EqualsLiteral("dweb") || scheme.EqualsLiteral("dat") ||
+ scheme.EqualsLiteral("ipfs") || scheme.EqualsLiteral("ipns") ||
+ scheme.EqualsLiteral("ssb") || scheme.EqualsLiteral("wtp")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, -1, aURI);
+ }
+ }
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ rv = NS_NewMailnewsURI(aURI, aSpec, aCharset, aBaseURI);
+ if (rv != NS_ERROR_UNKNOWN_PROTOCOL) {
+ return rv;
+ }
+#endif
+
+ if (aBaseURI) {
+ nsAutoCString newSpec;
+ rv = aBaseURI->Resolve(aSpec, newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newScheme;
+ rv = net_ExtractURLScheme(newSpec, newScheme);
+ if (NS_SUCCEEDED(rv)) {
+ // The scheme shouldn't really change at this point.
+ MOZ_DIAGNOSTIC_ASSERT(newScheme == scheme);
+ }
+
+ if (StaticPrefs::network_url_useDefaultURI()) {
+ return NS_MutateURI(new DefaultURI::Mutator())
+ .SetSpec(newSpec)
+ .Finalize(aURI);
+ }
+
+ return NS_MutateURI(new nsSimpleURI::Mutator())
+ .SetSpec(newSpec)
+ .Finalize(aURI);
+ }
+
+ if (StaticPrefs::network_url_useDefaultURI()) {
+ return NS_MutateURI(new DefaultURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(aURI);
+ }
+
+ // Falls back to external protocol handler.
+ return NS_MutateURI(new nsSimpleURI::Mutator()).SetSpec(aSpec).Finalize(aURI);
+}
+
+nsresult NS_GetSanitizedURIStringFromURI(nsIURI* aUri,
+ nsAString& aSanitizedSpec) {
+ aSanitizedSpec.Truncate();
+
+ nsCOMPtr<nsISensitiveInfoHiddenURI> safeUri = do_QueryInterface(aUri);
+ nsAutoCString cSpec;
+ nsresult rv;
+ if (safeUri) {
+ rv = safeUri->GetSensitiveInfoHiddenSpec(cSpec);
+ } else {
+ rv = aUri->GetSpec(cSpec);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ aSanitizedSpec.Assign(NS_ConvertUTF8toUTF16(cSpec));
+ }
+ return rv;
+}
+
+nsresult NS_LoadPersistentPropertiesFromURISpec(
+ nsIPersistentProperties** outResult, const nsACString& aSpec) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStream> in;
+ rv = channel->Open(getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPersistentProperties> properties = new nsPersistentProperties();
+ rv = properties->Load(in);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ properties.swap(*outResult);
+ return NS_OK;
+}
+
+bool NS_UsePrivateBrowsing(nsIChannel* channel) {
+ OriginAttributes attrs;
+ bool result = StoragePrincipalHelper::GetOriginAttributes(
+ channel, attrs, StoragePrincipalHelper::eRegularPrincipal);
+ NS_ENSURE_TRUE(result, result);
+ return attrs.mPrivateBrowsingId > 0;
+}
+
+bool NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ // TYPE_DOCUMENT loads have a null LoadingPrincipal and can not be cross
+ // origin.
+ if (!loadInfo->GetLoadingPrincipal()) {
+ return false;
+ }
+
+ // Always treat tainted channels as cross-origin.
+ if (loadInfo->GetTainting() != LoadTainting::Basic) {
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->GetLoadingPrincipal();
+ uint32_t mode = loadInfo->GetSecurityMode();
+ bool dataInherits =
+ mode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT ||
+ mode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT ||
+ mode == nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
+
+ bool aboutBlankInherits = dataInherits && loadInfo->GetAboutBlankInherits();
+
+ uint64_t innerWindowID = loadInfo->GetInnerWindowID();
+
+ for (nsIRedirectHistoryEntry* redirectHistoryEntry :
+ loadInfo->RedirectChain()) {
+ nsCOMPtr<nsIPrincipal> principal;
+ redirectHistoryEntry->GetPrincipal(getter_AddRefs(principal));
+ if (!principal) {
+ return true;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ auto* basePrin = BasePrincipal::Cast(principal);
+ basePrin->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ return true;
+ }
+
+ if (aboutBlankInherits && NS_IsAboutBlank(uri)) {
+ continue;
+ }
+
+ nsresult res;
+ if (aReport) {
+ res = loadingPrincipal->CheckMayLoadWithReporting(uri, dataInherits,
+ innerWindowID);
+ } else {
+ res = loadingPrincipal->CheckMayLoad(uri, dataInherits);
+ }
+ if (NS_FAILED(res)) {
+ return true;
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ if (!uri) {
+ return true;
+ }
+
+ if (aboutBlankInherits && NS_IsAboutBlank(uri)) {
+ return false;
+ }
+
+ nsresult res;
+ if (aReport) {
+ res = loadingPrincipal->CheckMayLoadWithReporting(uri, dataInherits,
+ innerWindowID);
+ } else {
+ res = loadingPrincipal->CheckMayLoad(uri, dataInherits);
+ }
+
+ return NS_FAILED(res);
+}
+
+bool NS_IsSafeMethodNav(nsIChannel* aChannel) {
+ RefPtr<HttpBaseChannel> baseChan = do_QueryObject(aChannel);
+ if (!baseChan) {
+ return false;
+ }
+ nsHttpRequestHead* requestHead = baseChan->GetRequestHead();
+ if (!requestHead) {
+ return false;
+ }
+ return requestHead->IsSafeMethod();
+}
+
+void NS_WrapAuthPrompt(nsIAuthPrompt* aAuthPrompt,
+ nsIAuthPrompt2** aAuthPrompt2) {
+ nsCOMPtr<nsIAuthPromptAdapterFactory> factory =
+ do_GetService(NS_AUTHPROMPT_ADAPTER_FACTORY_CONTRACTID);
+ if (!factory) return;
+
+ NS_WARNING("Using deprecated nsIAuthPrompt");
+ factory->CreateAdapter(aAuthPrompt, aAuthPrompt2);
+}
+
+void NS_QueryAuthPrompt2(nsIInterfaceRequestor* aCallbacks,
+ nsIAuthPrompt2** aAuthPrompt) {
+ CallGetInterface(aCallbacks, aAuthPrompt);
+ if (*aAuthPrompt) return;
+
+ // Maybe only nsIAuthPrompt is provided and we have to wrap it.
+ nsCOMPtr<nsIAuthPrompt> prompt(do_GetInterface(aCallbacks));
+ if (!prompt) return;
+
+ NS_WrapAuthPrompt(prompt, aAuthPrompt);
+}
+
+void NS_QueryAuthPrompt2(nsIChannel* aChannel, nsIAuthPrompt2** aAuthPrompt) {
+ *aAuthPrompt = nullptr;
+
+ // We want to use any auth prompt we can find on the channel's callbacks,
+ // and if that fails use the loadgroup's prompt (if any)
+ // Therefore, we can't just use NS_QueryNotificationCallbacks, because
+ // that would prefer a loadgroup's nsIAuthPrompt2 over a channel's
+ // nsIAuthPrompt.
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ NS_QueryAuthPrompt2(callbacks, aAuthPrompt);
+ if (*aAuthPrompt) return;
+ }
+
+ nsCOMPtr<nsILoadGroup> group;
+ aChannel->GetLoadGroup(getter_AddRefs(group));
+ if (!group) return;
+
+ group->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (!callbacks) return;
+ NS_QueryAuthPrompt2(callbacks, aAuthPrompt);
+}
+
+nsresult NS_NewNotificationCallbacksAggregation(
+ nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup,
+ nsIEventTarget* target, nsIInterfaceRequestor** result) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ if (loadGroup) loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ return NS_NewInterfaceRequestorAggregation(callbacks, cbs, target, result);
+}
+
+nsresult NS_NewNotificationCallbacksAggregation(
+ nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup,
+ nsIInterfaceRequestor** result) {
+ return NS_NewNotificationCallbacksAggregation(callbacks, loadGroup, nullptr,
+ result);
+}
+
+nsresult NS_DoImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result) {
+ MOZ_ASSERT(nestedURI, "Must have a nested URI!");
+ MOZ_ASSERT(!*result, "Must have null *result");
+
+ nsCOMPtr<nsIURI> inner;
+ nsresult rv = nestedURI->GetInnerURI(getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We may need to loop here until we reach the innermost
+ // URI.
+ nsCOMPtr<nsINestedURI> nestedInner(do_QueryInterface(inner));
+ while (nestedInner) {
+ rv = nestedInner->GetInnerURI(getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nestedInner = do_QueryInterface(inner);
+ }
+
+ // Found the innermost one if we reach here.
+ inner.swap(*result);
+
+ return rv;
+}
+
+nsresult NS_ImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result) {
+ // Make it safe to use swap()
+ *result = nullptr;
+
+ return NS_DoImplGetInnermostURI(nestedURI, result);
+}
+
+already_AddRefed<nsIURI> NS_GetInnermostURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI, "Must have URI");
+
+ nsCOMPtr<nsIURI> uri = aURI;
+
+ nsCOMPtr<nsINestedURI> nestedURI(do_QueryInterface(uri));
+ if (!nestedURI) {
+ return uri.forget();
+ }
+
+ nsresult rv = nestedURI->GetInnermostURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+nsresult NS_GetFinalChannelURI(nsIChannel* channel, nsIURI** uri) {
+ *uri = nullptr;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+ if (resultPrincipalURI) {
+ resultPrincipalURI.forget(uri);
+ return NS_OK;
+ }
+ return channel->GetOriginalURI(uri);
+}
+
+nsresult NS_URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return util->URIChainHasFlags(uri, flags, result);
+}
+
+uint32_t NS_SecurityHashURI(nsIURI* aURI) {
+ nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
+
+ nsAutoCString scheme;
+ uint32_t schemeHash = 0;
+ if (NS_SUCCEEDED(baseURI->GetScheme(scheme))) {
+ schemeHash = mozilla::HashString(scheme);
+ }
+
+ // TODO figure out how to hash file:// URIs
+ if (scheme.EqualsLiteral("file")) return schemeHash; // sad face
+
+#if IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ bool hasFlag;
+ if (NS_FAILED(NS_URIChainHasFlags(
+ baseURI, nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, &hasFlag)) ||
+ hasFlag) {
+ nsAutoCString spec;
+ uint32_t specHash;
+ nsresult res = baseURI->GetSpec(spec);
+ if (NS_SUCCEEDED(res))
+ specHash = mozilla::HashString(spec);
+ else
+ specHash = static_cast<uint32_t>(res);
+ return specHash;
+ }
+#endif
+
+ nsAutoCString host;
+ uint32_t hostHash = 0;
+ if (NS_SUCCEEDED(baseURI->GetAsciiHost(host))) {
+ hostHash = mozilla::HashString(host);
+ }
+
+ return mozilla::AddToHash(schemeHash, hostHash, NS_GetRealPort(baseURI));
+}
+
+bool NS_SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI,
+ bool aStrictFileOriginPolicy) {
+ nsresult rv;
+
+ // Note that this is not an Equals() test on purpose -- for URIs that don't
+ // support host/port, we want equality to basically be object identity, for
+ // security purposes. Otherwise, for example, two javascript: URIs that
+ // are otherwise unrelated could end up "same origin", which would be
+ // unfortunate.
+ if (aSourceURI && aSourceURI == aTargetURI) {
+ return true;
+ }
+
+ if (!aTargetURI || !aSourceURI) {
+ return false;
+ }
+
+ // If either URI is a nested URI, get the base URI
+ nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(aSourceURI);
+ nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI);
+
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ // Check if either URI has a special origin.
+ nsCOMPtr<nsIURI> origin;
+ nsCOMPtr<nsIURIWithSpecialOrigin> uriWithSpecialOrigin =
+ do_QueryInterface(sourceBaseURI);
+ if (uriWithSpecialOrigin) {
+ rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ MOZ_ASSERT(origin);
+ sourceBaseURI = origin;
+ }
+ uriWithSpecialOrigin = do_QueryInterface(targetBaseURI);
+ if (uriWithSpecialOrigin) {
+ rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ MOZ_ASSERT(origin);
+ targetBaseURI = origin;
+ }
+#endif
+
+ nsCOMPtr<nsIPrincipal> sourceBlobPrincipal;
+ if (BlobURLProtocolHandler::GetBlobURLPrincipal(
+ sourceBaseURI, getter_AddRefs(sourceBlobPrincipal))) {
+ nsCOMPtr<nsIURI> sourceBlobOwnerURI;
+ auto* basePrin = BasePrincipal::Cast(sourceBlobPrincipal);
+ rv = basePrin->GetURI(getter_AddRefs(sourceBlobOwnerURI));
+ if (NS_SUCCEEDED(rv)) {
+ sourceBaseURI = sourceBlobOwnerURI;
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> targetBlobPrincipal;
+ if (BlobURLProtocolHandler::GetBlobURLPrincipal(
+ targetBaseURI, getter_AddRefs(targetBlobPrincipal))) {
+ nsCOMPtr<nsIURI> targetBlobOwnerURI;
+ auto* basePrin = BasePrincipal::Cast(targetBlobPrincipal);
+ rv = basePrin->GetURI(getter_AddRefs(targetBlobOwnerURI));
+ if (NS_SUCCEEDED(rv)) {
+ targetBaseURI = targetBlobOwnerURI;
+ }
+ }
+
+ if (!sourceBaseURI || !targetBaseURI) return false;
+
+ // Compare schemes
+ nsAutoCString targetScheme;
+ bool sameScheme = false;
+ if (NS_FAILED(targetBaseURI->GetScheme(targetScheme)) ||
+ NS_FAILED(sourceBaseURI->SchemeIs(targetScheme.get(), &sameScheme)) ||
+ !sameScheme) {
+ // Not same-origin if schemes differ
+ return false;
+ }
+
+ // For file scheme, reject unless the files are identical. See
+ // NS_RelaxStrictFileOriginPolicy for enforcing file same-origin checking
+ if (targetScheme.EqualsLiteral("file")) {
+ // in traditional unsafe behavior all files are the same origin
+ if (!aStrictFileOriginPolicy) return true;
+
+ nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(sourceBaseURI));
+ nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(targetBaseURI));
+
+ if (!sourceFileURL || !targetFileURL) return false;
+
+ nsCOMPtr<nsIFile> sourceFile, targetFile;
+
+ sourceFileURL->GetFile(getter_AddRefs(sourceFile));
+ targetFileURL->GetFile(getter_AddRefs(targetFile));
+
+ if (!sourceFile || !targetFile) return false;
+
+ // Otherwise they had better match
+ bool filesAreEqual = false;
+ rv = sourceFile->Equals(targetFile, &filesAreEqual);
+ return NS_SUCCEEDED(rv) && filesAreEqual;
+ }
+
+#if IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ bool hasFlag;
+ if (NS_FAILED(NS_URIChainHasFlags(
+ targetBaseURI, nsIProtocolHandler::ORIGIN_IS_FULL_SPEC, &hasFlag)) ||
+ hasFlag) {
+ // URIs with this flag have the whole spec as a distinct trust
+ // domain; use the whole spec for comparison
+ nsAutoCString targetSpec;
+ nsAutoCString sourceSpec;
+ return (NS_SUCCEEDED(targetBaseURI->GetSpec(targetSpec)) &&
+ NS_SUCCEEDED(sourceBaseURI->GetSpec(sourceSpec)) &&
+ targetSpec.Equals(sourceSpec));
+ }
+#endif
+
+ // Compare hosts
+ nsAutoCString targetHost;
+ nsAutoCString sourceHost;
+ if (NS_FAILED(targetBaseURI->GetAsciiHost(targetHost)) ||
+ NS_FAILED(sourceBaseURI->GetAsciiHost(sourceHost))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIStandardURL> targetURL(do_QueryInterface(targetBaseURI));
+ nsCOMPtr<nsIStandardURL> sourceURL(do_QueryInterface(sourceBaseURI));
+ if (!targetURL || !sourceURL) {
+ return false;
+ }
+
+ if (!targetHost.Equals(sourceHost, nsCaseInsensitiveCStringComparator)) {
+ return false;
+ }
+
+ return NS_GetRealPort(targetBaseURI) == NS_GetRealPort(sourceBaseURI);
+}
+
+bool NS_URIIsLocalFile(nsIURI* aURI) {
+ nsCOMPtr<nsINetUtil> util = do_GetNetUtil();
+
+ bool isFile;
+ return util &&
+ NS_SUCCEEDED(util->ProtocolHasFlags(
+ aURI, nsIProtocolHandler::URI_IS_LOCAL_FILE, &isFile)) &&
+ isFile;
+}
+
+bool NS_RelaxStrictFileOriginPolicy(nsIURI* aTargetURI, nsIURI* aSourceURI,
+ bool aAllowDirectoryTarget /* = false */) {
+ if (!NS_URIIsLocalFile(aTargetURI)) {
+ // This is probably not what the caller intended
+ MOZ_ASSERT_UNREACHABLE(
+ "NS_RelaxStrictFileOriginPolicy called with non-file URI");
+ return false;
+ }
+
+ if (!NS_URIIsLocalFile(aSourceURI)) {
+ // If the source is not also a file: uri then forget it
+ // (don't want resource: principals in a file: doc)
+ //
+ // note: we're not de-nesting jar: uris here, we want to
+ // keep archive content bottled up in its own little island
+ return false;
+ }
+
+ //
+ // pull out the internal files
+ //
+ nsCOMPtr<nsIFileURL> targetFileURL(do_QueryInterface(aTargetURI));
+ nsCOMPtr<nsIFileURL> sourceFileURL(do_QueryInterface(aSourceURI));
+ nsCOMPtr<nsIFile> targetFile;
+ nsCOMPtr<nsIFile> sourceFile;
+ bool targetIsDir;
+
+ // Make sure targetFile is not a directory (bug 209234)
+ // and that it exists w/out unescaping (bug 395343)
+ if (!sourceFileURL || !targetFileURL ||
+ NS_FAILED(targetFileURL->GetFile(getter_AddRefs(targetFile))) ||
+ NS_FAILED(sourceFileURL->GetFile(getter_AddRefs(sourceFile))) ||
+ !targetFile || !sourceFile || NS_FAILED(targetFile->Normalize()) ||
+#ifndef MOZ_WIDGET_ANDROID
+ NS_FAILED(sourceFile->Normalize()) ||
+#endif
+ (!aAllowDirectoryTarget &&
+ (NS_FAILED(targetFile->IsDirectory(&targetIsDir)) || targetIsDir))) {
+ return false;
+ }
+
+ return false;
+}
+
+bool NS_IsInternalSameURIRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel, uint32_t aFlags) {
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (!oldURI || !newURI) {
+ return false;
+ }
+
+ bool res;
+ return NS_SUCCEEDED(oldURI->Equals(newURI, &res)) && res;
+}
+
+bool NS_IsHSTSUpgradeRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel,
+ uint32_t aFlags) {
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (!oldURI || !newURI) {
+ return false;
+ }
+
+ if (!oldURI->SchemeIs("http")) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ nsresult rv = NS_GetSecureUpgradedURI(oldURI, getter_AddRefs(upgradedURI));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ bool res;
+ return NS_SUCCEEDED(upgradedURI->Equals(newURI, &res)) && res;
+}
+
+bool NS_ShouldRemoveAuthHeaderOnRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags) {
+ // we need to strip Authentication headers for external cross-origin redirects
+ // Howerver, we should NOT strip auth headers for
+ // - internal redirects/HSTS upgrades
+ // - same origin redirects
+ // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
+ if ((aFlags & (nsIChannelEventSink::REDIRECT_STS_UPGRADE |
+ nsIChannelEventSink::REDIRECT_INTERNAL))) {
+ // this is an internal redirect do not strip auth header
+ return false;
+ }
+ nsCOMPtr<nsIURI> oldUri;
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldUri)));
+
+ nsCOMPtr<nsIURI> newUri;
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newUri)));
+
+ nsresult rv = nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
+ newUri, oldUri, false, false);
+
+ return NS_FAILED(rv);
+}
+
+nsresult NS_LinkRedirectChannels(uint64_t channelId,
+ nsIParentChannel* parentChannel,
+ nsIChannel** _result) {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ return registrar->LinkChannels(channelId, parentChannel, _result);
+}
+
+nsILoadInfo::CrossOriginEmbedderPolicy
+NS_GetCrossOriginEmbedderPolicyFromHeader(
+ const nsACString& aHeader, bool aIsOriginTrialCoepCredentiallessEnabled) {
+ nsCOMPtr<nsISFVService> sfv = GetSFVService();
+
+ nsCOMPtr<nsISFVItem> item;
+ nsresult rv = sfv->ParseItem(aHeader, getter_AddRefs(item));
+ if (NS_FAILED(rv)) {
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+ }
+
+ nsCOMPtr<nsISFVBareItem> value;
+ rv = item->GetValue(getter_AddRefs(value));
+ if (NS_FAILED(rv)) {
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+ }
+
+ nsCOMPtr<nsISFVToken> token = do_QueryInterface(value);
+ if (!token) {
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+ }
+
+ nsAutoCString embedderPolicy;
+ rv = token->GetValue(embedderPolicy);
+ if (NS_FAILED(rv)) {
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+ }
+
+ if (embedderPolicy.EqualsLiteral("require-corp")) {
+ return nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP;
+ } else if (embedderPolicy.EqualsLiteral("credentialless") &&
+ IsCoepCredentiallessEnabled(
+ aIsOriginTrialCoepCredentiallessEnabled)) {
+ return nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS;
+ }
+
+ return nsILoadInfo::EMBEDDER_POLICY_NULL;
+}
+
+/** Given the first (disposition) token from a Content-Disposition header,
+ * tell whether it indicates the content is inline or attachment
+ * @param aDispToken the disposition token from the content-disposition header
+ */
+uint32_t NS_GetContentDispositionFromToken(const nsAString& aDispToken) {
+ // RFC 2183, section 2.8 says that an unknown disposition
+ // value should be treated as "attachment"
+ // If all of these tests eval to false, then we have a content-disposition of
+ // "attachment" or unknown
+ if (aDispToken.IsEmpty() || aDispToken.LowerCaseEqualsLiteral("inline") ||
+ // Broken sites just send
+ // Content-Disposition: filename="file"
+ // without a disposition token... screen those out.
+ StringHead(aDispToken, 8).LowerCaseEqualsLiteral("filename")) {
+ return nsIChannel::DISPOSITION_INLINE;
+ }
+
+ return nsIChannel::DISPOSITION_ATTACHMENT;
+}
+
+uint32_t NS_GetContentDispositionFromHeader(const nsACString& aHeader,
+ nsIChannel* aChan /* = nullptr */) {
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return nsIChannel::DISPOSITION_ATTACHMENT;
+
+ nsAutoString dispToken;
+ rv = mimehdrpar->GetParameterHTTP(aHeader, "", ""_ns, true, nullptr,
+ dispToken);
+
+ if (NS_FAILED(rv)) {
+ // special case (see bug 272541): empty disposition type handled as "inline"
+ if (rv == NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY) {
+ return nsIChannel::DISPOSITION_INLINE;
+ }
+ return nsIChannel::DISPOSITION_ATTACHMENT;
+ }
+
+ return NS_GetContentDispositionFromToken(dispToken);
+}
+
+nsresult NS_GetFilenameFromDisposition(nsAString& aFilename,
+ const nsACString& aDisposition) {
+ aFilename.Truncate();
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // Get the value of 'filename' parameter
+ rv = mimehdrpar->GetParameterHTTP(aDisposition, "filename", ""_ns, true,
+ nullptr, aFilename);
+
+ if (NS_FAILED(rv)) {
+ aFilename.Truncate();
+ return rv;
+ }
+
+ if (aFilename.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ // Filename may still be percent-encoded. Fix:
+ if (aFilename.FindChar('%') != -1) {
+ nsCOMPtr<nsITextToSubURI> textToSubURI =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString unescaped;
+ textToSubURI->UnEscapeURIForUI(NS_ConvertUTF16toUTF8(aFilename),
+ /* dontEscape = */ true, unescaped);
+ aFilename.Assign(unescaped);
+ }
+ }
+
+ return NS_OK;
+}
+
+void net_EnsurePSMInit() {
+ if (XRE_IsSocketProcess()) {
+ EnsureNSSInitializedChromeOrContent();
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ DebugOnly<bool> rv = EnsureNSSInitializedChromeOrContent();
+ MOZ_ASSERT(rv);
+}
+
+bool NS_IsAboutBlank(nsIURI* uri) {
+ // GetSpec can be expensive for some URIs, so check the scheme first.
+ if (!uri->SchemeIs("about")) {
+ return false;
+ }
+
+ nsAutoCString spec;
+ if (NS_FAILED(uri->GetSpec(spec))) {
+ return false;
+ }
+
+ return spec.EqualsLiteral("about:blank");
+}
+
+bool NS_IsAboutSrcdoc(nsIURI* uri) {
+ // GetSpec can be expensive for some URIs, so check the scheme first.
+ if (!uri->SchemeIs("about")) {
+ return false;
+ }
+
+ nsAutoCString spec;
+ if (NS_FAILED(uri->GetSpec(spec))) {
+ return false;
+ }
+
+ return spec.EqualsLiteral("about:srcdoc");
+}
+
+nsresult NS_GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine) {
+ if (strchr(host.get(), ':')) {
+ // host is an IPv6 address literal and must be encapsulated in []'s
+ hostLine.Assign('[');
+ // scope id is not needed for Host header.
+ int scopeIdPos = host.FindChar('%');
+ if (scopeIdPos == -1) {
+ hostLine.Append(host);
+ } else if (scopeIdPos > 0) {
+ hostLine.Append(Substring(host, 0, scopeIdPos));
+ } else {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ hostLine.Append(']');
+ } else {
+ hostLine.Assign(host);
+ }
+ if (port != -1) {
+ hostLine.Append(':');
+ hostLine.AppendInt(port);
+ }
+ return NS_OK;
+}
+
+void NS_SniffContent(const char* aSnifferType, nsIRequest* aRequest,
+ const uint8_t* aData, uint32_t aLength,
+ nsACString& aSniffedType) {
+ using ContentSnifferCache = nsCategoryCache<nsIContentSniffer>;
+ extern ContentSnifferCache* gNetSniffers;
+ extern ContentSnifferCache* gDataSniffers;
+ extern ContentSnifferCache* gORBSniffers;
+ extern ContentSnifferCache* gNetAndORBSniffers;
+ ContentSnifferCache* cache = nullptr;
+ if (!strcmp(aSnifferType, NS_CONTENT_SNIFFER_CATEGORY)) {
+ if (!gNetSniffers) {
+ gNetSniffers = new ContentSnifferCache(NS_CONTENT_SNIFFER_CATEGORY);
+ }
+ cache = gNetSniffers;
+ } else if (!strcmp(aSnifferType, NS_DATA_SNIFFER_CATEGORY)) {
+ if (!gDataSniffers) {
+ gDataSniffers = new ContentSnifferCache(NS_DATA_SNIFFER_CATEGORY);
+ }
+ cache = gDataSniffers;
+ } else if (!strcmp(aSnifferType, NS_ORB_SNIFFER_CATEGORY)) {
+ if (!gORBSniffers) {
+ gORBSniffers = new ContentSnifferCache(NS_ORB_SNIFFER_CATEGORY);
+ }
+ cache = gORBSniffers;
+ } else if (!strcmp(aSnifferType, NS_CONTENT_AND_ORB_SNIFFER_CATEGORY)) {
+ if (!gNetAndORBSniffers) {
+ gNetAndORBSniffers =
+ new ContentSnifferCache(NS_CONTENT_AND_ORB_SNIFFER_CATEGORY);
+ }
+ cache = gNetAndORBSniffers;
+ } else {
+ // Invalid content sniffer type was requested
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ // In case XCTO nosniff was present, we could just skip sniffing here
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ if (loadInfo->GetSkipContentSniffing()) {
+ /* Bug 1571742
+ * We cannot skip snffing if the current MIME-Type might be a JSON.
+ * The JSON-Viewer relies on its own sniffer to determine, if it can
+ * render the page, so we need to make an exception if the Server provides
+ * a application/ mime, as it might be json.
+ */
+ nsAutoCString currentContentType;
+ channel->GetContentType(currentContentType);
+ if (!StringBeginsWith(currentContentType, "application/"_ns)) {
+ return;
+ }
+ }
+ }
+ nsCOMArray<nsIContentSniffer> sniffers;
+ cache->GetEntries(sniffers);
+ for (int32_t i = 0; i < sniffers.Count(); ++i) {
+ nsresult rv = sniffers[i]->GetMIMETypeFromContent(aRequest, aData, aLength,
+ aSniffedType);
+ if (NS_SUCCEEDED(rv) && !aSniffedType.IsEmpty()) {
+ return;
+ }
+ }
+
+ aSniffedType.Truncate();
+}
+
+bool NS_IsSrcdocChannel(nsIChannel* aChannel) {
+ bool isSrcdoc;
+ nsCOMPtr<nsIInputStreamChannel> isr = do_QueryInterface(aChannel);
+ if (isr) {
+ isr->GetIsSrcdocChannel(&isSrcdoc);
+ return isSrcdoc;
+ }
+ nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(aChannel);
+ if (vsc) {
+ nsresult rv = vsc->GetIsSrcdocChannel(&isSrcdoc);
+ if (NS_SUCCEEDED(rv)) {
+ return isSrcdoc;
+ }
+ }
+ return false;
+}
+
+// helper function for NS_ShouldSecureUpgrade for checking HSTS
+bool handleResultFunc(bool aAllowSTS, bool aIsStsHost) {
+ if (aIsStsHost) {
+ LOG(("nsHttpChannel::Connect() STS permissions found\n"));
+ if (aAllowSTS) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::STS);
+ return true;
+ }
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::PrefBlockedSTS);
+ } else {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::NoReasonToUpgrade);
+ }
+ return false;
+};
+// That function is a helper function of NS_ShouldSecureUpgrade to check if
+// CSP upgrade-insecure-requests, Mixed content auto upgrading or HTTPs-Only/-
+// First should upgrade the given request.
+static bool ShouldSecureUpgradeNoHSTS(nsIURI* aURI, nsILoadInfo* aLoadInfo) {
+ // 2. CSP upgrade-insecure-requests
+ if (aLoadInfo->GetUpgradeInsecureRequests()) {
+ // let's log a message to the console that we are upgrading a request
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ // append the additional 's' for security to the scheme :-)
+ scheme.AppendLiteral("s");
+ NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 reportScheme(scheme);
+ AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
+ uint64_t innerWindowId = aLoadInfo->GetInnerWindowID();
+ CSP_LogLocalizedStr("upgradeInsecureRequest", params,
+ u""_ns, // aSourceFile
+ u""_ns, // aScriptSample
+ 0, // aLineNumber
+ 1, // aColumnNumber
+ nsIScriptError::warningFlag,
+ "upgradeInsecureRequest"_ns, innerWindowId,
+ !!aLoadInfo->GetOriginAttributes().mPrivateBrowsingId);
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::CSP);
+ return true;
+ }
+ // 3. Mixed content auto upgrading
+ if (aLoadInfo->GetBrowserUpgradeInsecureRequests()) {
+ // let's log a message to the console that we are upgrading a request
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ // append the additional 's' for security to the scheme :-)
+ scheme.AppendLiteral("s");
+ NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 reportScheme(scheme);
+ AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
+
+ nsAutoString localizedMsg;
+ nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
+ "MixedContentAutoUpgrade", params,
+ localizedMsg);
+
+ // Prepending ixed Content to the outgoing console message
+ nsString message;
+ message.AppendLiteral(u"Mixed Content: ");
+ message.Append(localizedMsg);
+
+ uint64_t innerWindowId = aLoadInfo->GetInnerWindowID();
+ nsContentUtils::ReportToConsoleByWindowID(
+ message, nsIScriptError::warningFlag, "Mixed Content Message"_ns,
+ innerWindowId, aURI);
+
+ // Set this flag so we know we'll upgrade because of
+ // 'security.mixed_content.upgrade_display_content'.
+ aLoadInfo->SetBrowserDidUpgradeInsecureRequests(true);
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::BrowserDisplay);
+
+ return true;
+ }
+
+ // 4. Https-Only
+ if (nsHTTPSOnlyUtils::ShouldUpgradeRequest(aURI, aLoadInfo)) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::HTTPSOnly);
+ return true;
+ }
+ // 4.a Https-First
+ if (nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(aURI, aLoadInfo)) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::HTTPSFirst);
+ return true;
+ }
+ return false;
+}
+
+// Check if channel should be upgraded. check in the following order:
+// 1. HSTS
+// 2. CSP upgrade-insecure-requests
+// 3. Mixed content auto upgrading
+// 4. Https-Only / first
+// (5. Https RR - will be checked in nsHttpChannel)
+nsresult NS_ShouldSecureUpgrade(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIPrincipal* aChannelResultPrincipal,
+ bool aAllowSTS, const OriginAttributes& aOriginAttributes,
+ bool& aShouldUpgrade, std::function<void(bool, nsresult)>&& aResultCallback,
+ bool& aWillCallback) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aWillCallback = false;
+ aShouldUpgrade = false;
+
+ // Even if we're in private browsing mode, we still enforce existing STS
+ // data (it is read-only).
+ // if the connection is not using SSL and either the exact host matches or
+ // a superdomain wants to force HTTPS, do it.
+ bool isHttps = aURI->SchemeIs("https");
+
+ // If request is https, then there is nothing to do here.
+ if (isHttps) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::AlreadyHTTPS);
+ aShouldUpgrade = false;
+ return NS_OK;
+ }
+ // If it is a mixed content trustworthy loopback, then we shouldn't upgrade
+ // it.
+ if (nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI)) {
+ aShouldUpgrade = false;
+ return NS_OK;
+ }
+ // If no loadInfo exist there is nothing to upgrade here.
+ if (!aLoadInfo) {
+ aShouldUpgrade = false;
+ return NS_OK;
+ }
+ MOZ_ASSERT(!aURI->SchemeIs("https"));
+
+ // enforce Strict-Transport-Security
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+
+ bool isStsHost = false;
+ // Calling |IsSecureURI| before the storage is ready to read will
+ // block the main thread. Once the storage is ready, we can call it
+ // from main thread.
+ static Atomic<bool, Relaxed> storageReady(false);
+ if (!storageReady && gSocketTransportService && aResultCallback) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aLoadInfo;
+ nsCOMPtr<nsIURI> uri = aURI;
+ auto callbackWrapper = [resultCallback{std::move(aResultCallback)}, uri,
+ loadInfo](bool aShouldUpgrade, nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // 1. HSTS upgrade
+ if (aShouldUpgrade || NS_FAILED(aStatus)) {
+ resultCallback(aShouldUpgrade, aStatus);
+ return;
+ }
+ // Check if we need to upgrade because of other reasons.
+ // 2. CSP upgrade-insecure-requests
+ // 3. Mixed content auto upgrading
+ // 4. Https-Only / first
+ bool shouldUpgrade = ShouldSecureUpgradeNoHSTS(uri, loadInfo);
+ resultCallback(shouldUpgrade, aStatus);
+ };
+ nsCOMPtr<nsISiteSecurityService> service = sss;
+ nsresult rv = gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction(
+ "net::NS_ShouldSecureUpgrade",
+ [service{std::move(service)}, uri{std::move(uri)},
+ originAttributes(aOriginAttributes),
+ handleResultFunc{std::move(handleResultFunc)},
+ callbackWrapper{std::move(callbackWrapper)},
+ allowSTS{std::move(aAllowSTS)}]() mutable {
+ bool isStsHost = false;
+ nsresult rv =
+ service->IsSecureURI(uri, originAttributes, &isStsHost);
+
+ // Successfully get the result from |IsSecureURI| implies that
+ // the storage is ready to read.
+ storageReady = NS_SUCCEEDED(rv);
+ bool shouldUpgrade = handleResultFunc(allowSTS, isStsHost);
+ // Check if request should be upgraded.
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::NS_ShouldSecureUpgrade::ResultCallback",
+ [rv, shouldUpgrade,
+ callbackWrapper{std::move(callbackWrapper)}]() {
+ callbackWrapper(shouldUpgrade, rv);
+ }));
+ }),
+ NS_DISPATCH_NORMAL);
+ aWillCallback = NS_SUCCEEDED(rv);
+ return rv;
+ }
+
+ nsresult rv = sss->IsSecureURI(aURI, aOriginAttributes, &isStsHost);
+
+ // if the SSS check fails, it's likely because this load is on a
+ // malformed URI or something else in the setup is wrong, so any error
+ // should be reported.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aShouldUpgrade = handleResultFunc(aAllowSTS, isStsHost);
+ if (!aShouldUpgrade) {
+ // Check for CSP upgrade-insecure-requests, Mixed content auto upgrading
+ // and Https-Only / -First.
+ aShouldUpgrade = ShouldSecureUpgradeNoHSTS(aURI, aLoadInfo);
+ }
+ return rv;
+}
+
+nsresult NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI) {
+ NS_MutateURI mutator(aURI);
+ mutator.SetScheme("https"_ns); // Change the scheme to HTTPS:
+
+ // Change the default port to 443:
+ nsCOMPtr<nsIStandardURL> stdURL = do_QueryInterface(aURI);
+ if (stdURL) {
+ mutator.Apply(&nsIStandardURLMutator::SetDefaultPort, 443, nullptr);
+ } else {
+ // If we don't have a nsStandardURL, fall back to using GetPort/SetPort.
+ // XXXdholbert Is this function even called with a non-nsStandardURL arg,
+ // in practice?
+ NS_WARNING("Calling NS_GetSecureUpgradedURI for non nsStandardURL");
+ int32_t oldPort = -1;
+ nsresult rv = aURI->GetPort(&oldPort);
+ if (NS_FAILED(rv)) return rv;
+
+ // Keep any nonstandard ports so only the scheme is changed.
+ // For example:
+ // http://foo.com:80 -> https://foo.com:443
+ // http://foo.com:81 -> https://foo.com:81
+
+ if (oldPort == 80 || oldPort == -1) {
+ mutator.SetPort(-1);
+ } else {
+ mutator.SetPort(oldPort);
+ }
+ }
+
+ return mutator.Finalize(aUpgradedURI);
+}
+
+nsresult NS_CompareLoadInfoAndLoadContext(nsIChannel* aChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(aChannel, loadContext);
+ if (!loadContext) {
+ return NS_OK;
+ }
+
+ // We try to skip about:newtab.
+ // about:newtab will use SystemPrincipal to download thumbnails through
+ // https:// and blob URLs.
+ bool isAboutPage = false;
+ nsINode* node = loadInfo->LoadingNode();
+ if (node) {
+ nsIURI* uri = node->OwnerDoc()->GetDocumentURI();
+ isAboutPage = uri->SchemeIs("about");
+ }
+
+ if (isAboutPage) {
+ return NS_OK;
+ }
+
+ // We skip the favicon loading here. The favicon loading might be
+ // triggered by the XUL image. For that case, the loadContext will have
+ // default originAttributes since the XUL image uses SystemPrincipal, but
+ // the loadInfo will use originAttributes from the content. Thus, the
+ // originAttributes between loadInfo and loadContext will be different.
+ // That's why we have to skip the comparison for the favicon loading.
+ if (loadInfo->GetLoadingPrincipal() &&
+ loadInfo->GetLoadingPrincipal()->IsSystemPrincipal() &&
+ loadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ return NS_OK;
+ }
+
+ OriginAttributes originAttrsLoadInfo = loadInfo->GetOriginAttributes();
+ OriginAttributes originAttrsLoadContext;
+ loadContext->GetOriginAttributes(originAttrsLoadContext);
+
+ LOG(
+ ("NS_CompareLoadInfoAndLoadContext - loadInfo: %d, %d; "
+ "loadContext: %d, %d. [channel=%p]",
+ originAttrsLoadInfo.mUserContextId,
+ originAttrsLoadInfo.mPrivateBrowsingId,
+ originAttrsLoadContext.mUserContextId,
+ originAttrsLoadContext.mPrivateBrowsingId, aChannel));
+
+ MOZ_ASSERT(originAttrsLoadInfo.mUserContextId ==
+ originAttrsLoadContext.mUserContextId,
+ "The value of mUserContextId in the loadContext and in the "
+ "loadInfo are not the same!");
+
+ MOZ_ASSERT(originAttrsLoadInfo.mPrivateBrowsingId ==
+ originAttrsLoadContext.mPrivateBrowsingId,
+ "The value of mPrivateBrowsingId in the loadContext and in the "
+ "loadInfo are not the same!");
+
+ return NS_OK;
+}
+
+nsresult NS_SetRequestBlockingReason(nsIChannel* channel, uint32_t reason) {
+ NS_ENSURE_ARG(channel);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ return NS_SetRequestBlockingReason(loadInfo, reason);
+}
+
+nsresult NS_SetRequestBlockingReason(nsILoadInfo* loadInfo, uint32_t reason) {
+ NS_ENSURE_ARG(loadInfo);
+
+ return loadInfo->SetRequestBlockingReason(reason);
+}
+
+nsresult NS_SetRequestBlockingReasonIfNull(nsILoadInfo* loadInfo,
+ uint32_t reason) {
+ NS_ENSURE_ARG(loadInfo);
+
+ uint32_t existingReason;
+ if (NS_SUCCEEDED(loadInfo->GetRequestBlockingReason(&existingReason)) &&
+ existingReason != nsILoadInfo::BLOCKING_REASON_NONE) {
+ return NS_OK;
+ }
+
+ return loadInfo->SetRequestBlockingReason(reason);
+}
+
+bool NS_IsOffline() {
+ bool offline = true;
+ bool connectivity = true;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (ios) {
+ ios->GetOffline(&offline);
+ ios->GetConnectivity(&connectivity);
+ }
+ return offline || !connectivity;
+}
+
+/**
+ * This function returns true if this channel should be classified by
+ * the URL Classifier, false otherwise.
+ *
+ * The idea of the algorithm to determine if a channel should be
+ * classified is based on:
+ * 1. Channels created by non-privileged code should be classified.
+ * 2. Top-level document’s channels, if loaded by privileged code
+ * (system principal), should be classified.
+ * 3. Any other channel, created by privileged code, is considered safe.
+ *
+ * A bad/hacked/corrupted safebrowsing database, plus a mistakenly
+ * classified critical channel (this may result from a bug in the exemption
+ * rules or incorrect information being passed into) can cause serious
+ * problems. For example, if the updater channel is classified and blocked
+ * by the Safe Browsing, Firefox can't update itself, and there is no way to
+ * recover from that.
+ *
+ * So two safeguards are added to ensure critical channels are never
+ * automatically classified either because there is a bug in the algorithm
+ * or the data in loadinfo is wrong.
+ * 1. beConservative, this is set by ServiceRequest and we treat
+ * channel created for ServiceRequest as critical channels.
+ * 2. nsIChannel::LOAD_BYPASS_URL_CLASSIFIER, channel's opener can use this
+ * flag to enforce bypassing the URL classifier check.
+ */
+bool NS_ShouldClassifyChannel(nsIChannel* aChannel) {
+ nsLoadFlags loadFlags;
+ Unused << aChannel->GetLoadFlags(&loadFlags);
+ // If our load flags dictate that we must let this channel through without
+ // URL classification, obey that here without performing more checks.
+ if (loadFlags & nsIChannel::LOAD_BYPASS_URL_CLASSIFIER) {
+ return false;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel(do_QueryInterface(aChannel));
+ if (httpChannel) {
+ bool beConservative;
+ nsresult rv = httpChannel->GetBeConservative(&beConservative);
+
+ // beConservative flag, set by ServiceRequest to ensure channels that
+ // fetch update use conservative TLS setting, are used here to identify
+ // channels are critical to bypass classification. for channels don't
+ // support beConservative, continue to apply the exemption rules.
+ if (NS_SUCCEEDED(rv) && beConservative) {
+ return false;
+ }
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ ExtContentPolicyType type = loadInfo->GetExternalContentPolicyType();
+ // Skip classifying channel triggered by system unless it is a top-level
+ // load.
+ return !(loadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
+ ExtContentPolicy::TYPE_DOCUMENT != type);
+}
+
+namespace mozilla {
+namespace net {
+
+bool InScriptableRange(int64_t val) {
+ return (val <= kJS_MAX_SAFE_INTEGER) && (val >= kJS_MIN_SAFE_INTEGER);
+}
+
+bool InScriptableRange(uint64_t val) { return val <= kJS_MAX_SAFE_UINTEGER; }
+
+nsresult GetParameterHTTP(const nsACString& aHeaderVal, const char* aParamName,
+ nsAString& aResult) {
+ return nsMIMEHeaderParamImpl::GetParameterHTTP(aHeaderVal, aParamName,
+ aResult);
+}
+
+bool ChannelIsPost(nsIChannel* aChannel) {
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
+ nsAutoCString method;
+ Unused << httpChannel->GetRequestMethod(method);
+ return method.EqualsLiteral("POST");
+ }
+ return false;
+}
+
+bool SchemeIsHTTP(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("http");
+}
+
+bool SchemeIsHTTPS(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("https");
+}
+
+bool SchemeIsJavascript(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("javascript");
+}
+
+bool SchemeIsChrome(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("chrome");
+}
+
+bool SchemeIsAbout(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("about");
+}
+
+bool SchemeIsBlob(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("blob");
+}
+
+bool SchemeIsFile(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("file");
+}
+
+bool SchemeIsData(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("data");
+}
+
+bool SchemeIsViewSource(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("view-source");
+}
+
+bool SchemeIsResource(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("resource");
+}
+
+bool SchemeIsFTP(nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+ return aURI->SchemeIs("ftp");
+}
+
+bool SchemeIsSpecial(const nsACString& aScheme) {
+ // See https://url.spec.whatwg.org/#special-scheme
+ return aScheme.EqualsIgnoreCase("ftp") || aScheme.EqualsIgnoreCase("file") ||
+ aScheme.EqualsIgnoreCase("http") ||
+ aScheme.EqualsIgnoreCase("https") || aScheme.EqualsIgnoreCase("ws") ||
+ aScheme.EqualsIgnoreCase("wss");
+}
+
+bool IsSchemeChangePermitted(nsIURI* aOldURI, const nsACString& newScheme) {
+ // See step 2.1 in https://url.spec.whatwg.org/#special-scheme
+ // Note: The spec text uses "buffer" instead of newScheme, and "url"
+ MOZ_ASSERT(aOldURI);
+
+ nsAutoCString tmp;
+ nsresult rv = aOldURI->GetScheme(tmp);
+ // If url's scheme is a special scheme and buffer is not a
+ // special scheme, then return.
+ // If url's scheme is not a special scheme and buffer is a
+ // special scheme, then return.
+ if (NS_FAILED(rv) || SchemeIsSpecial(tmp) != SchemeIsSpecial(newScheme)) {
+ return false;
+ }
+
+ // If url's scheme is "file" and its host is an empty host, then return.
+ if (aOldURI->SchemeIs("file")) {
+ rv = aOldURI->GetHost(tmp);
+ if (NS_FAILED(rv) || tmp.IsEmpty()) {
+ return false;
+ }
+ }
+
+ // URL Spec: If url includes credentials or has a non-null port, and
+ // buffer is "file", then return.
+ if (newScheme.EqualsIgnoreCase("file")) {
+ bool hasUserPass;
+ if (NS_FAILED(aOldURI->GetHasUserPass(&hasUserPass)) || hasUserPass) {
+ return false;
+ }
+ int32_t port;
+ rv = aOldURI->GetPort(&port);
+ if (NS_FAILED(rv) || port != -1) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+already_AddRefed<nsIURI> TryChangeProtocol(nsIURI* aURI,
+ const nsAString& aProtocol) {
+ MOZ_ASSERT(aURI);
+
+ nsAString::const_iterator start;
+ aProtocol.BeginReading(start);
+
+ nsAString::const_iterator end;
+ aProtocol.EndReading(end);
+
+ nsAString::const_iterator iter(start);
+ FindCharInReadable(':', iter, end);
+
+ // Changing the protocol of a URL, changes the "nature" of the URI
+ // implementation. In order to do this properly, we have to serialize the
+ // existing URL and reparse it in a new object.
+ nsCOMPtr<nsIURI> clone;
+ nsresult rv = NS_MutateURI(aURI)
+ .SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter)))
+ .Finalize(clone);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ if (StaticPrefs::network_url_strict_protocol_setter()) {
+ nsAutoCString newScheme;
+ rv = clone->GetScheme(newScheme);
+ if (NS_FAILED(rv) || !net::IsSchemeChangePermitted(aURI, newScheme)) {
+ nsAutoCString url;
+ Unused << clone->GetSpec(url);
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(NS_ConvertUTF8toUTF16(url));
+ params.AppendElement(NS_ConvertUTF8toUTF16(newScheme));
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Strict Url Protocol Setter"_ns, nullptr,
+ nsContentUtils::eNECKO_PROPERTIES, "StrictUrlProtocolSetter", params);
+ return nullptr;
+ }
+ }
+
+ nsAutoCString href;
+ rv = clone->GetSpec(href);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ RefPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), href);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ return uri.forget();
+}
+
+// Decode a parameter value using the encoding defined in RFC 5987 (in place)
+//
+// charset "'" [ language ] "'" value-chars
+//
+// returns true when decoding happened successfully (otherwise leaves
+// passed value alone)
+static bool Decode5987Format(nsAString& aEncoded) {
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return false;
+
+ nsAutoCString asciiValue;
+
+ const char16_t* encstart = aEncoded.BeginReading();
+ const char16_t* encend = aEncoded.EndReading();
+
+ // create a plain ASCII string, aborting if we can't do that
+ // converted form is always shorter than input
+ while (encstart != encend) {
+ if (*encstart > 0 && *encstart < 128) {
+ asciiValue.Append((char)*encstart);
+ } else {
+ return false;
+ }
+ encstart++;
+ }
+
+ nsAutoString decoded;
+ nsAutoCString language;
+
+ rv = mimehdrpar->DecodeRFC5987Param(asciiValue, language, decoded);
+ if (NS_FAILED(rv)) return false;
+
+ aEncoded = decoded;
+ return true;
+}
+
+LinkHeader::LinkHeader() { mCrossOrigin.SetIsVoid(true); }
+
+void LinkHeader::Reset() {
+ mHref.Truncate();
+ mRel.Truncate();
+ mTitle.Truncate();
+ mNonce.Truncate();
+ mIntegrity.Truncate();
+ mSrcset.Truncate();
+ mSizes.Truncate();
+ mType.Truncate();
+ mMedia.Truncate();
+ mAnchor.Truncate();
+ mCrossOrigin.Truncate();
+ mReferrerPolicy.Truncate();
+ mAs.Truncate();
+ mCrossOrigin.SetIsVoid(true);
+ mFetchPriority.Truncate();
+}
+
+nsresult LinkHeader::NewResolveHref(nsIURI** aOutURI, nsIURI* aBaseURI) const {
+ if (mAnchor.IsEmpty()) {
+ // use the base uri
+ return NS_NewURI(aOutURI, mHref, nullptr, aBaseURI);
+ }
+
+ // compute the anchored URI
+ nsCOMPtr<nsIURI> anchoredURI;
+ nsresult rv =
+ NS_NewURI(getter_AddRefs(anchoredURI), mAnchor, nullptr, aBaseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewURI(aOutURI, mHref, nullptr, anchoredURI);
+}
+
+bool LinkHeader::operator==(const LinkHeader& rhs) const {
+ return mHref == rhs.mHref && mRel == rhs.mRel && mTitle == rhs.mTitle &&
+ mNonce == rhs.mNonce && mIntegrity == rhs.mIntegrity &&
+ mSrcset == rhs.mSrcset && mSizes == rhs.mSizes && mType == rhs.mType &&
+ mMedia == rhs.mMedia && mAnchor == rhs.mAnchor &&
+ mCrossOrigin == rhs.mCrossOrigin &&
+ mReferrerPolicy == rhs.mReferrerPolicy && mAs == rhs.mAs &&
+ mFetchPriority == rhs.mFetchPriority;
+}
+
+constexpr auto kTitleStar = "title*"_ns;
+
+nsTArray<LinkHeader> ParseLinkHeader(const nsAString& aLinkData) {
+ nsTArray<LinkHeader> linkHeaders;
+
+ // keep track where we are within the header field
+ bool seenParameters = false;
+
+ // parse link content and add to array
+ LinkHeader header;
+ nsAutoString titleStar;
+
+ // copy to work buffer
+ nsAutoString stringList(aLinkData);
+
+ // put an extra null at the end
+ stringList.Append(kNullCh);
+
+ char16_t* start = stringList.BeginWriting();
+
+ while (*start != kNullCh) {
+ // parse link content and call process style link
+
+ // skip leading space
+ while ((*start != kNullCh) && nsCRT::IsAsciiSpace(*start)) {
+ ++start;
+ }
+
+ char16_t* end = start;
+ char16_t* last = end - 1;
+
+ bool wasQuotedString = false;
+
+ // look for semicolon or comma
+ while (*end != kNullCh && *end != kSemicolon && *end != kComma) {
+ char16_t ch = *end;
+
+ if (ch == kQuote || ch == kLessThan) {
+ // quoted string
+
+ char16_t quote = ch;
+ if (quote == kLessThan) {
+ quote = kGreaterThan;
+ }
+
+ wasQuotedString = (ch == kQuote);
+
+ char16_t* closeQuote = (end + 1);
+
+ // seek closing quote
+ while (*closeQuote != kNullCh && quote != *closeQuote) {
+ // in quoted-string, "\" is an escape character
+ if (wasQuotedString && *closeQuote == kBackSlash &&
+ *(closeQuote + 1) != kNullCh) {
+ ++closeQuote;
+ }
+
+ ++closeQuote;
+ }
+
+ if (quote == *closeQuote) {
+ // found closer
+
+ // skip to close quote
+ end = closeQuote;
+
+ last = end - 1;
+
+ ch = *(end + 1);
+
+ if (ch != kNullCh && ch != kSemicolon && ch != kComma) {
+ // end string here
+ *(++end) = kNullCh;
+
+ ch = *(end + 1);
+
+ // keep going until semi or comma
+ while (ch != kNullCh && ch != kSemicolon && ch != kComma) {
+ ++end;
+
+ ch = *(end + 1);
+ }
+ }
+ }
+ }
+
+ ++end;
+ ++last;
+ }
+
+ char16_t endCh = *end;
+
+ // end string here
+ *end = kNullCh;
+
+ if (start < end) {
+ if ((*start == kLessThan) && (*last == kGreaterThan)) {
+ *last = kNullCh;
+
+ // first instance of <...> wins
+ // also, do not allow hrefs after the first param was seen
+ if (header.mHref.IsEmpty() && !seenParameters) {
+ header.mHref = (start + 1);
+ header.mHref.StripWhitespace();
+ }
+ } else {
+ char16_t* equals = start;
+ seenParameters = true;
+
+ while ((*equals != kNullCh) && (*equals != kEqual)) {
+ equals++;
+ }
+
+ const bool hadEquals = *equals != kNullCh;
+ *equals = kNullCh;
+ nsAutoString attr(start);
+ attr.StripWhitespace();
+
+ char16_t* value = hadEquals ? ++equals : equals;
+ while (nsCRT::IsAsciiSpace(*value)) {
+ value++;
+ }
+
+ if ((*value == kQuote) && (*value == *last)) {
+ *last = kNullCh;
+ value++;
+ }
+
+ if (wasQuotedString) {
+ // unescape in-place
+ char16_t* unescaped = value;
+ char16_t* src = value;
+
+ while (*src != kNullCh) {
+ if (*src == kBackSlash && *(src + 1) != kNullCh) {
+ src++;
+ }
+ *unescaped++ = *src++;
+ }
+
+ *unescaped = kNullCh;
+ }
+
+ if (attr.LowerCaseEqualsASCII(kTitleStar.get())) {
+ if (titleStar.IsEmpty() && !wasQuotedString) {
+ // RFC 5987 encoding; uses token format only, so skip if we get
+ // here with a quoted-string
+ nsAutoString tmp;
+ tmp = value;
+ if (Decode5987Format(tmp)) {
+ titleStar = tmp;
+ titleStar.CompressWhitespace();
+ } else {
+ // header value did not parse, throw it away
+ titleStar.Truncate();
+ }
+ }
+ } else {
+ header.MaybeUpdateAttribute(attr, value);
+ }
+ }
+ }
+
+ if (endCh == kComma) {
+ // hit a comma, process what we've got so far
+
+ header.mHref.Trim(" \t\n\r\f"); // trim HTML5 whitespace
+ if (!header.mHref.IsEmpty() && !header.mRel.IsEmpty()) {
+ if (!titleStar.IsEmpty()) {
+ // prefer RFC 5987 variant over non-I18zed version
+ header.mTitle = titleStar;
+ }
+ linkHeaders.AppendElement(header);
+ }
+
+ titleStar.Truncate();
+ header.Reset();
+
+ seenParameters = false;
+ }
+
+ start = ++end;
+ }
+
+ header.mHref.Trim(" \t\n\r\f"); // trim HTML5 whitespace
+ if (!header.mHref.IsEmpty() && !header.mRel.IsEmpty()) {
+ if (!titleStar.IsEmpty()) {
+ // prefer RFC 5987 variant over non-I18zed version
+ header.mTitle = titleStar;
+ }
+ linkHeaders.AppendElement(header);
+ }
+
+ return linkHeaders;
+}
+
+void LinkHeader::MaybeUpdateAttribute(const nsAString& aAttribute,
+ const char16_t* aValue) {
+ MOZ_ASSERT(!aAttribute.LowerCaseEqualsASCII(kTitleStar.get()));
+
+ if (aAttribute.LowerCaseEqualsLiteral("rel")) {
+ if (mRel.IsEmpty()) {
+ mRel = aValue;
+ mRel.CompressWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("title")) {
+ if (mTitle.IsEmpty()) {
+ mTitle = aValue;
+ mTitle.CompressWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("type")) {
+ if (mType.IsEmpty()) {
+ mType = aValue;
+ mType.StripWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("media")) {
+ if (mMedia.IsEmpty()) {
+ mMedia = aValue;
+
+ // The HTML5 spec is formulated in terms of the CSS3 spec,
+ // which specifies that media queries are case insensitive.
+ nsContentUtils::ASCIIToLower(mMedia);
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("anchor")) {
+ if (mAnchor.IsEmpty()) {
+ mAnchor = aValue;
+ mAnchor.StripWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("crossorigin")) {
+ if (mCrossOrigin.IsVoid()) {
+ mCrossOrigin.SetIsVoid(false);
+ mCrossOrigin = aValue;
+ mCrossOrigin.StripWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("as")) {
+ if (mAs.IsEmpty()) {
+ mAs = aValue;
+ mAs.CompressWhitespace();
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("referrerpolicy")) {
+ // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#referrer-policy-attribute
+ // Specs says referrer policy attribute is an enumerated attribute,
+ // case insensitive and includes the empty string
+ // We will parse the aValue with AttributeReferrerPolicyFromString
+ // later, which will handle parsing it as an enumerated attribute.
+ if (mReferrerPolicy.IsEmpty()) {
+ mReferrerPolicy = aValue;
+ }
+
+ } else if (aAttribute.LowerCaseEqualsLiteral("nonce")) {
+ if (mNonce.IsEmpty()) {
+ mNonce = aValue;
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("integrity")) {
+ if (mIntegrity.IsEmpty()) {
+ mIntegrity = aValue;
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("imagesrcset")) {
+ if (mSrcset.IsEmpty()) {
+ mSrcset = aValue;
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("imagesizes")) {
+ if (mSizes.IsEmpty()) {
+ mSizes = aValue;
+ }
+ } else if (aAttribute.LowerCaseEqualsLiteral("fetchpriority")) {
+ if (mFetchPriority.IsEmpty()) {
+ LOG(("Update fetchPriority to \"%s\"",
+ NS_ConvertUTF16toUTF8(aValue).get()));
+ mFetchPriority = aValue;
+ }
+ }
+}
+
+// We will use official mime-types from:
+// https://www.iana.org/assignments/media-types/media-types.xhtml#font
+// We do not support old deprecated mime-types for preload feature.
+// (We currectly do not support font/collection)
+static uint32_t StyleLinkElementFontMimeTypesNum = 5;
+static const char* StyleLinkElementFontMimeTypes[] = {
+ "font/otf", "font/sfnt", "font/ttf", "font/woff", "font/woff2"};
+
+bool IsFontMimeType(const nsAString& aType) {
+ if (aType.IsEmpty()) {
+ return true;
+ }
+ for (uint32_t i = 0; i < StyleLinkElementFontMimeTypesNum; i++) {
+ if (aType.EqualsASCII(StyleLinkElementFontMimeTypes[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static const nsAttrValue::EnumTable kAsAttributeTable[] = {
+ {"", DESTINATION_INVALID}, {"audio", DESTINATION_AUDIO},
+ {"font", DESTINATION_FONT}, {"image", DESTINATION_IMAGE},
+ {"script", DESTINATION_SCRIPT}, {"style", DESTINATION_STYLE},
+ {"track", DESTINATION_TRACK}, {"video", DESTINATION_VIDEO},
+ {"fetch", DESTINATION_FETCH}, {nullptr, 0}};
+
+void ParseAsValue(const nsAString& aValue, nsAttrValue& aResult) {
+ DebugOnly<bool> success =
+ aResult.ParseEnumValue(aValue, kAsAttributeTable, false,
+ // default value is a empty string
+ // if aValue is not a value we
+ // understand
+ &kAsAttributeTable[0]);
+ MOZ_ASSERT(success);
+}
+
+nsContentPolicyType AsValueToContentPolicy(const nsAttrValue& aValue) {
+ switch (aValue.GetEnumValue()) {
+ case DESTINATION_INVALID:
+ return nsIContentPolicy::TYPE_INVALID;
+ case DESTINATION_AUDIO:
+ return nsIContentPolicy::TYPE_INTERNAL_AUDIO;
+ case DESTINATION_TRACK:
+ return nsIContentPolicy::TYPE_INTERNAL_TRACK;
+ case DESTINATION_VIDEO:
+ return nsIContentPolicy::TYPE_INTERNAL_VIDEO;
+ case DESTINATION_FONT:
+ return nsIContentPolicy::TYPE_FONT;
+ case DESTINATION_IMAGE:
+ return nsIContentPolicy::TYPE_IMAGE;
+ case DESTINATION_SCRIPT:
+ return nsIContentPolicy::TYPE_SCRIPT;
+ case DESTINATION_STYLE:
+ return nsIContentPolicy::TYPE_STYLESHEET;
+ case DESTINATION_FETCH:
+ return nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD;
+ }
+ return nsIContentPolicy::TYPE_INVALID;
+}
+
+// TODO: implement this using nsAttrValue's destination enums when support for
+// the new destinations is added; see this diff for a possible start:
+// https://phabricator.services.mozilla.com/D172368?vs=705114&id=708720
+bool IsScriptLikeOrInvalid(const nsAString& aAs) {
+ return !(
+ aAs.LowerCaseEqualsASCII("fetch") || aAs.LowerCaseEqualsASCII("audio") ||
+ aAs.LowerCaseEqualsASCII("document") ||
+ aAs.LowerCaseEqualsASCII("embed") || aAs.LowerCaseEqualsASCII("font") ||
+ aAs.LowerCaseEqualsASCII("frame") || aAs.LowerCaseEqualsASCII("iframe") ||
+ aAs.LowerCaseEqualsASCII("image") ||
+ aAs.LowerCaseEqualsASCII("manifest") ||
+ aAs.LowerCaseEqualsASCII("object") ||
+ aAs.LowerCaseEqualsASCII("report") || aAs.LowerCaseEqualsASCII("style") ||
+ aAs.LowerCaseEqualsASCII("track") || aAs.LowerCaseEqualsASCII("video") ||
+ aAs.LowerCaseEqualsASCII("webidentity") ||
+ aAs.LowerCaseEqualsASCII("xslt"));
+}
+
+bool CheckPreloadAttrs(const nsAttrValue& aAs, const nsAString& aType,
+ const nsAString& aMedia,
+ mozilla::dom::Document* aDocument) {
+ nsContentPolicyType policyType = AsValueToContentPolicy(aAs);
+ if (policyType == nsIContentPolicy::TYPE_INVALID) {
+ return false;
+ }
+
+ // Check if media attribute is valid.
+ if (!aMedia.IsEmpty()) {
+ RefPtr<mozilla::dom::MediaList> mediaList =
+ mozilla::dom::MediaList::Create(NS_ConvertUTF16toUTF8(aMedia));
+ if (!mediaList->Matches(*aDocument)) {
+ return false;
+ }
+ }
+
+ if (aType.IsEmpty()) {
+ return true;
+ }
+
+ if (policyType == nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD) {
+ return true;
+ }
+
+ nsAutoString type(aType);
+ ToLowerCase(type);
+ if (policyType == nsIContentPolicy::TYPE_MEDIA) {
+ if (aAs.GetEnumValue() == DESTINATION_TRACK) {
+ return type.EqualsASCII("text/vtt");
+ }
+ Maybe<MediaContainerType> mimeType = MakeMediaContainerType(aType);
+ if (!mimeType) {
+ return false;
+ }
+ DecoderDoctorDiagnostics diagnostics;
+ CanPlayStatus status =
+ DecoderTraits::CanHandleContainerType(*mimeType, &diagnostics);
+ // Preload if this return CANPLAY_YES and CANPLAY_MAYBE.
+ return status != CANPLAY_NO;
+ }
+ if (policyType == nsIContentPolicy::TYPE_FONT) {
+ return IsFontMimeType(type);
+ }
+ if (policyType == nsIContentPolicy::TYPE_IMAGE) {
+ return imgLoader::SupportImageWithMimeType(
+ NS_ConvertUTF16toUTF8(type), AcceptedMimeTypes::IMAGES_AND_DOCUMENTS);
+ }
+ if (policyType == nsIContentPolicy::TYPE_SCRIPT) {
+ return nsContentUtils::IsJavascriptMIMEType(type);
+ }
+ if (policyType == nsIContentPolicy::TYPE_STYLESHEET) {
+ return type.EqualsASCII("text/css");
+ }
+ return false;
+}
+
+void WarnIgnoredPreload(const mozilla::dom::Document& aDoc, nsIURI& aURI) {
+ AutoTArray<nsString, 1> params;
+ {
+ nsCString uri = nsContentUtils::TruncatedURLForDisplay(&aURI);
+ AppendUTF8toUTF16(uri, *params.AppendElement());
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, &aDoc,
+ nsContentUtils::eDOM_PROPERTIES,
+ "PreloadIgnoredInvalidAttr", params);
+}
+
+nsresult HasRootDomain(const nsACString& aInput, const nsACString& aHost,
+ bool* aResult) {
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = false;
+
+ // If the strings are the same, we obviously have a match.
+ if (aInput == aHost) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ // If aHost is not found, we know we do not have it as a root domain.
+ int32_t index = nsAutoCString(aInput).Find(aHost);
+ if (index == kNotFound) {
+ return NS_OK;
+ }
+
+ // Otherwise, we have aHost as our root domain iff the index of aHost is
+ // aHost.length subtracted from our length and (since we do not have an
+ // exact match) the character before the index is a dot or slash.
+ *aResult = index > 0 && (uint32_t)index == aInput.Length() - aHost.Length() &&
+ (aInput[index - 1] == '.' || aInput[index - 1] == '/');
+ return NS_OK;
+}
+
+void CheckForBrokenChromeURL(nsILoadInfo* aLoadInfo, nsIURI* aURI) {
+ if (!aURI) {
+ return;
+ }
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("resource")) {
+ return;
+ }
+ nsAutoCString host;
+ aURI->GetHost(host);
+ // Ignore test hits.
+ if (host.EqualsLiteral("mochitests") || host.EqualsLiteral("reftest")) {
+ return;
+ }
+
+ nsAutoCString filePath;
+ aURI->GetFilePath(filePath);
+ // Fluent likes checking for files everywhere and expects failure.
+ if (StringEndsWith(filePath, ".ftl"_ns)) {
+ return;
+ }
+
+ // Ignore fetches/xhrs, as they are frequently used in a way where
+ // non-existence is OK (ie with fallbacks). This risks false negatives (ie
+ // files that *should* be there but aren't) - which we accept for now.
+ ExtContentPolicy policy = aLoadInfo
+ ? aLoadInfo->GetExternalContentPolicyType()
+ : ExtContentPolicy::TYPE_OTHER;
+ if (policy == ExtContentPolicy::TYPE_FETCH ||
+ policy == ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
+ return;
+ }
+
+ if (aLoadInfo) {
+ bool shouldSkipCheckForBrokenURLOrZeroSized;
+ MOZ_ALWAYS_SUCCEEDS(aLoadInfo->GetShouldSkipCheckForBrokenURLOrZeroSized(
+ &shouldSkipCheckForBrokenURLOrZeroSized));
+ if (shouldSkipCheckForBrokenURLOrZeroSized) {
+ return;
+ }
+ }
+
+ nsCString spec;
+ aURI->GetSpec(spec);
+
+#ifdef ANDROID
+ // Various toolkit files use this and are shipped on android, but
+ // info-pages.css and aboutLicense.css are not - bug 1808987
+ if (StringEndsWith(spec, "info-pages.css"_ns) ||
+ StringEndsWith(spec, "aboutLicense.css"_ns) ||
+ // Error page CSS is also missing: bug 1810039
+ StringEndsWith(spec, "aboutNetError.css"_ns) ||
+ StringEndsWith(spec, "aboutHttpsOnlyError.css"_ns) ||
+ StringEndsWith(spec, "error-pages.css"_ns) ||
+ // popup.css is used in a single mochitest: bug 1810577
+ StringEndsWith(spec, "/popup.css"_ns) ||
+ // Used by an extension installation test - bug 1809650
+ StringBeginsWith(spec, "resource://android/assets/web_extensions/"_ns)) {
+ return;
+ }
+#endif
+
+ // DTD files from gre may not exist when requested by tests.
+ if (StringBeginsWith(spec, "resource://gre/res/dtd/"_ns)) {
+ return;
+ }
+
+ // The background task machinery allows the caller to specify a JSM on the
+ // command line, which is then looked up in both app-specific and toolkit-wide
+ // locations.
+ if (spec.Find("backgroundtasks") != kNotFound) {
+ return;
+ }
+
+ if (xpc::IsInAutomation()) {
+#ifdef DEBUG
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
+ Unused << xpc->DebugDumpJSStack(false, false, false);
+ }
+#endif
+ MOZ_CRASH_UNSAFE_PRINTF("Missing chrome or resource URLs: %s", spec.get());
+ } else {
+ printf_stderr("Missing chrome or resource URL: %s\n", spec.get());
+ }
+}
+
+bool IsCoepCredentiallessEnabled(bool aIsOriginTrialCoepCredentiallessEnabled) {
+ return StaticPrefs::
+ browser_tabs_remote_coep_credentialless_DoNotUseDirectly() ||
+ aIsOriginTrialCoepCredentiallessEnabled;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h
new file mode 100644
index 0000000000..c1f30a56ed
--- /dev/null
+++ b/netwerk/base/nsNetUtil.h
@@ -0,0 +1,1105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNetUtil_h__
+#define nsNetUtil_h__
+
+#include <functional>
+#include "mozilla/Maybe.h"
+#include "mozilla/ResultExtensions.h"
+#include "nsAttrValue.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadGroup.h"
+#include "nsINestedURI.h"
+#include "nsINetUtil.h"
+#include "nsIRequest.h"
+#include "nsILoadInfo.h"
+#include "nsIIOService.h"
+#include "nsIURI.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "nsNetCID.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIPrincipal;
+class nsIAsyncStreamCopier;
+class nsIAuthPrompt;
+class nsIAuthPrompt2;
+class nsIChannel;
+class nsIChannelPolicy;
+class nsICookieJarSettings;
+class nsIDownloadObserver;
+class nsIEventTarget;
+class nsIFileProtocolHandler;
+class nsIFileRandomAccessStream;
+class nsIHttpChannel;
+class nsIInputStream;
+class nsIInputStreamPump;
+class nsIInterfaceRequestor;
+class nsIOutputStream;
+class nsIParentChannel;
+class nsIPersistentProperties;
+class nsIProxyInfo;
+class nsIRandomAccessStream;
+class nsIRequestObserver;
+class nsISerialEventTarget;
+class nsIStreamListener;
+class nsIStreamLoader;
+class nsIStreamLoaderObserver;
+class nsIIncrementalStreamLoader;
+class nsIIncrementalStreamLoaderObserver;
+
+namespace mozilla {
+class Encoding;
+class OriginAttributes;
+class OriginTrials;
+namespace dom {
+class ClientInfo;
+class PerformanceStorage;
+class ServiceWorkerDescriptor;
+} // namespace dom
+
+namespace ipc {
+class FileDescriptor;
+} // namespace ipc
+
+} // namespace mozilla
+
+template <class>
+class nsCOMPtr;
+template <typename>
+struct already_AddRefed;
+
+already_AddRefed<nsIIOService> do_GetIOService(nsresult* error = nullptr);
+
+already_AddRefed<nsINetUtil> do_GetNetUtil(nsresult* error = nullptr);
+
+// private little helper function... don't call this directly!
+nsresult net_EnsureIOService(nsIIOService** ios, nsCOMPtr<nsIIOService>& grip);
+
+nsresult NS_NewURI(nsIURI** aURI, const nsACString& spec,
+ const char* charset = nullptr, nsIURI* baseURI = nullptr);
+
+nsresult NS_NewURI(nsIURI** result, const nsACString& spec,
+ mozilla::NotNull<const mozilla::Encoding*> encoding,
+ nsIURI* baseURI = nullptr);
+
+nsresult NS_NewURI(nsIURI** result, const nsAString& spec,
+ const char* charset = nullptr, nsIURI* baseURI = nullptr);
+
+nsresult NS_NewURI(nsIURI** result, const nsAString& spec,
+ mozilla::NotNull<const mozilla::Encoding*> encoding,
+ nsIURI* baseURI = nullptr);
+
+nsresult NS_NewURI(nsIURI** result, const char* spec,
+ nsIURI* baseURI = nullptr);
+
+nsresult NS_NewFileURI(
+ nsIURI** result, nsIFile* spec,
+ nsIIOService* ioService =
+ nullptr); // pass in nsIIOService to optimize callers
+
+// Functions for adding additional encoding to a URL for compatibility with
+// Apple's NSURL class URLWithString method.
+//
+// @param aResult
+// Out parameter for the encoded URL spec
+// @param aSpec
+// The spec for the URL to be encoded
+nsresult NS_GetSpecWithNSURLEncoding(nsACString& aResult,
+ const nsACString& aSpec);
+// @param aResult
+// Out parameter for the encoded URI
+// @param aSpec
+// The spec for the URL to be encoded
+nsresult NS_NewURIWithNSURLEncoding(nsIURI** aResult, const nsACString& aSpec);
+
+// These methods will only mutate the URI if the ref of aInput doesn't already
+// match the ref we are trying to set.
+// If aInput has no ref, and we are calling NS_GetURIWithoutRef, or
+// NS_GetURIWithNewRef with an empty string, then aOutput will be the same
+// as aInput. The same is true if aRef is already equal to the ref of aInput.
+// This is OK because URIs are immutable and threadsafe.
+// If the URI doesn't support ref fragments aOutput will be the same as aInput.
+nsresult NS_GetURIWithNewRef(nsIURI* aInput, const nsACString& aRef,
+ nsIURI** aOutput);
+nsresult NS_GetURIWithoutRef(nsIURI* aInput, nsIURI** aOutput);
+
+nsresult NS_GetSanitizedURIStringFromURI(nsIURI* aUri,
+ nsAString& aSanitizedSpec);
+
+/*
+ * How to create a new Channel, using NS_NewChannel,
+ * NS_NewChannelWithTriggeringPrincipal,
+ * NS_NewInputStreamChannel, NS_NewChannelInternal
+ * and it's variations:
+ *
+ * What specific API function to use:
+ * * The NS_NewChannelInternal functions should almost never be directly
+ * called outside of necko code.
+ * * If possible, use NS_NewChannel() providing a loading *nsINode*
+ * * If no loading *nsINode* is available, try calling NS_NewChannel() providing
+ * a loading *ClientInfo*.
+ * * If no loading *nsINode* or *ClientInfo* are available, call NS_NewChannel()
+ * providing a loading *nsIPrincipal*.
+ * * Call NS_NewChannelWithTriggeringPrincipal if the triggeringPrincipal
+ * is different from the loadingPrincipal.
+ * * Call NS_NewChannelInternal() providing aLoadInfo object in cases where
+ * you already have loadInfo object, e.g in case of a channel redirect.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ *
+ * Note, if you provide a loading ClientInfo its principal must match the
+ * loading principal. Currently you must pass both as the loading principal
+ * may have additional mutable values like CSP on it. In the future these
+ * will be removed from nsIPrincipal and the API can be changed to take just
+ * the loading ClientInfo.
+ *
+ * Keep in mind that URIs coming from a webpage should *never* use the
+ * systemPrincipal as the loadingPrincipal.
+ */
+nsresult NS_NewChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ const mozilla::Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo,
+ const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings = nullptr,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0,
+ bool aSkipCheckForBrokenURLOrZeroSized = false);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, nsILoadInfo* aLoadInfo,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */
+NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode,
+ nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings = nullptr,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannelWithTriggeringPrincipal(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ const mozilla::dom::ClientInfo& aLoadingClientInfo,
+ const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings = nullptr,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannel(
+ nsIChannel** outChannel, nsIURI* aUri, nsINode* aLoadingNode,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0,
+ bool aSkipCheckForBrokenURLOrZeroSized = false);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannel(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings = nullptr,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0,
+ bool aSkipCheckForBrokenURLOrZeroSized = false);
+
+// See NS_NewChannelInternal for usage and argument description
+nsresult NS_NewChannel(
+ nsIChannel** outChannel, nsIURI* aUri, nsIPrincipal* aLoadingPrincipal,
+ const mozilla::dom::ClientInfo& aLoadingClientInfo,
+ const mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ nsICookieJarSettings* aCookieJarSettings = nullptr,
+ mozilla::dom::PerformanceStorage* aPerformanceStorage = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL,
+ nsIIOService* aIoService = nullptr, uint32_t aSandboxFlags = 0,
+ bool aSkipCheckForBrokenURLOrZeroSized = false);
+
+nsresult NS_GetIsDocumentChannel(nsIChannel* aChannel, bool* aIsDocument);
+
+nsresult NS_MakeAbsoluteURI(nsACString& result, const nsACString& spec,
+ nsIURI* baseURI);
+
+nsresult NS_MakeAbsoluteURI(char** result, const char* spec, nsIURI* baseURI);
+
+nsresult NS_MakeAbsoluteURI(nsAString& result, const nsAString& spec,
+ nsIURI* baseURI);
+
+/**
+ * This function is a helper function to get a scheme's default port.
+ */
+int32_t NS_GetDefaultPort(const char* scheme,
+ nsIIOService* ioService = nullptr);
+
+/**
+ * This function is a helper function to apply the ToAscii conversion
+ * to a string
+ */
+bool NS_StringToACE(const nsACString& idn, nsACString& result);
+
+/**
+ * This function is a helper function to get a protocol's default port if the
+ * URI does not specify a port explicitly. Returns -1 if this protocol has no
+ * concept of ports or if there was an error getting the port.
+ */
+int32_t NS_GetRealPort(nsIURI* aURI);
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType,
+ const nsACString& aContentCharset, nsILoadInfo* aLoadInfo);
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream, const nsACString& aContentType,
+ const nsACString& aContentCharset, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType);
+
+nsresult NS_NewInputStreamChannel(nsIChannel** outChannel, nsIURI* aUri,
+ already_AddRefed<nsIInputStream> aStream,
+ nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ const nsACString& aContentType = ""_ns,
+ const nsACString& aContentCharset = ""_ns);
+
+nsresult NS_NewInputStreamChannelInternal(
+ nsIChannel** outChannel, nsIURI* aUri, const nsAString& aData,
+ const nsACString& aContentType, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel = false);
+
+nsresult NS_NewInputStreamChannelInternal(nsIChannel** outChannel, nsIURI* aUri,
+ const nsAString& aData,
+ const nsACString& aContentType,
+ nsILoadInfo* aLoadInfo,
+ bool aIsSrcdocChannel = false);
+
+nsresult NS_NewInputStreamChannel(nsIChannel** outChannel, nsIURI* aUri,
+ const nsAString& aData,
+ const nsACString& aContentType,
+ nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ bool aIsSrcdocChannel = false);
+
+nsresult NS_NewInputStreamPump(
+ nsIInputStreamPump** aResult, already_AddRefed<nsIInputStream> aStream,
+ uint32_t aSegsize = 0, uint32_t aSegcount = 0, bool aCloseWhenDone = false,
+ nsISerialEventTarget* aMainThreadTarget = nullptr);
+
+nsresult NS_NewLoadGroup(nsILoadGroup** result, nsIRequestObserver* obs);
+
+// Create a new nsILoadGroup that will match the given principal.
+nsresult NS_NewLoadGroup(nsILoadGroup** aResult, nsIPrincipal* aPrincipal);
+
+// Determine if the given loadGroup/principal pair will produce a principal
+// with similar permissions when passed to NS_NewChannel(). This checks for
+// things like making sure the browser element flag matches. Without
+// an appropriate load group these values can be lost when getting the result
+// principal back out of the channel. Null principals are also always allowed
+// as they do not have permissions to actually use the load group.
+bool NS_LoadGroupMatchesPrincipal(nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal);
+
+nsresult NS_NewDownloader(nsIStreamListener** result,
+ nsIDownloadObserver* observer,
+ nsIFile* downloadLocation = nullptr);
+
+nsresult NS_NewStreamLoader(nsIStreamLoader** result,
+ nsIStreamLoaderObserver* observer,
+ nsIRequestObserver* requestObserver = nullptr);
+
+nsresult NS_NewIncrementalStreamLoader(
+ nsIIncrementalStreamLoader** result,
+ nsIIncrementalStreamLoaderObserver* observer);
+
+nsresult NS_NewStreamLoaderInternal(
+ nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver, nsINode* aLoadingNode,
+ nsIPrincipal* aLoadingPrincipal, nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType, nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL);
+
+nsresult NS_NewStreamLoader(nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver,
+ nsINode* aLoadingNode,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL);
+
+nsresult NS_NewStreamLoader(nsIStreamLoader** outStream, nsIURI* aUri,
+ nsIStreamLoaderObserver* aObserver,
+ nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags,
+ nsContentPolicyType aContentPolicyType,
+ nsILoadGroup* aLoadGroup = nullptr,
+ nsIInterfaceRequestor* aCallbacks = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL);
+
+nsresult NS_NewSyncStreamListener(nsIStreamListener** result,
+ nsIInputStream** stream);
+
+/**
+ * Implement the nsIChannel::Open(nsIInputStream**) method using the channel's
+ * AsyncOpen method.
+ *
+ * NOTE: Reading from the returned nsIInputStream may spin the current
+ * thread's event queue, which could result in any event being processed.
+ */
+nsresult NS_ImplementChannelOpen(nsIChannel* channel, nsIInputStream** result);
+
+nsresult NS_NewRequestObserverProxy(nsIRequestObserver** result,
+ nsIRequestObserver* observer,
+ nsISupports* context);
+
+nsresult NS_NewSimpleStreamListener(nsIStreamListener** result,
+ nsIOutputStream* sink,
+ nsIRequestObserver* observer = nullptr);
+
+nsresult NS_CheckPortSafety(int32_t port, const char* scheme,
+ nsIIOService* ioService = nullptr);
+
+// Determine if this URI is using a safe port.
+nsresult NS_CheckPortSafety(nsIURI* uri);
+
+nsresult NS_NewProxyInfo(const nsACString& type, const nsACString& host,
+ int32_t port, uint32_t flags, nsIProxyInfo** result);
+
+nsresult NS_GetFileProtocolHandler(nsIFileProtocolHandler** result,
+ nsIIOService* ioService = nullptr);
+
+nsresult NS_GetFileFromURLSpec(const nsACString& inURL, nsIFile** result,
+ nsIIOService* ioService = nullptr);
+
+nsresult NS_GetURLSpecFromFile(nsIFile* file, nsACString& url,
+ nsIIOService* ioService = nullptr);
+
+/**
+ * Converts the nsIFile to the corresponding URL string.
+ * Should only be called on files which are not directories,
+ * is otherwise identical to NS_GetURLSpecFromFile, but is
+ * usually more efficient.
+ * Warning: this restriction may not be enforced at runtime!
+ */
+nsresult NS_GetURLSpecFromActualFile(nsIFile* file, nsACString& url,
+ nsIIOService* ioService = nullptr);
+
+/**
+ * Converts the nsIFile to the corresponding URL string.
+ * Should only be called on files which are directories,
+ * is otherwise identical to NS_GetURLSpecFromFile, but is
+ * usually more efficient.
+ * Warning: this restriction may not be enforced at runtime!
+ */
+nsresult NS_GetURLSpecFromDir(nsIFile* file, nsACString& url,
+ nsIIOService* ioService = nullptr);
+
+/**
+ * Obtains the referrer for a given channel. This first tries to obtain the
+ * referrer from the property docshell.internalReferrer, and if that doesn't
+ * work and the channel is an nsIHTTPChannel, we check it's referrer property.
+ *
+ */
+void NS_GetReferrerFromChannel(nsIChannel* channel, nsIURI** referrer);
+
+nsresult NS_ParseRequestContentType(const nsACString& rawContentType,
+ nsCString& contentType,
+ nsCString& contentCharset);
+
+nsresult NS_ParseResponseContentType(const nsACString& rawContentType,
+ nsCString& contentType,
+ nsCString& contentCharset);
+
+nsresult NS_ExtractCharsetFromContentType(const nsACString& rawContentType,
+ nsCString& contentCharset,
+ bool* hadCharset,
+ int32_t* charsetStart,
+ int32_t* charsetEnd);
+
+nsresult NS_NewLocalFileInputStream(nsIInputStream** result, nsIFile* file,
+ int32_t ioFlags = -1, int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+mozilla::Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewLocalFileInputStream(
+ nsIFile* file, int32_t ioFlags = -1, int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result, nsIFile* file,
+ int32_t ioFlags = -1, int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+mozilla::Result<nsCOMPtr<nsIOutputStream>, nsresult>
+NS_NewLocalFileOutputStream(nsIFile* file, int32_t ioFlags = -1,
+ int32_t perm = -1, int32_t behaviorFlags = 0);
+
+nsresult NS_NewLocalFileOutputStream(nsIOutputStream** result,
+ const mozilla::ipc::FileDescriptor& fd);
+
+// returns a file output stream which can be QI'ed to nsISafeOutputStream.
+nsresult NS_NewAtomicFileOutputStream(nsIOutputStream** result, nsIFile* file,
+ int32_t ioFlags = -1, int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+// returns a file output stream which can be QI'ed to nsISafeOutputStream.
+nsresult NS_NewSafeLocalFileOutputStream(nsIOutputStream** result,
+ nsIFile* file, int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+nsresult NS_NewLocalFileRandomAccessStream(nsIRandomAccessStream** result,
+ nsIFile* file, int32_t ioFlags = -1,
+ int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+mozilla::Result<nsCOMPtr<nsIRandomAccessStream>, nsresult>
+NS_NewLocalFileRandomAccessStream(nsIFile* file, int32_t ioFlags = -1,
+ int32_t perm = -1, int32_t behaviorFlags = 0);
+
+[[nodiscard]] nsresult NS_NewBufferedInputStream(
+ nsIInputStream** aResult, already_AddRefed<nsIInputStream> aInputStream,
+ uint32_t aBufferSize);
+
+mozilla::Result<nsCOMPtr<nsIInputStream>, nsresult> NS_NewBufferedInputStream(
+ already_AddRefed<nsIInputStream> aInputStream, uint32_t aBufferSize);
+
+// note: the resulting stream can be QI'ed to nsISafeOutputStream iff the
+// provided stream supports it.
+nsresult NS_NewBufferedOutputStream(
+ nsIOutputStream** aResult, already_AddRefed<nsIOutputStream> aOutputStream,
+ uint32_t aBufferSize);
+
+/**
+ * This function reads an inputStream and stores its content into a buffer. In
+ * general, you should avoid using this function because, it blocks the current
+ * thread until the operation is done.
+ * If the inputStream is async, the reading happens on an I/O thread.
+ *
+ * @param aInputStream the inputStream.
+ * @param aDest the destination buffer. if *aDest is null, it will be allocated
+ * with the size of the written data. if aDest is not null, aCount
+ * must greater than 0.
+ * @param aCount the amount of data to read. Use -1 if you want that all the
+ * stream is read.
+ * @param aWritten this pointer will be used to store the number of data
+ * written in the buffer. If you don't need, pass nullptr.
+ */
+nsresult NS_ReadInputStreamToBuffer(nsIInputStream* aInputStream, void** aDest,
+ int64_t aCount,
+ uint64_t* aWritten = nullptr);
+
+/**
+ * See the comment for NS_ReadInputStreamToBuffer
+ */
+nsresult NS_ReadInputStreamToString(nsIInputStream* aInputStream,
+ nsACString& aDest, int64_t aCount,
+ uint64_t* aWritten = nullptr);
+
+nsresult NS_LoadPersistentPropertiesFromURISpec(
+ nsIPersistentProperties** outResult, const nsACString& aSpec);
+
+/**
+ * NS_QueryNotificationCallbacks implements the canonical algorithm for
+ * querying interfaces from a channel's notification callbacks. It first
+ * searches the channel's notificationCallbacks attribute, and if the interface
+ * is not found there, then it inspects the notificationCallbacks attribute of
+ * the channel's loadGroup.
+ *
+ * Note: templatized only because nsIWebSocketChannel is currently not an
+ * nsIChannel.
+ */
+template <class T>
+inline void NS_QueryNotificationCallbacks(T* channel, const nsIID& iid,
+ void** result) {
+ MOZ_ASSERT(channel, "null channel");
+ *result = nullptr;
+
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ mozilla::Unused << channel->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs) cbs->GetInterface(iid, result);
+ if (!*result) {
+ // try load group's notification callbacks...
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mozilla::Unused << channel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs) cbs->GetInterface(iid, result);
+ }
+ }
+}
+
+// template helper:
+// Note: "class C" templatized only because nsIWebSocketChannel is currently not
+// an nsIChannel.
+
+template <class C, class T>
+inline void NS_QueryNotificationCallbacks(C* channel, nsCOMPtr<T>& result) {
+ NS_QueryNotificationCallbacks(channel, NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(result));
+}
+
+/**
+ * Alternate form of NS_QueryNotificationCallbacks designed for use by
+ * nsIChannel implementations.
+ */
+inline void NS_QueryNotificationCallbacks(nsIInterfaceRequestor* callbacks,
+ nsILoadGroup* loadGroup,
+ const nsIID& iid, void** result) {
+ *result = nullptr;
+
+ if (callbacks) callbacks->GetInterface(iid, result);
+ if (!*result) {
+ // try load group's notification callbacks...
+ if (loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ if (cbs) cbs->GetInterface(iid, result);
+ }
+ }
+}
+
+/**
+ * Returns true if channel is using Private Browsing, or false if not.
+ * Returns false if channel's callbacks don't implement nsILoadContext.
+ */
+bool NS_UsePrivateBrowsing(nsIChannel* channel);
+
+/**
+ * Returns true if the channel has visited any cross-origin URLs on any
+ * URLs that it was redirected through.
+ */
+bool NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport = false);
+
+/**
+ * Returns true if the channel has a safe method.
+ */
+bool NS_IsSafeMethodNav(nsIChannel* aChannel);
+
+// Unique first-party domain for separating the safebrowsing cookie.
+// Note if this value is changed, code in test_cookiejars_safebrowsing.js and
+// nsUrlClassifierHashCompleter.js should also be changed.
+#define NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN \
+ "safebrowsing.86868755-6b82-4842-b301-72671a0db32e.mozilla"
+
+// Unique first-party domain for separating about uri.
+#define ABOUT_URI_FIRST_PARTY_DOMAIN \
+ "about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla"
+
+/**
+ * Wraps an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2. This
+ * method is provided mainly for use by other methods in this file.
+ *
+ * *aAuthPrompt2 should be set to null before calling this function.
+ */
+void NS_WrapAuthPrompt(nsIAuthPrompt* aAuthPrompt,
+ nsIAuthPrompt2** aAuthPrompt2);
+
+/**
+ * Gets an auth prompt from an interface requestor. This takes care of wrapping
+ * an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2.
+ */
+void NS_QueryAuthPrompt2(nsIInterfaceRequestor* aCallbacks,
+ nsIAuthPrompt2** aAuthPrompt);
+
+/**
+ * Gets an nsIAuthPrompt2 from a channel. Use this instead of
+ * NS_QueryNotificationCallbacks for better backwards compatibility.
+ */
+void NS_QueryAuthPrompt2(nsIChannel* aChannel, nsIAuthPrompt2** aAuthPrompt);
+
+/* template helper */
+template <class T>
+inline void NS_QueryNotificationCallbacks(nsIInterfaceRequestor* callbacks,
+ nsILoadGroup* loadGroup,
+ nsCOMPtr<T>& result) {
+ NS_QueryNotificationCallbacks(callbacks, loadGroup, NS_GET_TEMPLATE_IID(T),
+ getter_AddRefs(result));
+}
+
+/* template helper */
+template <class T>
+inline void NS_QueryNotificationCallbacks(
+ const nsCOMPtr<nsIInterfaceRequestor>& aCallbacks,
+ const nsCOMPtr<nsILoadGroup>& aLoadGroup, nsCOMPtr<T>& aResult) {
+ NS_QueryNotificationCallbacks(aCallbacks.get(), aLoadGroup.get(), aResult);
+}
+
+/* template helper */
+template <class T>
+inline void NS_QueryNotificationCallbacks(const nsCOMPtr<nsIChannel>& aChannel,
+ nsCOMPtr<T>& aResult) {
+ NS_QueryNotificationCallbacks(aChannel.get(), aResult);
+}
+
+/**
+ * This function returns a nsIInterfaceRequestor instance that returns the
+ * same result as NS_QueryNotificationCallbacks when queried. It is useful
+ * as the value for nsISocketTransport::securityCallbacks.
+ */
+nsresult NS_NewNotificationCallbacksAggregation(
+ nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup,
+ nsIEventTarget* target, nsIInterfaceRequestor** result);
+
+nsresult NS_NewNotificationCallbacksAggregation(
+ nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup,
+ nsIInterfaceRequestor** result);
+
+/**
+ * Helper function for testing online/offline state of the browser.
+ */
+bool NS_IsOffline();
+
+/**
+ * Helper functions for implementing nsINestedURI::innermostURI.
+ *
+ * Note that NS_DoImplGetInnermostURI is "private" -- call
+ * NS_ImplGetInnermostURI instead.
+ */
+nsresult NS_DoImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result);
+
+nsresult NS_ImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result);
+
+/**
+ * Helper function for testing whether the given URI, or any of its
+ * inner URIs, has all the given protocol flags.
+ */
+nsresult NS_URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result);
+
+/**
+ * Helper function for getting the innermost URI for a given URI. The return
+ * value could be just the object passed in if it's not a nested URI.
+ */
+already_AddRefed<nsIURI> NS_GetInnermostURI(nsIURI* aURI);
+
+/**
+ * Helper function for getting the host name of the innermost URI for a given
+ * URI. The return value could be the host name of the URI passed in if it's
+ * not a nested URI.
+ */
+inline nsresult NS_GetInnermostURIHost(nsIURI* aURI, nsACString& aHost) {
+ aHost.Truncate();
+
+ // This block is optimized in order to avoid the overhead of calling
+ // NS_GetInnermostURI() which incurs a lot of overhead in terms of
+ // AddRef/Release calls.
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(aURI);
+ if (nestedURI) {
+ // We have a nested URI!
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = nestedURI->GetInnermostURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = uri->GetAsciiHost(aHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ // We have a non-nested URI!
+ nsresult rv = aURI->GetAsciiHost(aHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Get the "final" URI for a channel. This is either channel's load info
+ * resultPrincipalURI, if set, or GetOriginalURI. In most cases (but not all)
+ * load info resultPrincipalURI, if set, corresponds to URI of the channel if
+ * it's required to represent the actual principal for the channel.
+ */
+nsresult NS_GetFinalChannelURI(nsIChannel* channel, nsIURI** uri);
+
+// NS_SecurityHashURI must return the same hash value for any two URIs that
+// compare equal according to NS_SecurityCompareURIs. Unfortunately, in the
+// case of files, it's not clear we can do anything better than returning
+// the schemeHash, so hashing files degenerates to storing them in a list.
+uint32_t NS_SecurityHashURI(nsIURI* aURI);
+
+bool NS_SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI,
+ bool aStrictFileOriginPolicy);
+
+bool NS_URIIsLocalFile(nsIURI* aURI);
+
+// When strict file origin policy is enabled, SecurityCompareURIs will fail for
+// file URIs that do not point to the same local file. This call provides an
+// alternate file-specific origin check that allows target files that are
+// contained in the same directory as the source.
+//
+// https://developer.mozilla.org/en-US/docs/Same-origin_policy_for_file:_URIs
+bool NS_RelaxStrictFileOriginPolicy(nsIURI* aTargetURI, nsIURI* aSourceURI,
+ bool aAllowDirectoryTarget = false);
+
+bool NS_IsInternalSameURIRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel, uint32_t aFlags);
+
+bool NS_IsHSTSUpgradeRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel,
+ uint32_t aFlags);
+
+bool NS_ShouldRemoveAuthHeaderOnRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags);
+
+nsresult NS_LinkRedirectChannels(uint64_t channelId,
+ nsIParentChannel* parentChannel,
+ nsIChannel** _result);
+
+/**
+ * Returns nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP if `aHeader` is
+ * "require-corp" and nsILoadInfo::EMBEDDER_POLICY_NULL otherwise.
+ *
+ * See: https://mikewest.github.io/corpp/#parsing
+ */
+nsILoadInfo::CrossOriginEmbedderPolicy
+NS_GetCrossOriginEmbedderPolicyFromHeader(
+ const nsACString& aHeader, bool aIsOriginTrialCoepCredentiallessEnabled);
+
+/** Given the first (disposition) token from a Content-Disposition header,
+ * tell whether it indicates the content is inline or attachment
+ * @param aDispToken the disposition token from the content-disposition header
+ */
+uint32_t NS_GetContentDispositionFromToken(const nsAString& aDispToken);
+
+/** Determine the disposition (inline/attachment) of the content based on the
+ * Content-Disposition header
+ * @param aHeader the content-disposition header (full value)
+ * @param aChan the channel the header came from
+ */
+uint32_t NS_GetContentDispositionFromHeader(const nsACString& aHeader,
+ nsIChannel* aChan = nullptr);
+
+/** Extracts the filename out of a content-disposition header
+ * @param aFilename [out] The filename. Can be empty on error.
+ * @param aDisposition Value of a Content-Disposition header
+
+ */
+nsresult NS_GetFilenameFromDisposition(nsAString& aFilename,
+ const nsACString& aDisposition);
+
+/**
+ * Make sure Personal Security Manager is initialized
+ */
+void net_EnsurePSMInit();
+
+/**
+ * Test whether a URI is "about:blank". |uri| must not be null
+ */
+bool NS_IsAboutBlank(nsIURI* uri);
+
+/**
+ * Test whether a URI is "about:srcdoc". |uri| must not be null
+ */
+bool NS_IsAboutSrcdoc(nsIURI* uri);
+
+nsresult NS_GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine);
+
+/**
+ * Sniff the content type for a given request or a given buffer.
+ *
+ * aSnifferType can be either NS_CONTENT_SNIFFER_CATEGORY or
+ * NS_DATA_SNIFFER_CATEGORY. The function returns the sniffed content type
+ * in the aSniffedType argument. This argument will not be modified if the
+ * content type could not be sniffed.
+ */
+void NS_SniffContent(const char* aSnifferType, nsIRequest* aRequest,
+ const uint8_t* aData, uint32_t aLength,
+ nsACString& aSniffedType);
+
+/**
+ * Whether the channel was created to load a srcdoc document.
+ * Note that view-source:about:srcdoc is classified as a srcdoc document by
+ * this function, which may not be applicable everywhere.
+ */
+bool NS_IsSrcdocChannel(nsIChannel* aChannel);
+
+/**
+ * Return true if the given string is a reasonable HTTP header value given the
+ * definition in RFC 2616 section 4.2. Currently we don't pay the cost to do
+ * full, sctrict validation here since it would require fulling parsing the
+ * value.
+ */
+bool NS_IsReasonableHTTPHeaderValue(const nsACString& aValue);
+
+/**
+ * Return true if the given string is a valid HTTP token per RFC 2616 section
+ * 2.2.
+ */
+bool NS_IsValidHTTPToken(const nsACString& aToken);
+
+/**
+ * Strip the leading or trailing HTTP whitespace per fetch spec section 2.2.
+ */
+void NS_TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest);
+
+template <typename Char>
+constexpr bool NS_IsHTTPTokenPoint(Char aChar) {
+ using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
+ auto c = static_cast<UnsignedChar>(aChar);
+ return c == '!' || c == '#' || c == '$' || c == '%' || c == '&' ||
+ c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' ||
+ c == '^' || c == '_' || c == '`' || c == '|' || c == '~' ||
+ mozilla::IsAsciiAlphanumeric(c);
+}
+
+template <typename Char>
+constexpr bool NS_IsHTTPQuotedStringTokenPoint(Char aChar) {
+ using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
+ auto c = static_cast<UnsignedChar>(aChar);
+ return c == 0x9 || (c >= ' ' && c <= '~') || mozilla::IsNonAsciiLatin1(c);
+}
+
+template <typename Char>
+constexpr bool NS_IsHTTPWhitespace(Char aChar) {
+ using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
+ auto c = static_cast<UnsignedChar>(aChar);
+ return c == 0x9 || c == 0xA || c == 0xD || c == 0x20;
+}
+
+/**
+ * Return true if the given request must be upgraded to HTTPS.
+ * If |aResultCallback| is provided and the storage is not ready to read, the
+ * result will be sent back through the callback and |aWillCallback| will be
+ * true. Otherwiew, the result will be set to |aShouldUpgrade| and
+ * |aWillCallback| is false.
+ */
+nsresult NS_ShouldSecureUpgrade(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIPrincipal* aChannelResultPrincipal,
+ bool aAllowSTS, const mozilla::OriginAttributes& aOriginAttributes,
+ bool& aShouldUpgrade, std::function<void(bool, nsresult)>&& aResultCallback,
+ bool& aWillCallback);
+
+/**
+ * Returns an https URI for channels that need to go through secure upgrades.
+ */
+nsresult NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI);
+
+nsresult NS_CompareLoadInfoAndLoadContext(nsIChannel* aChannel);
+
+/**
+ * Return true if this channel should be classified by the URL classifier.
+ */
+bool NS_ShouldClassifyChannel(nsIChannel* aChannel);
+
+/**
+ * Helper to set the blocking reason on loadinfo of the channel.
+ */
+nsresult NS_SetRequestBlockingReason(nsIChannel* channel, uint32_t reason);
+nsresult NS_SetRequestBlockingReason(nsILoadInfo* loadInfo, uint32_t reason);
+nsresult NS_SetRequestBlockingReasonIfNull(nsILoadInfo* loadInfo,
+ uint32_t reason);
+
+namespace mozilla {
+namespace net {
+
+const static uint64_t kJS_MAX_SAFE_UINTEGER = +9007199254740991ULL;
+const static int64_t kJS_MIN_SAFE_INTEGER = -9007199254740991LL;
+const static int64_t kJS_MAX_SAFE_INTEGER = +9007199254740991LL;
+
+// Make sure a 64bit value can be captured by JS MAX_SAFE_INTEGER
+bool InScriptableRange(int64_t val);
+
+// Make sure a 64bit value can be captured by JS MAX_SAFE_INTEGER
+bool InScriptableRange(uint64_t val);
+
+/**
+ * Given the value of a single header field (such as
+ * Content-Disposition and Content-Type) and the name of a parameter
+ * (e.g. filename, name, charset), returns the value of the parameter.
+ * See nsIMIMEHeaderParam.idl for more information.
+ *
+ * @param aHeaderVal a header string to get the value of a parameter
+ * from.
+ * @param aParamName the name of a MIME header parameter (e.g.
+ * filename, name, charset). If empty or nullptr,
+ * returns the first (possibly) _unnamed_ 'parameter'.
+ * @return the value of <code>aParamName</code> in Unichar(UTF-16).
+ */
+nsresult GetParameterHTTP(const nsACString& aHeaderVal, const char* aParamName,
+ nsAString& aResult);
+
+/**
+ * Helper function that determines if channel is an HTTP POST.
+ *
+ * @param aChannel
+ * The channel to test
+ *
+ * @return True if channel is an HTTP post.
+ */
+bool ChannelIsPost(nsIChannel* aChannel);
+
+/**
+ * Convenience functions for verifying nsIURI schemes. These functions simply
+ * wrap aURI->SchemeIs(), but specify the protocol as part of the function name.
+ */
+
+bool SchemeIsHTTP(nsIURI* aURI);
+bool SchemeIsHTTPS(nsIURI* aURI);
+bool SchemeIsJavascript(nsIURI* aURI);
+bool SchemeIsChrome(nsIURI* aURI);
+bool SchemeIsAbout(nsIURI* aURI);
+bool SchemeIsBlob(nsIURI* aURI);
+bool SchemeIsFile(nsIURI* aURI);
+bool SchemeIsData(nsIURI* aURI);
+bool SchemeIsViewSource(nsIURI* aURI);
+bool SchemeIsResource(nsIURI* aURI);
+bool SchemeIsFTP(nsIURI* aURI);
+
+// Helper functions for SetProtocol methods to follow
+// step 2.1 in https://url.spec.whatwg.org/#scheme-state
+bool SchemeIsSpecial(const nsACString&);
+bool IsSchemeChangePermitted(nsIURI*, const nsACString&);
+already_AddRefed<nsIURI> TryChangeProtocol(nsIURI*, const nsAString&);
+
+struct LinkHeader {
+ nsString mHref;
+ nsString mRel;
+ nsString mTitle;
+ nsString mNonce;
+ nsString mIntegrity;
+ nsString mSrcset;
+ nsString mSizes;
+ nsString mType;
+ nsString mMedia;
+ nsString mAnchor;
+ nsString mCrossOrigin;
+ nsString mReferrerPolicy;
+ nsString mAs;
+ nsString mFetchPriority;
+
+ LinkHeader();
+ void Reset();
+
+ nsresult NewResolveHref(nsIURI** aOutURI, nsIURI* aBaseURI) const;
+
+ bool operator==(const LinkHeader& rhs) const;
+
+ void MaybeUpdateAttribute(const nsAString& aAttribute,
+ const char16_t* aValue);
+};
+
+// Implements roughly step 2 to 4 of
+// <https://httpwg.org/specs/rfc8288.html#parse-set>.
+nsTArray<LinkHeader> ParseLinkHeader(const nsAString& aLinkData);
+
+enum ASDestination : uint8_t {
+ DESTINATION_INVALID,
+ DESTINATION_AUDIO,
+ DESTINATION_DOCUMENT,
+ DESTINATION_EMBED,
+ DESTINATION_FONT,
+ DESTINATION_IMAGE,
+ DESTINATION_MANIFEST,
+ DESTINATION_OBJECT,
+ DESTINATION_REPORT,
+ DESTINATION_SCRIPT,
+ DESTINATION_SERVICEWORKER,
+ DESTINATION_SHAREDWORKER,
+ DESTINATION_STYLE,
+ DESTINATION_TRACK,
+ DESTINATION_VIDEO,
+ DESTINATION_WORKER,
+ DESTINATION_XSLT,
+ DESTINATION_FETCH
+};
+
+void ParseAsValue(const nsAString& aValue, nsAttrValue& aResult);
+nsContentPolicyType AsValueToContentPolicy(const nsAttrValue& aValue);
+bool IsScriptLikeOrInvalid(const nsAString& aAs);
+
+bool CheckPreloadAttrs(const nsAttrValue& aAs, const nsAString& aType,
+ const nsAString& aMedia,
+ mozilla::dom::Document* aDocument);
+void WarnIgnoredPreload(const mozilla::dom::Document&, nsIURI&);
+
+/**
+ * Returns true if the |aInput| in is part of the root domain of |aHost|.
+ * For example, if |aInput| is "www.mozilla.org", and we pass in
+ * "mozilla.org" as |aHost|, this will return true. It would return false
+ * the other way around.
+ *
+ * @param aInput The host to be analyzed.
+ * @param aHost The host to compare to.
+ */
+nsresult HasRootDomain(const nsACString& aInput, const nsACString& aHost,
+ bool* aResult);
+
+void CheckForBrokenChromeURL(nsILoadInfo* aLoadInfo, nsIURI* aURI);
+
+bool IsCoepCredentiallessEnabled(bool aIsOriginTrialCoepCredentiallessEnabled);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsNetUtil_h__
diff --git a/netwerk/base/nsPACMan.cpp b/netwerk/base/nsPACMan.cpp
new file mode 100644
index 0000000000..0cf66c8ee6
--- /dev/null
+++ b/netwerk/base/nsPACMan.cpp
@@ -0,0 +1,1009 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPACMan.h"
+
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIAuthPrompt.h"
+#include "nsIDHCPClient.h"
+#include "nsIHttpChannel.h"
+#include "nsIPrefBranch.h"
+#include "nsIPromptFactory.h"
+#include "nsIProtocolProxyService.h"
+#include "nsISystemProxySettings.h"
+#include "nsIOService.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Try.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gProxyLog("proxy");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
+#define MOZ_WPAD_URL "http://wpad/wpad.dat"
+#define MOZ_DHCP_WPAD_OPTION 252
+
+// These pointers are declared in nsProtocolProxyService.cpp
+extern const char kProxyType_HTTPS[];
+extern const char kProxyType_DIRECT[];
+
+// The PAC thread does evaluations of both PAC files and
+// nsISystemProxySettings because they can both block the calling thread and we
+// don't want that on the main thread
+
+// Check to see if the underlying request was not an error page in the case of
+// a HTTP request. For other types of channels, just return true.
+static bool HttpRequestSucceeded(nsIStreamLoader* loader) {
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+
+ bool result = true; // default to assuming success
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
+ if (httpChannel) {
+ // failsafe
+ Unused << httpChannel->GetRequestSucceeded(&result);
+ }
+
+ return result;
+}
+
+// Read preference setting of extra JavaScript context heap size.
+// PrefService tends to be run on main thread, where ProxyAutoConfig runs on
+// ProxyResolution thread, so it's read here and passed to ProxyAutoConfig.
+static uint32_t GetExtraJSContextHeapSize() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ static int32_t extraSize = -1;
+
+ if (extraSize < 0) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ int32_t value;
+
+ if (prefs &&
+ NS_SUCCEEDED(prefs->GetIntPref(
+ "network.proxy.autoconfig_extra_jscontext_heap_size", &value))) {
+ LOG(("autoconfig_extra_jscontext_heap_size: %d\n", value));
+
+ extraSize = value;
+ }
+ }
+
+ return extraSize < 0 ? 0 : extraSize;
+}
+
+// Read network proxy type from preference
+// Used to verify that the preference is WPAD in nsPACMan::ConfigureWPAD
+nsresult GetNetworkProxyTypeFromPref(int32_t* type) {
+ *type = 0;
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ if (!prefs) {
+ LOG(("Failed to get a preference service object"));
+ return NS_ERROR_FACTORY_NOT_REGISTERED;
+ }
+ nsresult rv = prefs->GetIntPref("network.proxy.type", type);
+ if (NS_FAILED(rv)) {
+ LOG(("Failed to retrieve network.proxy.type from prefs"));
+ return rv;
+ }
+ LOG(("network.proxy.type pref retrieved: %d\n", *type));
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+// The ExecuteCallback runnable is triggered by
+// nsPACManCallback::OnQueryComplete on the Main thread when its completion is
+// discovered on the pac thread
+
+class ExecuteCallback final : public Runnable {
+ public:
+ ExecuteCallback(nsPACManCallback* aCallback, nsresult status)
+ : Runnable("net::ExecuteCallback"),
+ mCallback(aCallback),
+ mStatus(status) {}
+
+ void SetPACString(const nsACString& pacString) { mPACString = pacString; }
+
+ void SetPACURL(const nsACString& pacURL) { mPACURL = pacURL; }
+
+ NS_IMETHOD Run() override {
+ mCallback->OnQueryComplete(mStatus, mPACString, mPACURL);
+ mCallback = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsPACManCallback> mCallback;
+ nsresult mStatus;
+ nsCString mPACString;
+ nsCString mPACURL;
+};
+
+//-----------------------------------------------------------------------------
+
+// The PAC thread must be deleted from the main thread, this class
+// acts as a proxy to do that, as the PACMan is reference counted
+// and might be destroyed on either thread
+
+class ShutdownThread final : public Runnable {
+ public:
+ explicit ShutdownThread(nsIThread* thread)
+ : Runnable("net::ShutdownThread"), mThread(thread) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mThread->Shutdown();
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+// Dispatch this to wait until the PAC thread shuts down.
+
+class WaitForThreadShutdown final : public Runnable {
+ public:
+ explicit WaitForThreadShutdown(nsPACMan* aPACMan)
+ : Runnable("net::WaitForThreadShutdown"), mPACMan(aPACMan) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mPACMan->mPACThread) {
+ mPACMan->mPACThread->Shutdown();
+ mPACMan->mPACThread = nullptr;
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsPACMan> mPACMan;
+};
+
+//-----------------------------------------------------------------------------
+
+// PACLoadComplete allows the PAC thread to tell the main thread that
+// the javascript PAC file has been installed (perhaps unsuccessfully)
+// and that there is no reason to queue executions anymore
+
+class PACLoadComplete final : public Runnable {
+ public:
+ explicit PACLoadComplete(nsPACMan* aPACMan)
+ : Runnable("net::PACLoadComplete"), mPACMan(aPACMan) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ {
+ auto loader = mPACMan->mLoader.Lock();
+ loader.ref() = nullptr;
+ }
+ mPACMan->PostProcessPendingQ();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsPACMan> mPACMan;
+};
+
+//-----------------------------------------------------------------------------
+
+// ConfigureWPADComplete allows the PAC thread to tell the main thread that
+// the URL for the PAC file has been found
+class ConfigureWPADComplete final : public Runnable {
+ public:
+ ConfigureWPADComplete(nsPACMan* aPACMan, const nsACString& aPACURISpec)
+ : Runnable("net::ConfigureWPADComplete"),
+ mPACMan(aPACMan),
+ mPACURISpec(aPACURISpec) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mPACMan->AssignPACURISpec(mPACURISpec);
+ mPACMan->ContinueLoadingAfterPACUriKnown();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsPACMan> mPACMan;
+ nsCString mPACURISpec;
+};
+
+//-----------------------------------------------------------------------------
+
+// ExecutePACThreadAction is used to proxy actions from the main
+// thread onto the PAC thread. There are 4 options: process the queue,
+// cancel the queue, query DHCP for the PAC option
+// and setup the javascript context with a new PAC file
+
+class ExecutePACThreadAction final : public Runnable {
+ public:
+ // by default we just process the queue
+ explicit ExecutePACThreadAction(nsPACMan* aPACMan)
+ : Runnable("net::ExecutePACThreadAction"),
+ mPACMan(aPACMan),
+ mCancel(false),
+ mCancelStatus(NS_OK),
+ mSetupPAC(false),
+ mExtraHeapSize(0),
+ mConfigureWPAD(false),
+ mShutdown(false) {}
+
+ void CancelQueue(nsresult status, bool aShutdown) {
+ mCancel = true;
+ mCancelStatus = status;
+ mShutdown = aShutdown;
+ }
+
+ void SetupPAC(const char* data, uint32_t dataLen, const nsACString& pacURI,
+ uint32_t extraHeapSize) {
+ mSetupPAC = true;
+ mSetupPACData.Assign(data, dataLen);
+ mSetupPACURI = pacURI;
+ mExtraHeapSize = extraHeapSize;
+ }
+
+ void ConfigureWPAD() { mConfigureWPAD = true; }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ if (mCancel) {
+ mPACMan->CancelPendingQ(mCancelStatus, mShutdown);
+ mCancel = false;
+ return NS_OK;
+ }
+
+ if (mSetupPAC) {
+ mSetupPAC = false;
+
+ nsCOMPtr<nsISerialEventTarget> target = mPACMan->GetNeckoTarget();
+ mPACMan->mPAC->ConfigurePAC(mSetupPACURI, mSetupPACData,
+ mPACMan->mIncludePath, mExtraHeapSize,
+ target);
+
+ RefPtr<PACLoadComplete> runnable = new PACLoadComplete(mPACMan);
+ mPACMan->Dispatch(runnable.forget());
+ return NS_OK;
+ }
+
+ if (mConfigureWPAD) {
+ nsAutoCString spec;
+ mConfigureWPAD = false;
+ mPACMan->ConfigureWPAD(spec);
+ RefPtr<ConfigureWPADComplete> runnable =
+ new ConfigureWPADComplete(mPACMan, spec);
+ mPACMan->Dispatch(runnable.forget());
+ return NS_OK;
+ }
+
+ mPACMan->ProcessPendingQ();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsPACMan> mPACMan;
+
+ bool mCancel;
+ nsresult mCancelStatus;
+
+ bool mSetupPAC;
+ uint32_t mExtraHeapSize;
+ nsCString mSetupPACData;
+ nsCString mSetupPACURI;
+ bool mConfigureWPAD;
+ bool mShutdown;
+};
+
+//-----------------------------------------------------------------------------
+
+PendingPACQuery::PendingPACQuery(nsPACMan* pacMan, nsIURI* uri,
+ nsPACManCallback* callback, uint32_t flags,
+ bool mainThreadResponse)
+ : Runnable("net::PendingPACQuery"),
+ mPort(0),
+ mFlags(flags),
+ mPACMan(pacMan),
+ mCallback(callback),
+ mOnMainThreadOnly(mainThreadResponse) {
+ uri->GetAsciiSpec(mSpec);
+ uri->GetAsciiHost(mHost);
+ uri->GetScheme(mScheme);
+ uri->GetPort(&mPort);
+}
+
+void PendingPACQuery::Complete(nsresult status, const nsACString& pacString) {
+ if (!mCallback) return;
+ RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, status);
+ runnable->SetPACString(pacString);
+ if (mOnMainThreadOnly) {
+ mPACMan->Dispatch(runnable.forget());
+ } else {
+ runnable->Run();
+ }
+}
+
+void PendingPACQuery::UseAlternatePACFile(const nsACString& pacURL) {
+ if (!mCallback) return;
+
+ RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, NS_OK);
+ runnable->SetPACURL(pacURL);
+ if (mOnMainThreadOnly) {
+ mPACMan->Dispatch(runnable.forget());
+ } else {
+ runnable->Run();
+ }
+}
+
+NS_IMETHODIMP
+PendingPACQuery::Run() {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ mPACMan->PostQuery(this);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+static bool sThreadLocalSetup = false;
+static uint32_t sThreadLocalIndex = 0xdeadbeef; // out of range
+
+static const char* kPACIncludePath =
+ "network.proxy.autoconfig_url.include_path";
+
+nsPACMan::nsPACMan(nsISerialEventTarget* mainThreadEventTarget)
+ : NeckoTargetHolder(mainThreadEventTarget),
+ mLoader("nsPACMan::mLoader"),
+ mLoadPending(false),
+ mShutdown(false),
+ mLoadFailureCount(0),
+ mInProgress(false),
+ mAutoDetect(false),
+ mWPADOverDHCPEnabled(false),
+ mProxyConfigType(0) {
+ MOZ_ASSERT(NS_IsMainThread(), "pacman must be created on main thread");
+ mIncludePath = Preferences::GetBool(kPACIncludePath, false);
+ if (StaticPrefs::network_proxy_parse_pac_on_socket_process() &&
+ gIOService->SocketProcessReady()) {
+ mPAC = MakeUnique<RemoteProxyAutoConfig>();
+ } else {
+ mPAC = MakeUnique<ProxyAutoConfig>();
+ if (!sThreadLocalSetup) {
+ sThreadLocalSetup = true;
+ PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr);
+ }
+ mPAC->SetThreadLocalIndex(sThreadLocalIndex);
+ }
+}
+
+nsPACMan::~nsPACMan() {
+ MOZ_ASSERT(mShutdown, "Shutdown must be called before dtor.");
+
+ if (mPACThread) {
+ if (NS_IsMainThread()) {
+ mPACThread->Shutdown();
+ mPACThread = nullptr;
+ } else {
+ RefPtr<ShutdownThread> runnable = new ShutdownThread(mPACThread);
+ Dispatch(runnable.forget());
+ }
+ }
+
+#ifdef DEBUG
+ {
+ auto loader = mLoader.Lock();
+ NS_ASSERTION(loader.ref() == nullptr, "pac man not shutdown properly");
+ }
+#endif
+
+ NS_ASSERTION(mPendingQ.isEmpty(), "pac man not shutdown properly");
+}
+
+void nsPACMan::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread(), "pacman must be shutdown on main thread");
+ if (mShutdown) {
+ return;
+ }
+
+ CancelExistingLoad();
+
+ if (mPACThread) {
+ PostCancelPendingQ(NS_ERROR_ABORT, /*aShutdown =*/true);
+
+ // Shutdown is initiated from an observer. We don't want to block the
+ // observer service on thread shutdown so we post a shutdown runnable that
+ // will run after we return instead.
+ RefPtr<WaitForThreadShutdown> runnable = new WaitForThreadShutdown(this);
+ Dispatch(runnable.forget());
+ }
+
+ mShutdown = true;
+}
+
+nsresult nsPACMan::DispatchToPAC(already_AddRefed<nsIRunnable> aEvent,
+ bool aSync) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ nsCOMPtr<nsIRunnable> e(aEvent);
+
+ if (mShutdown) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Lazily create the PAC thread. This method is main-thread only so we don't
+ // have to worry about threading issues here.
+ if (!mPACThread) {
+ MOZ_TRY(NS_NewNamedThread("ProxyResolution", getter_AddRefs(mPACThread)));
+ nsresult rv = mPAC->Init(mPACThread);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (aSync) {
+ return NS_DispatchAndSpinEventLoopUntilComplete(
+ "nsPACMan::DispatchToPAC"_ns, mPACThread, e.forget());
+ } else {
+ return mPACThread->Dispatch(e.forget());
+ }
+}
+
+nsresult nsPACMan::AsyncGetProxyForURI(nsIURI* uri, nsPACManCallback* callback,
+ uint32_t flags,
+ bool mainThreadResponse) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mShutdown) return NS_ERROR_NOT_AVAILABLE;
+
+ // Maybe Reload PAC
+ if (!mPACURISpec.IsEmpty() && !mScheduledReload.IsNull() &&
+ TimeStamp::Now() > mScheduledReload) {
+ LOG(("nsPACMan::AsyncGetProxyForURI reload as scheduled\n"));
+
+ LoadPACFromURI(mAutoDetect ? ""_ns : mPACURISpec, false);
+ }
+
+ RefPtr<PendingPACQuery> query =
+ new PendingPACQuery(this, uri, callback, flags, mainThreadResponse);
+
+ if (IsPACURI(uri)) {
+ // deal with this directly instead of queueing it
+ query->Complete(NS_OK, ""_ns);
+ return NS_OK;
+ }
+
+ return DispatchToPAC(query.forget());
+}
+
+nsresult nsPACMan::PostQuery(PendingPACQuery* query) {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+
+ if (mShutdown) {
+ query->Complete(NS_ERROR_NOT_AVAILABLE, ""_ns);
+ return NS_OK;
+ }
+
+ // add a reference to the query while it is in the pending list
+ RefPtr<PendingPACQuery> addref(query);
+ mPendingQ.insertBack(addref.forget().take());
+ ProcessPendingQ();
+ return NS_OK;
+}
+
+nsresult nsPACMan::LoadPACFromURI(const nsACString& aSpec) {
+ return LoadPACFromURI(aSpec, true);
+}
+
+nsresult nsPACMan::LoadPACFromURI(const nsACString& aSpec,
+ bool aResetLoadFailureCount) {
+ NS_ENSURE_STATE(!mShutdown);
+
+ nsCOMPtr<nsIStreamLoader> loader =
+ do_CreateInstance(NS_STREAMLOADER_CONTRACTID);
+ NS_ENSURE_STATE(loader);
+
+ LOG(("nsPACMan::LoadPACFromURI aSpec: %s, aResetLoadFailureCount: %s\n",
+ aSpec.BeginReading(), aResetLoadFailureCount ? "true" : "false"));
+
+ CancelExistingLoad();
+
+ {
+ auto locked = mLoader.Lock();
+ locked.ref() = loader.forget();
+ }
+ mPACURIRedirectSpec.Truncate();
+ mNormalPACURISpec.Truncate(); // set at load time
+ if (aResetLoadFailureCount) {
+ mLoadFailureCount = 0;
+ }
+ mAutoDetect = aSpec.IsEmpty();
+ mPACURISpec.Assign(aSpec);
+
+ // reset to Null
+ mScheduledReload = TimeStamp();
+
+ // if we're on the main thread here so we can get hold of prefs,
+ // we check that we have WPAD preffed on if we're auto-detecting
+ if (mAutoDetect && NS_IsMainThread()) {
+ nsresult rv = GetNetworkProxyTypeFromPref(&mProxyConfigType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (mProxyConfigType != nsIProtocolProxyService::PROXYCONFIG_WPAD) {
+ LOG(
+ ("LoadPACFromURI - Aborting WPAD autodetection because the pref "
+ "doesn't match anymore"));
+ return NS_BINDING_ABORTED;
+ }
+ }
+ // Since we might get called from nsProtocolProxyService::Init, we need to
+ // post an event back to the main thread before we try to use the IO service.
+ //
+ // But, we need to flag ourselves as loading, so that we queue up any PAC
+ // queries the enter between now and when we actually load the PAC file.
+
+ if (!mLoadPending) {
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
+ "nsPACMan::StartLoading", this, &nsPACMan::StartLoading);
+ nsresult rv =
+ NS_IsMainThread()
+ ? Dispatch(runnable.forget())
+ : GetCurrentSerialEventTarget()->Dispatch(runnable.forget());
+ if (NS_FAILED(rv)) return rv;
+ mLoadPending = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsPACMan::GetPACFromDHCP(nsACString& aSpec) {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ if (!mDHCPClient) {
+ LOG(
+ ("nsPACMan::GetPACFromDHCP DHCP option %d query failed because there "
+ "is no DHCP client available\n",
+ MOZ_DHCP_WPAD_OPTION));
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsresult rv;
+ rv = mDHCPClient->GetOption(MOZ_DHCP_WPAD_OPTION, aSpec);
+ if (NS_FAILED(rv)) {
+ LOG((
+ "nsPACMan::GetPACFromDHCP DHCP option %d query failed with result %d\n",
+ MOZ_DHCP_WPAD_OPTION, (uint32_t)rv));
+ } else {
+ LOG(
+ ("nsPACMan::GetPACFromDHCP DHCP option %d query succeeded, finding PAC "
+ "URL %s\n",
+ MOZ_DHCP_WPAD_OPTION, aSpec.BeginReading()));
+ }
+ return rv;
+}
+
+nsresult nsPACMan::ConfigureWPAD(nsACString& aSpec) {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+
+ if (mProxyConfigType != nsIProtocolProxyService::PROXYCONFIG_WPAD) {
+ LOG(
+ ("ConfigureWPAD - Aborting WPAD autodetection because the pref "
+ "doesn't match anymore"));
+ return NS_BINDING_ABORTED;
+ }
+
+ aSpec.Truncate();
+ if (mWPADOverDHCPEnabled) {
+ GetPACFromDHCP(aSpec);
+ }
+
+ if (aSpec.IsEmpty()) {
+ // We diverge from the WPAD spec here in that we don't walk the
+ // hosts's FQDN, stripping components until we hit a TLD. Doing so
+ // is dangerous in the face of an incomplete list of TLDs, and TLDs
+ // get added over time. We could consider doing only a single
+ // substitution of the first component, if that proves to help
+ // compatibility.
+ aSpec.AssignLiteral(MOZ_WPAD_URL);
+ }
+ return NS_OK;
+}
+
+void nsPACMan::AssignPACURISpec(const nsACString& aSpec) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mPACURISpec.Assign(aSpec);
+}
+
+void nsPACMan::StartLoading() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ mLoadPending = false;
+
+ {
+ // CancelExistingLoad was called...
+ nsCOMPtr<nsIStreamLoader> loader;
+ {
+ auto locked = mLoader.Lock();
+ loader = locked.ref();
+ }
+ if (!loader) {
+ PostCancelPendingQ(NS_ERROR_ABORT);
+ return;
+ }
+ }
+
+ if (mAutoDetect) {
+ nsresult rv = GetNetworkProxyTypeFromPref(&mProxyConfigType);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "Could not retrieve Network Proxy Type pref when auto-detecting "
+ "proxy. Halting.");
+ return;
+ }
+ RefPtr<ExecutePACThreadAction> wpadConfigurer =
+ new ExecutePACThreadAction(this);
+ wpadConfigurer->ConfigureWPAD();
+ DispatchToPAC(wpadConfigurer.forget());
+ } else {
+ ContinueLoadingAfterPACUriKnown();
+ }
+}
+
+void nsPACMan::ContinueLoadingAfterPACUriKnown() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ {
+ auto locked = mLoader.Lock();
+ loader = locked.ref();
+ }
+
+ // CancelExistingLoad was called...
+ if (!loader) {
+ PostCancelPendingQ(NS_ERROR_ABORT);
+ return;
+ }
+ if (NS_SUCCEEDED(loader->Init(this, nullptr))) {
+ // Always hit the origin server when loading PAC.
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (ios) {
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIURI> pacURI;
+ NS_NewURI(getter_AddRefs(pacURI), mPACURISpec);
+
+ // NOTE: This results in GetProxyForURI being called
+ if (pacURI) {
+ nsresult rv = pacURI->GetSpec(mNormalPACURISpec);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ NS_NewChannel(getter_AddRefs(channel), pacURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // nsICookieJarSettings
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ios);
+ } else {
+ LOG(("nsPACMan::StartLoading Failed pacspec uri conversion %s\n",
+ mPACURISpec.get()));
+ }
+
+ if (channel) {
+ // allow deprecated HTTP request from SystemPrincipal
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ loadInfo->SetAllowDeprecatedSystemRequests(true);
+ loadInfo->SetHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_EXEMPT);
+
+ channel->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE);
+ channel->SetNotificationCallbacks(this);
+ if (NS_SUCCEEDED(channel->AsyncOpen(loader))) return;
+ }
+ }
+ }
+
+ CancelExistingLoad();
+ PostCancelPendingQ(NS_ERROR_UNEXPECTED);
+}
+
+void nsPACMan::OnLoadFailure() {
+ int32_t minInterval = 5; // 5 seconds
+ int32_t maxInterval = 300; // 5 minutes
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ prefs->GetIntPref("network.proxy.autoconfig_retry_interval_min",
+ &minInterval);
+ prefs->GetIntPref("network.proxy.autoconfig_retry_interval_max",
+ &maxInterval);
+ }
+
+ int32_t interval = minInterval << mLoadFailureCount++; // seconds
+ if (!interval || interval > maxInterval) interval = maxInterval;
+
+ mScheduledReload = TimeStamp::Now() + TimeDuration::FromSeconds(interval);
+
+ LOG(("OnLoadFailure: retry in %d seconds (%d fails)\n", interval,
+ (uint32_t)mLoadFailureCount));
+
+ // while we wait for the retry queued members should try direct
+ // even if that means fast failure.
+ PostCancelPendingQ(NS_ERROR_NOT_AVAILABLE);
+}
+
+void nsPACMan::CancelExistingLoad() {
+ nsCOMPtr<nsIStreamLoader> loader;
+ {
+ auto locked = mLoader.Lock();
+ loader.swap(*locked);
+ }
+ if (loader) {
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+ if (request) {
+ request->Cancel(NS_ERROR_ABORT);
+ }
+ }
+}
+
+void nsPACMan::PostProcessPendingQ() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this);
+ DispatchToPAC(pending.forget());
+}
+
+void nsPACMan::PostCancelPendingQ(nsresult status, bool aShutdown) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this);
+ pending->CancelQueue(status, aShutdown);
+ DispatchToPAC(pending.forget());
+}
+
+void nsPACMan::CancelPendingQ(nsresult status, bool aShutdown) {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ RefPtr<PendingPACQuery> query;
+
+ while (!mPendingQ.isEmpty()) {
+ query = dont_AddRef(mPendingQ.popLast());
+ query->Complete(status, ""_ns);
+ }
+
+ if (aShutdown) {
+ mPAC->Shutdown();
+ }
+}
+
+void nsPACMan::ProcessPendingQ() {
+ MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+ while (ProcessPending()) {
+ ;
+ }
+
+ if (mShutdown) {
+ mPAC->Shutdown();
+ } else {
+ // do GC while the thread has nothing pending
+ mPAC->GC();
+ }
+}
+
+// returns true if progress was made by shortening the queue
+bool nsPACMan::ProcessPending() {
+ if (mPendingQ.isEmpty()) return false;
+
+ // queue during normal load, but if we are retrying a failed load then
+ // fast fail the queries
+ if (mInProgress || (IsLoading() && !mLoadFailureCount)) return false;
+
+ RefPtr<PendingPACQuery> query(dont_AddRef(mPendingQ.popFirst()));
+
+ // Having |mLoadFailureCount > 0| means we haven't had a sucessful PAC load
+ // yet. We should use DIRECT instead.
+ if (mShutdown || IsLoading() || mLoadFailureCount > 0) {
+ query->Complete(NS_ERROR_NOT_AVAILABLE, ""_ns);
+ return true;
+ }
+
+ nsAutoCString pacString;
+ bool completed = false;
+ mInProgress = true;
+ nsAutoCString PACURI;
+
+ // first we need to consider the system proxy changing the pac url
+ if (mSystemProxySettings &&
+ NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) &&
+ !PACURI.IsEmpty() && !PACURI.Equals(mPACURISpec)) {
+ query->UseAlternatePACFile(PACURI);
+ LOG(("Use PAC from system settings: %s\n", PACURI.get()));
+ completed = true;
+ }
+
+ // now try the system proxy settings for this particular url if
+ // PAC was not specified
+ if (!completed && mSystemProxySettings && PACURI.IsEmpty() &&
+ NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(
+ query->mSpec, query->mScheme, query->mHost, query->mPort,
+ pacString))) {
+ if (query->mFlags & nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY &&
+ query->mFlags & nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY) {
+ if (StringBeginsWith(pacString, nsDependentCString(kProxyType_DIRECT),
+ nsCaseInsensitiveUTF8StringComparator)) {
+ // DIRECT indicates that system proxy settings are not configured to use
+ // SOCKS proxy. Try https proxy as a secondary preferrable proxy. This
+ // is mainly for websocket whose precedence is SOCKS > HTTPS > DIRECT.
+ NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(
+ query->mSpec, nsDependentCString(kProxyType_HTTPS), query->mHost,
+ query->mPort, pacString));
+ }
+ }
+ LOG(("Use proxy from system settings: %s\n", pacString.get()));
+ query->Complete(NS_OK, pacString);
+ completed = true;
+ }
+
+ // the systemproxysettings didn't complete the resolution. try via PAC
+ if (!completed) {
+ auto callback = [query(query)](nsresult aStatus,
+ const nsACString& aResult) {
+ LOG(("Use proxy from PAC: %s\n", PromiseFlatCString(aResult).get()));
+ query->Complete(aStatus, aResult);
+ };
+ mPAC->GetProxyForURIWithCallback(query->mSpec, query->mHost,
+ std::move(callback));
+ }
+
+ mInProgress = false;
+ return true;
+}
+
+NS_IMPL_ISUPPORTS(nsPACMan, nsIStreamLoaderObserver, nsIInterfaceRequestor,
+ nsIChannelEventSink)
+
+NS_IMETHODIMP
+nsPACMan::OnStreamComplete(nsIStreamLoader* loader, nsISupports* context,
+ nsresult status, uint32_t dataLen,
+ const uint8_t* data) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ bool loadSucceeded = NS_SUCCEEDED(status) && HttpRequestSucceeded(loader);
+ {
+ auto locked = mLoader.Lock();
+ if (locked.ref() != loader) {
+ // If this happens, then it means that LoadPACFromURI was called more
+ // than once before the initial call completed. In this case, status
+ // should be NS_ERROR_ABORT, and if so, then we know that we can and
+ // should delay any processing.
+ LOG(("OnStreamComplete: called more than once\n"));
+ if (status == NS_ERROR_ABORT) {
+ return NS_OK;
+ }
+ } else if (!loadSucceeded) {
+ // We have to clear the loader to indicate that we are not loading PAC
+ // currently.
+ // Note that we can only clear the loader when |loader| and |mLoader| are
+ // the same one.
+ locked.ref() = nullptr;
+ }
+ }
+
+ LOG(("OnStreamComplete: entry\n"));
+
+ if (loadSucceeded) {
+ // Get the URI spec used to load this PAC script.
+ nsAutoCString pacURI;
+ {
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) uri->GetAsciiSpec(pacURI);
+ }
+ }
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+ MOZ_ASSERT(pps);
+ if (pps) {
+ pps->NotifyProxyConfigChangedInternal();
+ }
+
+ // We succeeded in loading the pac file using a bunch of interfaces that are
+ // main thread only. Unfortunately, we have to initialize the instance of
+ // the PAC evaluator (NS_PROXYAUTOCONFIG_CONTRACTID) on the PAC thread,
+ // because that's where it will be used.
+ RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this);
+ pending->SetupPAC(reinterpret_cast<const char*>(data), dataLen, pacURI,
+ GetExtraJSContextHeapSize());
+ DispatchToPAC(pending.forget());
+
+ LOG(("OnStreamComplete: process the PAC contents\n"));
+
+ // Even if the PAC file could not be parsed, we did succeed in loading the
+ // data for it.
+ mLoadFailureCount = 0;
+ } else {
+ // We were unable to load the PAC file (presumably because of a network
+ // failure). Try again a little later.
+ LOG(("OnStreamComplete: unable to load PAC, retry later\n"));
+ OnLoadFailure();
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ PostProcessPendingQ();
+ } else {
+ PostCancelPendingQ(status);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPACMan::GetInterface(const nsIID& iid, void** result) {
+ // In case loading the PAC file requires authentication.
+ if (iid.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ nsCOMPtr<nsIPromptFactory> promptFac =
+ do_GetService("@mozilla.org/prompter;1");
+ NS_ENSURE_TRUE(promptFac, NS_ERROR_NO_INTERFACE);
+ nsresult rv =
+ promptFac->GetPrompt(nullptr, iid, reinterpret_cast<void**>(result));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ return NS_OK;
+ }
+
+ // In case loading the PAC file results in a redirect.
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *result = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsPACMan::AsyncOnChannelRedirect(nsIChannel* oldChannel, nsIChannel* newChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> pacURI;
+ if (NS_FAILED((rv = newChannel->GetURI(getter_AddRefs(pacURI))))) return rv;
+
+ rv = pacURI->GetSpec(mPACURIRedirectSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG(("nsPACMan redirect from original %s to redirected %s\n",
+ mPACURISpec.get(), mPACURIRedirectSpec.get()));
+
+ // do not update mPACURISpec - that needs to stay as the
+ // configured URI so that we can determine when the config changes.
+ // However do track the most recent URI in the redirect change
+ // as mPACURIRedirectSpec so that URI can be allowed to bypass
+ // the proxy and actually fetch the pac file.
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+nsresult nsPACMan::Init(nsISystemProxySettings* systemProxySettings) {
+ mSystemProxySettings = systemProxySettings;
+ mDHCPClient = do_GetService(NS_DHCPCLIENT_CONTRACTID);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsPACMan.h b/netwerk/base/nsPACMan.h
new file mode 100644
index 0000000000..dd7d6edb8c
--- /dev/null
+++ b/netwerk/base/nsPACMan.h
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPACMan_h__
+#define nsPACMan_h__
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamLoader.h"
+#include "nsThreadUtils.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "ProxyAutoConfig.h"
+
+class nsISystemProxySettings;
+class nsIDHCPClient;
+class nsIThread;
+
+namespace mozilla {
+namespace net {
+
+class nsPACMan;
+class WaitForThreadShutdown;
+
+/**
+ * This class defines a callback interface used by AsyncGetProxyForURI.
+ */
+class NS_NO_VTABLE nsPACManCallback : public nsISupports {
+ public:
+ /**
+ * This method is invoked on the same thread that called AsyncGetProxyForURI.
+ *
+ * @param status
+ * This parameter indicates whether or not the PAC query succeeded.
+ * @param pacString
+ * This parameter holds the value of the PAC string. It is empty when
+ * status is a failure code.
+ * @param newPACURL
+ * This parameter holds the URL of a new PAC file that should be loaded
+ * before the query is evaluated again. At least one of pacString and
+ * newPACURL should be 0 length.
+ */
+ virtual void OnQueryComplete(nsresult status, const nsACString& pacString,
+ const nsACString& newPACURL) = 0;
+};
+
+class PendingPACQuery final : public Runnable,
+ public LinkedListElement<PendingPACQuery> {
+ public:
+ PendingPACQuery(nsPACMan* pacMan, nsIURI* uri, nsPACManCallback* callback,
+ uint32_t flags, bool mainThreadResponse);
+
+ // can be called from either thread
+ void Complete(nsresult status, const nsACString& pacString);
+ void UseAlternatePACFile(const nsACString& pacURL);
+
+ nsCString mSpec;
+ nsCString mScheme;
+ nsCString mHost;
+ int32_t mPort;
+ uint32_t mFlags;
+
+ NS_IMETHOD Run(void) override; /* Runnable */
+
+ private:
+ nsPACMan* mPACMan; // weak reference
+
+ private:
+ RefPtr<nsPACManCallback> mCallback;
+ bool mOnMainThreadOnly;
+};
+
+/**
+ * This class provides an abstraction layer above the PAC thread. The methods
+ * defined on this class are intended to be called on the main thread only.
+ */
+
+class nsPACMan final : public nsIStreamLoaderObserver,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public NeckoTargetHolder {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit nsPACMan(nsISerialEventTarget* mainThreadEventTarget);
+
+ /**
+ * This method may be called to shutdown the PAC manager. Any async queries
+ * that have not yet completed will either finish normally or be canceled by
+ * the time this method returns.
+ */
+ void Shutdown();
+
+ /**
+ * This method queries a PAC result asynchronously. The callback runs on the
+ * calling thread. If the PAC file has not yet been loaded, then this method
+ * will queue up the request, and complete it once the PAC file has been
+ * loaded.
+ *
+ * @param uri
+ * The URI to query.
+ * @param callback
+ * The callback to run once the PAC result is available.
+ * @param flags
+ * A bit-wise combination of the RESOLVE_ flags defined above. Pass
+ * 0 to specify the default behavior.
+ * @param mustCallbackOnMainThread
+ * If set to false the callback can be made from the PAC thread
+ */
+ nsresult AsyncGetProxyForURI(nsIURI* uri, nsPACManCallback* callback,
+ uint32_t flags, bool mainThreadResponse);
+
+ /**
+ * This method may be called to reload the PAC file. While we are loading
+ * the PAC file, any asynchronous PAC queries will be queued up to be
+ * processed once the PAC file finishes loading.
+ *
+ * @param aSpec
+ * The non normalized uri spec of this URI used for comparison with
+ * system proxy settings to determine if the PAC uri has changed.
+ */
+ nsresult LoadPACFromURI(const nsACString& aSpec);
+
+ /**
+ * Returns true if we are currently loading the PAC file.
+ */
+ bool IsLoading() {
+ auto loader = mLoader.Lock();
+ return loader.ref() != nullptr;
+ }
+
+ /**
+ * Returns true if the given URI matches the URI of our PAC file or the
+ * URI it has been redirected to. In the case of a chain of redirections
+ * only the current one being followed and the original are considered
+ * becuase this information is used, respectively, to determine if we
+ * should bypass the proxy (to fetch the pac file) or if the pac
+ * configuration has changed (and we should reload the pac file)
+ */
+ bool IsPACURI(const nsACString& spec) {
+ return mPACURISpec.Equals(spec) || mPACURIRedirectSpec.Equals(spec) ||
+ mNormalPACURISpec.Equals(spec);
+ }
+
+ bool IsPACURI(nsIURI* uri) {
+ if (mPACURISpec.IsEmpty() && mPACURIRedirectSpec.IsEmpty()) {
+ return false;
+ }
+
+ nsAutoCString tmp;
+ nsresult rv = uri->GetSpec(tmp);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return IsPACURI(tmp);
+ }
+
+ bool IsUsingWPAD() { return mAutoDetect; }
+
+ nsresult Init(nsISystemProxySettings*);
+ static nsPACMan* sInstance;
+
+ // PAC thread operations only
+ void ProcessPendingQ();
+ void CancelPendingQ(nsresult, bool aShutdown);
+
+ void SetWPADOverDHCPEnabled(bool aValue) { mWPADOverDHCPEnabled = aValue; }
+
+ private:
+ NS_DECL_NSISTREAMLOADEROBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ friend class PendingPACQuery;
+ friend class PACLoadComplete;
+ friend class ConfigureWPADComplete;
+ friend class ExecutePACThreadAction;
+ friend class WaitForThreadShutdown;
+ friend class TestPACMan;
+
+ ~nsPACMan();
+
+ /**
+ * Cancel any existing load if any.
+ */
+ void CancelExistingLoad();
+
+ /**
+ * Start loading the PAC file.
+ */
+ void StartLoading();
+
+ /**
+ * Continue loading the PAC file.
+ */
+ void ContinueLoadingAfterPACUriKnown();
+
+ /**
+ * This method may be called to reload the PAC file. While we are loading
+ * the PAC file, any asynchronous PAC queries will be queued up to be
+ * processed once the PAC file finishes loading.
+ *
+ * @param aSpec
+ * The non normalized uri spec of this URI used for comparison with
+ * system proxy settings to determine if the PAC uri has changed.
+ * @param aResetLoadFailureCount
+ * A flag saying whether the exponential back-off for attempting to
+ * reload the PAC should be reset.
+ */
+ nsresult LoadPACFromURI(const nsACString& aSpec, bool aResetLoadFailureCount);
+
+ /**
+ * Reload the PAC file if there is reason to.
+ */
+ void MaybeReloadPAC();
+
+ /**
+ * Called when we fail to load the PAC file.
+ */
+ void OnLoadFailure();
+
+ /**
+ * PostQuery() only runs on the PAC thread and it is used to
+ * place a pendingPACQuery into the queue and potentially
+ * execute the queue if it was otherwise empty
+ */
+ nsresult PostQuery(PendingPACQuery* query);
+
+ // Having found the PAC URI on the PAC thread, copy it to a string which
+ // can be altered on the main thread.
+ void AssignPACURISpec(const nsACString& aSpec);
+
+ // PAC thread operations only
+ void PostProcessPendingQ();
+ void PostCancelPendingQ(nsresult, bool aShutdown = false);
+ bool ProcessPending();
+ nsresult GetPACFromDHCP(nsACString& aSpec);
+ nsresult ConfigureWPAD(nsACString& aSpec);
+
+ private:
+ /**
+ * Dispatches a runnable to the PAC processing thread. Handles lazy
+ * instantiation of the thread.
+ *
+ * @param aEvent The event to disptach.
+ * @param aSync Whether or not this should be synchronous dispatch.
+ */
+ nsresult DispatchToPAC(already_AddRefed<nsIRunnable> aEvent,
+ bool aSync = false);
+
+ UniquePtr<ProxyAutoConfigBase> mPAC;
+ nsCOMPtr<nsIThread> mPACThread;
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+ nsCOMPtr<nsIDHCPClient> mDHCPClient;
+
+ LinkedList<PendingPACQuery> mPendingQ; /* pac thread only */
+
+ // These specs are not nsIURI so that they can be used off the main thread.
+ // The non-normalized versions are directly from the configuration, the
+ // normalized version has been extracted from an nsIURI
+ nsCString mPACURISpec;
+ nsCString mPACURIRedirectSpec;
+ nsCString mNormalPACURISpec;
+
+ DataMutex<nsCOMPtr<nsIStreamLoader>> mLoader;
+ bool mLoadPending;
+ Atomic<bool, Relaxed> mShutdown;
+ TimeStamp mScheduledReload;
+ Atomic<uint32_t, Relaxed> mLoadFailureCount;
+
+ bool mInProgress;
+ bool mIncludePath;
+ bool mAutoDetect;
+ bool mWPADOverDHCPEnabled;
+ int32_t mProxyConfigType;
+};
+
+extern LazyLogModule gProxyLog;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsPACMan_h__
diff --git a/netwerk/base/nsPISocketTransportService.idl b/netwerk/base/nsPISocketTransportService.idl
new file mode 100644
index 0000000000..e7c8ac5a60
--- /dev/null
+++ b/netwerk/base/nsPISocketTransportService.idl
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISocketTransportService.idl"
+
+/**
+ * This is a private interface used by the internals of the networking library.
+ * It will never be frozen. Do not use it in external code.
+ */
+[builtinclass, scriptable, uuid(18f73bf1-b35b-4b7b-aa9a-11bcbdbc389c)]
+interface nsPISocketTransportService : nsIRoutedSocketTransportService
+{
+ /**
+ * init/shutdown routines.
+ */
+ void init();
+ void shutdown(in bool aXpcomShutdown);
+
+ /**
+ * controls the TCP sender window clamp
+ */
+ readonly attribute long sendBufferSize;
+
+ /**
+ * Controls whether the socket transport service is offline.
+ * Setting it offline will cause non-local socket detachment.
+ */
+ attribute boolean offline;
+
+ /**
+ * Controls the default timeout (in seconds) for sending keepalive probes.
+ */
+ readonly attribute long keepaliveIdleTime;
+
+ /**
+ * Controls the default interval (in seconds) between retrying keepalive probes.
+ */
+ readonly attribute long keepaliveRetryInterval;
+
+ /**
+ * Controls the default retransmission count for keepalive probes.
+ */
+ readonly attribute long keepaliveProbeCount;
+};
+
+%{C++
+/*
+ * I/O activity observer topic. Sends out information about the
+ * amount of data we're sending/receiving via sockets and disk files.
+ *
+ * Activated via the "io.activity.enabled" preference.
+ */
+#define NS_IO_ACTIVITY "io-activity"
+
+
+%}
diff --git a/netwerk/base/nsPreloadedStream.cpp b/netwerk/base/nsPreloadedStream.cpp
new file mode 100644
index 0000000000..cd7a7e79d2
--- /dev/null
+++ b/netwerk/base/nsPreloadedStream.cpp
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPreloadedStream.h"
+#include "nsIRunnable.h"
+
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsPreloadedStream, nsIInputStream, nsIAsyncInputStream,
+ nsIInputStreamCallback)
+
+nsPreloadedStream::nsPreloadedStream(nsIAsyncInputStream* aStream,
+ const char* data, uint32_t datalen)
+ : mStream(aStream),
+ mOffset(0),
+ mLen(datalen),
+ mCallback("nsPreloadedStream") {
+ mBuf = (char*)moz_xmalloc(datalen);
+ memcpy(mBuf, data, datalen);
+}
+
+nsPreloadedStream::~nsPreloadedStream() { free(mBuf); }
+
+NS_IMETHODIMP
+nsPreloadedStream::Close() {
+ mLen = 0;
+ return mStream->Close();
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::Available(uint64_t* _retval) {
+ uint64_t avail = 0;
+
+ nsresult rv = mStream->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+ *_retval = avail + mLen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::StreamStatus() { return mStream->StreamStatus(); }
+
+NS_IMETHODIMP
+nsPreloadedStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ if (!mLen) return mStream->Read(aBuf, aCount, _retval);
+
+ uint32_t toRead = std::min(mLen, aCount);
+ memcpy(aBuf, mBuf + mOffset, toRead);
+ mOffset += toRead;
+ mLen -= toRead;
+ *_retval = toRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* result) {
+ if (!mLen) return mStream->ReadSegments(aWriter, aClosure, aCount, result);
+
+ *result = 0;
+ while (mLen > 0 && aCount > 0) {
+ uint32_t toRead = std::min(mLen, aCount);
+ uint32_t didRead = 0;
+ nsresult rv;
+
+ rv = aWriter(this, aClosure, mBuf + mOffset, *result, toRead, &didRead);
+
+ if (NS_FAILED(rv)) return NS_OK;
+
+ *result += didRead;
+ mOffset += didRead;
+ mLen -= didRead;
+ aCount -= didRead;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::IsNonBlocking(bool* _retval) {
+ return mStream->IsNonBlocking(_retval);
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::CloseWithStatus(nsresult aStatus) {
+ mLen = 0;
+ return mStream->CloseWithStatus(aStatus);
+}
+
+class RunOnThread : public Runnable {
+ public:
+ RunOnThread(nsIAsyncInputStream* aStream, nsIInputStreamCallback* aCallback)
+ : Runnable("net::RunOnThread"), mStream(aStream), mCallback(aCallback) {}
+
+ virtual ~RunOnThread() = default;
+
+ NS_IMETHOD Run() override {
+ mCallback->OnInputStreamReady(mStream);
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+};
+
+NS_IMETHODIMP
+nsPreloadedStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ if (!mLen) {
+ {
+ auto lock = mCallback.Lock();
+ *lock = aCallback;
+ }
+ return mStream->AsyncWait(aCallback ? this : nullptr, aFlags,
+ aRequestedCount, aEventTarget);
+ }
+
+ if (!aCallback) return NS_OK;
+
+ if (!aEventTarget) return aCallback->OnInputStreamReady(this);
+
+ nsCOMPtr<nsIRunnable> event = new RunOnThread(this, aCallback);
+ return aEventTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ auto lock = mCallback.Lock();
+ callback = lock->forget();
+ }
+ if (callback) {
+ return callback->OnInputStreamReady(this);
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsPreloadedStream.h b/netwerk/base/nsPreloadedStream.h
new file mode 100644
index 0000000000..5c550b43e7
--- /dev/null
+++ b/netwerk/base/nsPreloadedStream.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This class allows you to prefix an existing nsIAsyncInputStream
+ * with a preloaded block of data known at construction time by wrapping the
+ * two data sources into a new nsIAsyncInputStream. Readers of the new
+ * stream initially see the preloaded data and when that has been exhausted
+ * they automatically read from the wrapped stream.
+ *
+ * It is used by nsHttpConnection when it has over buffered while reading from
+ * the HTTP input socket and accidentally consumed data that belongs to
+ * a different protocol via the HTTP Upgrade mechanism. That over-buffered
+ * data is preloaded together with the input socket to form the new input socket
+ * given to the new protocol handler.
+ */
+
+#ifndef nsPreloadedStream_h__
+#define nsPreloadedStream_h__
+
+#include "nsIAsyncInputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DataMutex.h"
+
+namespace mozilla {
+namespace net {
+
+class nsPreloadedStream final : public nsIAsyncInputStream,
+ public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ nsPreloadedStream(nsIAsyncInputStream* aStream, const char* data,
+ uint32_t datalen);
+
+ private:
+ ~nsPreloadedStream();
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+
+ char* mBuf;
+ uint32_t mOffset;
+ uint32_t mLen;
+
+ DataMutex<nsCOMPtr<nsIInputStreamCallback>> mCallback;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsProtocolProxyService.cpp b/netwerk/base/nsProtocolProxyService.cpp
new file mode 100644
index 0000000000..9f721f94e8
--- /dev/null
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -0,0 +1,2459 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+
+#include "nsProtocolProxyService.h"
+#include "nsProxyInfo.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIIOService.h"
+#include "nsIObserverService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIChannel.h"
+#include "nsICancelable.h"
+#include "nsDNSService2.h"
+#include "nsPIDNSService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include "nsQueryObject.h"
+#include "nsSOCKSIOLayer.h"
+#include "nsString.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "prnetdb.h"
+#include "nsPACMan.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
+#include "nsISystemProxySettings.h"
+#include "nsINetworkLinkService.h"
+#include "nsIHttpChannelInternal.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/Unused.h"
+
+//----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+extern const char kProxyType_HTTP[];
+extern const char kProxyType_HTTPS[];
+extern const char kProxyType_SOCKS[];
+extern const char kProxyType_SOCKS4[];
+extern const char kProxyType_SOCKS5[];
+extern const char kProxyType_DIRECT[];
+extern const char kProxyType_PROXY[];
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
+
+//----------------------------------------------------------------------------
+
+#define PROXY_PREF_BRANCH "network.proxy"
+#define PROXY_PREF(x) PROXY_PREF_BRANCH "." x
+
+//----------------------------------------------------------------------------
+
+// This structure is intended to be allocated on the stack
+struct nsProtocolInfo {
+ nsAutoCString scheme;
+ uint32_t flags = 0;
+ int32_t defaultPort = 0;
+};
+
+//----------------------------------------------------------------------------
+
+// Return the channel's proxy URI, or if it doesn't exist, the
+// channel's main URI.
+static nsresult GetProxyURI(nsIChannel* channel, nsIURI** aOut) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> proxyURI;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ rv = httpChannel->GetProxyURI(getter_AddRefs(proxyURI));
+ }
+ if (!proxyURI) {
+ rv = channel->GetURI(getter_AddRefs(proxyURI));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ proxyURI.forget(aOut);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+nsProtocolProxyService::FilterLink::FilterLink(uint32_t p,
+ nsIProtocolProxyFilter* f)
+ : position(p), filter(f), channelFilter(nullptr) {
+ LOG(("nsProtocolProxyService::FilterLink::FilterLink %p, filter=%p", this,
+ f));
+}
+nsProtocolProxyService::FilterLink::FilterLink(
+ uint32_t p, nsIProtocolProxyChannelFilter* cf)
+ : position(p), filter(nullptr), channelFilter(cf) {
+ LOG(("nsProtocolProxyService::FilterLink::FilterLink %p, channel-filter=%p",
+ this, cf));
+}
+
+nsProtocolProxyService::FilterLink::~FilterLink() {
+ LOG(("nsProtocolProxyService::FilterLink::~FilterLink %p", this));
+}
+
+//-----------------------------------------------------------------------------
+
+// The nsPACManCallback portion of this implementation should be run
+// on the main thread - so call nsPACMan::AsyncGetProxyForURI() with
+// a true mainThreadResponse parameter.
+class nsAsyncResolveRequest final : public nsIRunnable,
+ public nsPACManCallback,
+ public nsICancelable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsAsyncResolveRequest(nsProtocolProxyService* pps, nsIChannel* channel,
+ uint32_t aResolveFlags,
+ nsIProtocolProxyCallback* callback)
+ : mResolveFlags(aResolveFlags),
+ mPPS(pps),
+ mXPComPPS(pps),
+ mChannel(channel),
+ mCallback(callback) {
+ NS_ASSERTION(mCallback, "null callback");
+ }
+
+ private:
+ ~nsAsyncResolveRequest() {
+ if (!NS_IsMainThread()) {
+ // these xpcom pointers might need to be proxied back to the
+ // main thread to delete safely, but if this request had its
+ // callbacks called normally they will all be null and this is a nop
+
+ if (mChannel) {
+ NS_ReleaseOnMainThread("nsAsyncResolveRequest::mChannel",
+ mChannel.forget());
+ }
+
+ if (mCallback) {
+ NS_ReleaseOnMainThread("nsAsyncResolveRequest::mCallback",
+ mCallback.forget());
+ }
+
+ if (mProxyInfo) {
+ NS_ReleaseOnMainThread("nsAsyncResolveRequest::mProxyInfo",
+ mProxyInfo.forget());
+ }
+
+ if (mXPComPPS) {
+ NS_ReleaseOnMainThread("nsAsyncResolveRequest::mXPComPPS",
+ mXPComPPS.forget());
+ }
+ }
+ }
+
+ // Helper class to loop over all registered asynchronous filters.
+ // There is a cycle between nsAsyncResolveRequest and this class that
+ // is broken after the last filter has called back on this object.
+ class AsyncApplyFilters final : public nsIProxyProtocolFilterResult,
+ public nsIRunnable,
+ public nsICancelable {
+ // The reference counter is thread-safe, but the processing logic is
+ // considered single thread only. We want the counter be thread safe,
+ // since this class can be released on a background thread.
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROXYPROTOCOLFILTERRESULT
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSICANCELABLE
+
+ using Callback =
+ std::function<nsresult(nsAsyncResolveRequest*, nsIProxyInfo*, bool)>;
+
+ explicit AsyncApplyFilters(nsProtocolInfo& aInfo,
+ Callback const& aCallback);
+ // This method starts the processing or filters. If all of them
+ // answer synchronously (call back from within applyFilters) this method
+ // will return immediately and the returning result will carry return
+ // result of the callback given in constructor.
+ // This method is looping the registered filters (that have been copied
+ // locally) as long as an answer from a filter is obtained synchronously.
+ // Note that filters are processed serially to let them build a list
+ // of proxy info.
+ nsresult AsyncProcess(nsAsyncResolveRequest* aRequest);
+
+ private:
+ using FilterLink = nsProtocolProxyService::FilterLink;
+
+ virtual ~AsyncApplyFilters();
+ // Processes the next filter and loops until a filter is successfully
+ // called on or it has called back to us.
+ nsresult ProcessNextFilter();
+ // Called after the last filter has been processed (=called back or failed
+ // to be called on)
+ nsresult Finish();
+
+ nsProtocolInfo mInfo;
+ // This is nullified before we call back on the request or when
+ // Cancel() on this object has been called to break the cycle
+ // and signal to stop.
+ RefPtr<nsAsyncResolveRequest> mRequest;
+ Callback mCallback;
+ // A shallow snapshot of filters as they were registered at the moment
+ // we started to process filters for the given resolve request.
+ nsTArray<RefPtr<FilterLink>> mFiltersCopy;
+
+ nsTArray<RefPtr<FilterLink>>::index_type mNextFilterIndex;
+ // true when we are calling ProcessNextFilter() from inside AsyncProcess(),
+ // false otherwise.
+ bool mProcessingInLoop;
+ // true after a filter called back to us with a result, dropped to false
+ // just before we call a filter.
+ bool mFilterCalledBack;
+
+ // This keeps the initial value we pass to the first filter in line and also
+ // collects the result from each filter call.
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+
+ // The logic is written as non-thread safe, assert single-thread usage.
+ nsCOMPtr<nsISerialEventTarget> mProcessingThread;
+ };
+
+ void EnsureResolveFlagsMatch() {
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(mProxyInfo);
+ if (!pi || pi->ResolveFlags() == mResolveFlags) {
+ return;
+ }
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo =
+ pi->CloneProxyInfoWithNewResolveFlags(mResolveFlags);
+ mProxyInfo.swap(proxyInfo);
+ }
+
+ public:
+ nsresult ProcessLocally(nsProtocolInfo& info, nsIProxyInfo* pi,
+ bool isSyncOK) {
+ SetResult(NS_OK, pi);
+
+ auto consumeFiltersResult = [isSyncOK](nsAsyncResolveRequest* ctx,
+ nsIProxyInfo* pi,
+ bool aCalledAsync) -> nsresult {
+ ctx->SetResult(NS_OK, pi);
+ if (isSyncOK || aCalledAsync) {
+ ctx->Run();
+ return NS_OK;
+ }
+
+ return ctx->DispatchCallback();
+ };
+
+ mAsyncFilterApplier = new AsyncApplyFilters(info, consumeFiltersResult);
+ // may call consumeFiltersResult() directly
+ return mAsyncFilterApplier->AsyncProcess(this);
+ }
+
+ void SetResult(nsresult status, nsIProxyInfo* pi) {
+ mStatus = status;
+ mProxyInfo = pi;
+ }
+
+ NS_IMETHOD Run() override {
+ if (mCallback) DoCallback();
+ return NS_OK;
+ }
+
+ NS_IMETHOD Cancel(nsresult reason) override {
+ NS_ENSURE_ARG(NS_FAILED(reason));
+
+ if (mAsyncFilterApplier) {
+ mAsyncFilterApplier->Cancel(reason);
+ }
+
+ // If we've already called DoCallback then, nothing more to do.
+ if (!mCallback) return NS_OK;
+
+ SetResult(reason, nullptr);
+ return DispatchCallback();
+ }
+
+ nsresult DispatchCallback() {
+ if (mDispatched) { // Only need to dispatch once
+ return NS_OK;
+ }
+
+ nsresult rv = NS_DispatchToCurrentThread(this);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to dispatch callback event");
+ } else {
+ mDispatched = true;
+ return NS_OK;
+ }
+
+ mCallback = nullptr; // break possible reference cycle
+ return rv;
+ }
+
+ private:
+ // Called asynchronously, so we do not need to post another PLEvent
+ // before calling DoCallback.
+ void OnQueryComplete(nsresult status, const nsACString& pacString,
+ const nsACString& newPACURL) override {
+ // If we've already called DoCallback then, nothing more to do.
+ if (!mCallback) return;
+
+ // Provided we haven't been canceled...
+ if (mStatus == NS_OK) {
+ mStatus = status;
+ mPACString = pacString;
+ mPACURL = newPACURL;
+ }
+
+ // In the cancelation case, we may still have another PLEvent in
+ // the queue that wants to call DoCallback. No need to wait for
+ // it, just run the callback now.
+ DoCallback();
+ }
+
+ void DoCallback() {
+ bool pacAvailable = true;
+ if (mStatus == NS_ERROR_NOT_AVAILABLE && !mProxyInfo) {
+ // If the PAC service is not avail (e.g. failed pac load
+ // or shutdown) then we will be going direct. Make that
+ // mapping now so that any filters are still applied.
+ mPACString = "DIRECT;"_ns;
+ mStatus = NS_OK;
+
+ LOG(("pac not available, use DIRECT\n"));
+ pacAvailable = false;
+ }
+
+ // Generate proxy info from the PAC string if appropriate
+ if (NS_SUCCEEDED(mStatus) && !mProxyInfo && !mPACString.IsEmpty()) {
+ mPPS->ProcessPACString(mPACString, mResolveFlags,
+ getter_AddRefs(mProxyInfo));
+ nsCOMPtr<nsIURI> proxyURI;
+ GetProxyURI(mChannel, getter_AddRefs(proxyURI));
+
+ // Now apply proxy filters
+ nsProtocolInfo info;
+ mStatus = mPPS->GetProtocolInfo(proxyURI, &info);
+
+ auto consumeFiltersResult = [pacAvailable](nsAsyncResolveRequest* self,
+ nsIProxyInfo* pi,
+ bool async) -> nsresult {
+ LOG(("DoCallback::consumeFiltersResult this=%p, pi=%p, async=%d", self,
+ pi, async));
+
+ self->mProxyInfo = pi;
+
+ if (pacAvailable) {
+ // if !pacAvailable, it was already logged above
+ LOG(("pac thread callback %s\n", self->mPACString.get()));
+ }
+
+ if (NS_SUCCEEDED(self->mStatus)) {
+ self->mPPS->MaybeDisableDNSPrefetch(self->mProxyInfo);
+ }
+
+ self->EnsureResolveFlagsMatch();
+ self->mCallback->OnProxyAvailable(self, self->mChannel,
+ self->mProxyInfo, self->mStatus);
+
+ return NS_OK;
+ };
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mAsyncFilterApplier = new AsyncApplyFilters(info, consumeFiltersResult);
+ // This may call consumeFiltersResult() directly.
+ mAsyncFilterApplier->AsyncProcess(this);
+ return;
+ }
+
+ consumeFiltersResult(this, nullptr, false);
+ } else if (NS_SUCCEEDED(mStatus) && !mPACURL.IsEmpty()) {
+ LOG(("pac thread callback indicates new pac file load\n"));
+
+ nsCOMPtr<nsIURI> proxyURI;
+ GetProxyURI(mChannel, getter_AddRefs(proxyURI));
+
+ // trigger load of new pac url
+ nsresult rv = mPPS->ConfigureFromPAC(mPACURL, false);
+ if (NS_SUCCEEDED(rv)) {
+ // now that the load is triggered, we can resubmit the query
+ RefPtr<nsAsyncResolveRequest> newRequest =
+ new nsAsyncResolveRequest(mPPS, mChannel, mResolveFlags, mCallback);
+ rv = mPPS->mPACMan->AsyncGetProxyForURI(proxyURI, newRequest,
+ mResolveFlags, true);
+ }
+
+ if (NS_FAILED(rv)) {
+ mCallback->OnProxyAvailable(this, mChannel, nullptr, rv);
+ }
+
+ // do not call onproxyavailable() in SUCCESS case - the newRequest will
+ // take care of that
+ } else {
+ LOG(("pac thread callback did not provide information %" PRIX32 "\n",
+ static_cast<uint32_t>(mStatus)));
+ if (NS_SUCCEEDED(mStatus)) mPPS->MaybeDisableDNSPrefetch(mProxyInfo);
+ EnsureResolveFlagsMatch();
+ mCallback->OnProxyAvailable(this, mChannel, mProxyInfo, mStatus);
+ }
+
+ // We are on the main thread now and don't need these any more so
+ // release them to avoid having to proxy them back to the main thread
+ // in the dtor
+ mCallback = nullptr; // in case the callback holds an owning ref to us
+ mPPS = nullptr;
+ mXPComPPS = nullptr;
+ mChannel = nullptr;
+ mProxyInfo = nullptr;
+ }
+
+ private:
+ nsresult mStatus{NS_OK};
+ nsCString mPACString;
+ nsCString mPACURL;
+ bool mDispatched{false};
+ uint32_t mResolveFlags;
+
+ nsProtocolProxyService* mPPS;
+ nsCOMPtr<nsIProtocolProxyService> mXPComPPS;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIProtocolProxyCallback> mCallback;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+
+ RefPtr<AsyncApplyFilters> mAsyncFilterApplier;
+};
+
+NS_IMPL_ISUPPORTS(nsAsyncResolveRequest, nsICancelable, nsIRunnable)
+
+NS_IMPL_ISUPPORTS(nsAsyncResolveRequest::AsyncApplyFilters,
+ nsIProxyProtocolFilterResult, nsICancelable, nsIRunnable)
+
+nsAsyncResolveRequest::AsyncApplyFilters::AsyncApplyFilters(
+ nsProtocolInfo& aInfo, Callback const& aCallback)
+ : mInfo(aInfo),
+ mCallback(aCallback),
+ mNextFilterIndex(0),
+ mProcessingInLoop(false),
+ mFilterCalledBack(false) {
+ LOG(("AsyncApplyFilters %p", this));
+}
+
+nsAsyncResolveRequest::AsyncApplyFilters::~AsyncApplyFilters() {
+ LOG(("~AsyncApplyFilters %p", this));
+
+ MOZ_ASSERT(!mRequest);
+ MOZ_ASSERT(!mProxyInfo);
+ MOZ_ASSERT(!mFiltersCopy.Length());
+}
+
+nsresult nsAsyncResolveRequest::AsyncApplyFilters::AsyncProcess(
+ nsAsyncResolveRequest* aRequest) {
+ LOG(("AsyncApplyFilters::AsyncProcess %p for req %p", this, aRequest));
+
+ MOZ_ASSERT(!mRequest, "AsyncApplyFilters started more than once!");
+
+ if (!(mInfo.flags & nsIProtocolHandler::ALLOWS_PROXY)) {
+ // Calling the callback directly (not via Finish()) since we
+ // don't want to prune.
+ return mCallback(aRequest, aRequest->mProxyInfo, false);
+ }
+
+ mProcessingThread = NS_GetCurrentThread();
+
+ mRequest = aRequest;
+ mProxyInfo = aRequest->mProxyInfo;
+
+ aRequest->mPPS->CopyFilters(mFiltersCopy);
+
+ // We want to give filters a chance to process in a single loop to prevent
+ // any current-thread dispatch delays when those are not needed.
+ // This code is rather "loopy" than "recursive" to prevent long stack traces.
+ do {
+ MOZ_ASSERT(!mProcessingInLoop);
+
+ mozilla::AutoRestore<bool> restore(mProcessingInLoop);
+ mProcessingInLoop = true;
+
+ nsresult rv = ProcessNextFilter();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } while (mFilterCalledBack);
+
+ return NS_OK;
+}
+
+nsresult nsAsyncResolveRequest::AsyncApplyFilters::ProcessNextFilter() {
+ LOG(("AsyncApplyFilters::ProcessNextFilter %p ENTER pi=%p", this,
+ mProxyInfo.get()));
+
+ RefPtr<FilterLink> filter;
+ do {
+ mFilterCalledBack = false;
+
+ if (!mRequest) {
+ // We got canceled
+ LOG((" canceled"));
+ return NS_OK; // should we let the consumer know?
+ }
+
+ if (mNextFilterIndex == mFiltersCopy.Length()) {
+ return Finish();
+ }
+
+ filter = mFiltersCopy[mNextFilterIndex++];
+
+ // Loop until a call to a filter succeeded. Other option is to recurse
+ // but that would waste stack trace when a number of filters gets registered
+ // and all from some reason tend to fail.
+ // The !mFilterCalledBack part of the condition is there to protect us from
+ // calling on another filter when the current one managed to call back and
+ // then threw. We already have the result so take it and use it since
+ // the next filter will be processed by the root loop or a call to
+ // ProcessNextFilter has already been dispatched to this thread.
+ LOG((" calling filter %p pi=%p", filter.get(), mProxyInfo.get()));
+ } while (!mRequest->mPPS->ApplyFilter(filter, mRequest->mChannel, mInfo,
+ mProxyInfo, this) &&
+ !mFilterCalledBack);
+
+ LOG(("AsyncApplyFilters::ProcessNextFilter %p LEAVE pi=%p", this,
+ mProxyInfo.get()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncResolveRequest::AsyncApplyFilters::OnProxyFilterResult(
+ nsIProxyInfo* aProxyInfo) {
+ LOG(("AsyncApplyFilters::OnProxyFilterResult %p pi=%p", this, aProxyInfo));
+
+ MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread());
+ MOZ_ASSERT(!mFilterCalledBack);
+
+ if (mFilterCalledBack) {
+ LOG((" duplicate notification?"));
+ return NS_OK;
+ }
+
+ mFilterCalledBack = true;
+
+ if (!mRequest) {
+ // We got canceled
+ LOG((" canceled"));
+ return NS_OK;
+ }
+
+ mProxyInfo = aProxyInfo;
+
+ if (mProcessingInLoop) {
+ // No need to call/dispatch ProcessNextFilter(), we are in a control
+ // loop that will do this for us and save recursion/dispatching.
+ LOG((" in a root loop"));
+ return NS_OK;
+ }
+
+ if (mNextFilterIndex == mFiltersCopy.Length()) {
+ // We are done, all filters have been called on!
+ Finish();
+ return NS_OK;
+ }
+
+ // Redispatch, since we don't want long stacks when filters respond
+ // synchronously.
+ LOG((" redispatching"));
+ NS_DispatchToCurrentThread(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAsyncResolveRequest::AsyncApplyFilters::Run() {
+ LOG(("AsyncApplyFilters::Run %p", this));
+
+ MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread());
+
+ ProcessNextFilter();
+ return NS_OK;
+}
+
+nsresult nsAsyncResolveRequest::AsyncApplyFilters::Finish() {
+ LOG(("AsyncApplyFilters::Finish %p pi=%p", this, mProxyInfo.get()));
+
+ MOZ_ASSERT(mRequest);
+
+ mFiltersCopy.Clear();
+
+ RefPtr<nsAsyncResolveRequest> request;
+ request.swap(mRequest);
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ pi.swap(mProxyInfo);
+
+ request->mPPS->PruneProxyInfo(mInfo, pi);
+ return mCallback(request, pi, !mProcessingInLoop);
+}
+
+NS_IMETHODIMP
+nsAsyncResolveRequest::AsyncApplyFilters::Cancel(nsresult reason) {
+ LOG(("AsyncApplyFilters::Cancel %p", this));
+
+ MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread());
+
+ // This will be called only from inside the request, so don't call
+ // its's callback. Dropping the members means we simply break the cycle.
+ mFiltersCopy.Clear();
+ mProxyInfo = nullptr;
+ mRequest = nullptr;
+
+ return NS_OK;
+}
+
+// Bug 1366133: make GetPACURI off-main-thread since it may hang on Windows
+// platform
+class AsyncGetPACURIRequest final : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ using CallbackFunc = nsresult (nsProtocolProxyService::*)(bool, bool,
+ nsresult,
+ const nsACString&);
+
+ AsyncGetPACURIRequest(nsProtocolProxyService* aService,
+ CallbackFunc aCallback,
+ nsISystemProxySettings* aSystemProxySettings,
+ bool aMainThreadOnly, bool aForceReload,
+ bool aResetPACThread)
+ : mIsMainThreadOnly(aMainThreadOnly),
+ mService(aService),
+ mServiceHolder(do_QueryObject(aService)),
+ mCallback(aCallback),
+ mSystemProxySettings(aSystemProxySettings),
+ mForceReload(aForceReload),
+ mResetPACThread(aResetPACThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+ Unused << mIsMainThreadOnly;
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThreadOnly);
+
+ nsCString pacUri;
+ nsresult rv = mSystemProxySettings->GetPACURI(pacUri);
+
+ nsCOMPtr<nsIRunnable> event =
+ NewNonOwningCancelableRunnableMethod<bool, bool, nsresult, nsCString>(
+ "AsyncGetPACURIRequestCallback", mService, mCallback, mForceReload,
+ mResetPACThread, rv, pacUri);
+
+ return NS_DispatchToMainThread(event);
+ }
+
+ private:
+ ~AsyncGetPACURIRequest() {
+ NS_ReleaseOnMainThread("AsyncGetPACURIRequest::mServiceHolder",
+ mServiceHolder.forget());
+ }
+
+ bool mIsMainThreadOnly;
+
+ nsProtocolProxyService* mService; // ref-count is hold by mServiceHolder
+ nsCOMPtr<nsIProtocolProxyService2> mServiceHolder;
+ CallbackFunc mCallback;
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+
+ bool mForceReload;
+ bool mResetPACThread;
+};
+
+NS_IMPL_ISUPPORTS(AsyncGetPACURIRequest, nsIRunnable)
+
+//----------------------------------------------------------------------------
+
+//
+// apply mask to address (zeros out excluded bits).
+//
+// NOTE: we do the byte swapping here to minimize overall swapping.
+//
+static void proxy_MaskIPv6Addr(PRIPv6Addr& addr, uint16_t mask_len) {
+ if (mask_len == 128) return;
+
+ if (mask_len > 96) {
+ addr.pr_s6_addr32[3] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[3]) & (~0uL << (128 - mask_len)));
+ } else if (mask_len > 64) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[2]) & (~0uL << (96 - mask_len)));
+ } else if (mask_len > 32) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[1]) & (~0uL << (64 - mask_len)));
+ } else {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] = 0;
+ addr.pr_s6_addr32[0] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[0]) & (~0uL << (32 - mask_len)));
+ }
+}
+
+static void proxy_GetStringPref(nsIPrefBranch* aPrefBranch, const char* aPref,
+ nsCString& aResult) {
+ nsAutoCString temp;
+ nsresult rv = aPrefBranch->GetCharPref(aPref, temp);
+ if (NS_FAILED(rv)) {
+ aResult.Truncate();
+ } else {
+ aResult.Assign(temp);
+ // all of our string prefs are hostnames, so we should remove any
+ // whitespace characters that the user might have unknowingly entered.
+ aResult.StripWhitespace();
+ }
+}
+
+static void proxy_GetIntPref(nsIPrefBranch* aPrefBranch, const char* aPref,
+ int32_t& aResult) {
+ int32_t temp;
+ nsresult rv = aPrefBranch->GetIntPref(aPref, &temp);
+ if (NS_FAILED(rv)) {
+ aResult = -1;
+ } else {
+ aResult = temp;
+ }
+}
+
+static void proxy_GetBoolPref(nsIPrefBranch* aPrefBranch, const char* aPref,
+ bool& aResult) {
+ bool temp;
+ nsresult rv = aPrefBranch->GetBoolPref(aPref, &temp);
+ if (NS_FAILED(rv)) {
+ aResult = false;
+ } else {
+ aResult = temp;
+ }
+}
+
+//----------------------------------------------------------------------------
+
+static const int32_t PROXYCONFIG_DIRECT4X = 3;
+static const int32_t PROXYCONFIG_COUNT = 6;
+
+NS_IMPL_ADDREF(nsProtocolProxyService)
+NS_IMPL_RELEASE(nsProtocolProxyService)
+NS_IMPL_CLASSINFO(nsProtocolProxyService, nullptr, nsIClassInfo::SINGLETON,
+ NS_PROTOCOLPROXYSERVICE_CID)
+
+// NS_IMPL_QUERY_INTERFACE_CI with the nsProtocolProxyService QI change
+NS_INTERFACE_MAP_BEGIN(nsProtocolProxyService)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService2)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsProtocolProxyService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIProtocolProxyService)
+ NS_IMPL_QUERY_CLASSINFO(nsProtocolProxyService)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CI_INTERFACE_GETTER(nsProtocolProxyService, nsIProtocolProxyService,
+ nsIProtocolProxyService2)
+
+nsProtocolProxyService::nsProtocolProxyService() : mSessionStart(PR_Now()) {}
+
+nsProtocolProxyService::~nsProtocolProxyService() {
+ // These should have been cleaned up in our Observe method.
+ NS_ASSERTION(mHostFiltersArray.Length() == 0 && mFilters.Length() == 0 &&
+ mPACMan == nullptr,
+ "what happened to xpcom-shutdown?");
+}
+
+// nsProtocolProxyService methods
+nsresult nsProtocolProxyService::Init() {
+ // failure to access prefs is non-fatal
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ // monitor proxy prefs
+ prefBranch->AddObserver(PROXY_PREF_BRANCH, this, false);
+
+ // read all prefs
+ PrefsChanged(prefBranch, nullptr);
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ // register for shutdown notification so we can clean ourselves up
+ // properly.
+ obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ obs->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ }
+
+ return NS_OK;
+}
+
+// ReloadNetworkPAC() checks if there's a non-networked PAC in use then avoids
+// to call ReloadPAC()
+nsresult nsProtocolProxyService::ReloadNetworkPAC() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) {
+ return NS_OK;
+ }
+
+ int32_t type;
+ nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ if (type == PROXYCONFIG_PAC) {
+ nsAutoCString pacSpec;
+ prefs->GetCharPref(PROXY_PREF("autoconfig_url"), pacSpec);
+ if (!pacSpec.IsEmpty()) {
+ nsCOMPtr<nsIURI> pacURI;
+ rv = NS_NewURI(getter_AddRefs(pacURI), pacSpec);
+ if (!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ nsProtocolInfo pac;
+ rv = GetProtocolInfo(pacURI, &pac);
+ if (!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ if (!pac.scheme.EqualsLiteral("file") &&
+ !pac.scheme.EqualsLiteral("data")) {
+ LOG((": received network changed event, reload PAC"));
+ ReloadPAC();
+ }
+ }
+ } else if ((type == PROXYCONFIG_WPAD) || (type == PROXYCONFIG_SYSTEM)) {
+ ReloadPAC();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsProtocolProxyService::AsyncConfigureFromPAC(bool aForceReload,
+ bool aResetPACThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool mainThreadOnly;
+ nsresult rv = mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIRunnable> req = new AsyncGetPACURIRequest(
+ this, &nsProtocolProxyService::OnAsyncGetPACURI, mSystemProxySettings,
+ mainThreadOnly, aForceReload, aResetPACThread);
+
+ if (mainThreadOnly) {
+ return req->Run();
+ }
+
+ return NS_DispatchBackgroundTask(req.forget(),
+ nsIEventTarget::DISPATCH_NORMAL);
+}
+
+nsresult nsProtocolProxyService::OnAsyncGetPACURI(bool aForceReload,
+ bool aResetPACThread,
+ nsresult aResult,
+ const nsACString& aUri) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aResetPACThread) {
+ ResetPACThread();
+ }
+
+ if (NS_SUCCEEDED(aResult) && !aUri.IsEmpty()) {
+ ConfigureFromPAC(PromiseFlatCString(aUri), aForceReload);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ mIsShutdown = true;
+ // cleanup
+ mHostFiltersArray.Clear();
+ mFilters.Clear();
+
+ if (mPACMan) {
+ mPACMan->Shutdown();
+ mPACMan = nullptr;
+ }
+
+ if (mReloadPACTimer) {
+ mReloadPACTimer->Cancel();
+ mReloadPACTimer = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ } else if (strcmp(aTopic, NS_NETWORK_LINK_TOPIC) == 0) {
+ nsCString converted = NS_ConvertUTF16toUTF8(aData);
+ const char* state = converted.get();
+ if (!strcmp(state, NS_NETWORK_LINK_DATA_CHANGED)) {
+ uint32_t delay = StaticPrefs::network_proxy_reload_pac_delay();
+ LOG(("nsProtocolProxyService::Observe call ReloadNetworkPAC() delay=%u",
+ delay));
+
+ if (delay) {
+ if (mReloadPACTimer) {
+ mReloadPACTimer->Cancel();
+ mReloadPACTimer = nullptr;
+ }
+ NS_NewTimerWithCallback(getter_AddRefs(mReloadPACTimer), this, delay,
+ nsITimer::TYPE_ONE_SHOT);
+ } else {
+ ReloadNetworkPAC();
+ }
+ }
+ } else {
+ NS_ASSERTION(strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0,
+ "what is this random observer event?");
+ nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
+ if (prefs) PrefsChanged(prefs, NS_LossyConvertUTF16toASCII(aData).get());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(aTimer == mReloadPACTimer);
+ ReloadNetworkPAC();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsProtocolProxyService");
+ return NS_OK;
+}
+
+void nsProtocolProxyService::PrefsChanged(nsIPrefBranch* prefBranch,
+ const char* pref) {
+ nsresult rv = NS_OK;
+ bool reloadPAC = false;
+ nsAutoCString tempString;
+ auto invokeCallback =
+ MakeScopeExit([&] { NotifyProxyConfigChangedInternal(); });
+
+ if (!pref || !strcmp(pref, PROXY_PREF("type"))) {
+ int32_t type = -1;
+ rv = prefBranch->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_SUCCEEDED(rv)) {
+ // bug 115720 - for ns4.x backwards compatibility
+ if (type == PROXYCONFIG_DIRECT4X) {
+ type = PROXYCONFIG_DIRECT;
+ // Reset the type so that the dialog looks correct, and we
+ // don't have to handle this case everywhere else
+ // I'm paranoid about a loop of some sort - only do this
+ // if we're enumerating all prefs, and ignore any error
+ if (!pref) prefBranch->SetIntPref(PROXY_PREF("type"), type);
+ } else if (type >= PROXYCONFIG_COUNT) {
+ LOG(("unknown proxy type: %" PRId32 "; assuming direct\n", type));
+ type = PROXYCONFIG_DIRECT;
+ }
+ mProxyConfig = type;
+ reloadPAC = true;
+ }
+
+ if (mProxyConfig == PROXYCONFIG_SYSTEM) {
+ mSystemProxySettings = do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID);
+ if (!mSystemProxySettings) mProxyConfig = PROXYCONFIG_DIRECT;
+ ResetPACThread();
+ } else {
+ if (mSystemProxySettings) {
+ mSystemProxySettings = nullptr;
+ ResetPACThread();
+ }
+ }
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("http"))) {
+ proxy_GetStringPref(prefBranch, PROXY_PREF("http"), mHTTPProxyHost);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("http_port"))) {
+ proxy_GetIntPref(prefBranch, PROXY_PREF("http_port"), mHTTPProxyPort);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ssl"))) {
+ proxy_GetStringPref(prefBranch, PROXY_PREF("ssl"), mHTTPSProxyHost);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ssl_port"))) {
+ proxy_GetIntPref(prefBranch, PROXY_PREF("ssl_port"), mHTTPSProxyPort);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks"))) {
+ proxy_GetStringPref(prefBranch, PROXY_PREF("socks"), mSOCKSProxyTarget);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_port"))) {
+ proxy_GetIntPref(prefBranch, PROXY_PREF("socks_port"), mSOCKSProxyPort);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_version"))) {
+ int32_t version;
+ proxy_GetIntPref(prefBranch, PROXY_PREF("socks_version"), version);
+ // make sure this preference value remains sane
+ if (version == 5) {
+ mSOCKSProxyVersion = 5;
+ } else {
+ mSOCKSProxyVersion = 4;
+ }
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_remote_dns"))) {
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("socks_remote_dns"),
+ mSOCKSProxyRemoteDNS);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("proxy_over_tls"))) {
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("proxy_over_tls"), mProxyOverTLS);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("enable_wpad_over_dhcp"))) {
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("enable_wpad_over_dhcp"),
+ mWPADOverDHCPEnabled);
+ reloadPAC = reloadPAC || mProxyConfig == PROXYCONFIG_WPAD;
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("failover_timeout"))) {
+ proxy_GetIntPref(prefBranch, PROXY_PREF("failover_timeout"),
+ mFailedProxyTimeout);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("no_proxies_on"))) {
+ rv = prefBranch->GetCharPref(PROXY_PREF("no_proxies_on"), tempString);
+ if (NS_SUCCEEDED(rv)) LoadHostFilters(tempString);
+ }
+
+ // We're done if not using something that could give us a PAC URL
+ // (PAC, WPAD or System)
+ if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD &&
+ mProxyConfig != PROXYCONFIG_SYSTEM) {
+ return;
+ }
+
+ // OK, we need to reload the PAC file if:
+ // 1) network.proxy.type changed, or
+ // 2) network.proxy.autoconfig_url changed and PAC is configured
+
+ if (!pref || !strcmp(pref, PROXY_PREF("autoconfig_url"))) reloadPAC = true;
+
+ if (reloadPAC) {
+ tempString.Truncate();
+ if (mProxyConfig == PROXYCONFIG_PAC) {
+ prefBranch->GetCharPref(PROXY_PREF("autoconfig_url"), tempString);
+ if (mPACMan && !mPACMan->IsPACURI(tempString)) {
+ LOG(("PAC Thread URI Changed - Reset Pac Thread"));
+ ResetPACThread();
+ }
+ } else if (mProxyConfig == PROXYCONFIG_WPAD) {
+ LOG(("Auto-detecting proxy - Reset Pac Thread"));
+ ResetPACThread();
+ } else if (mSystemProxySettings) {
+ // Get System Proxy settings if available
+ AsyncConfigureFromPAC(false, false);
+ }
+ if (!tempString.IsEmpty() || mProxyConfig == PROXYCONFIG_WPAD) {
+ ConfigureFromPAC(tempString, false);
+ }
+ }
+}
+
+bool nsProtocolProxyService::CanUseProxy(nsIURI* aURI, int32_t defaultPort) {
+ int32_t port;
+ nsAutoCString host;
+
+ nsresult rv = aURI->GetAsciiHost(host);
+ if (NS_FAILED(rv) || host.IsEmpty()) return false;
+
+ rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv)) return false;
+ if (port == -1) port = defaultPort;
+
+ PRNetAddr addr;
+ bool is_ipaddr = (PR_StringToNetAddr(host.get(), &addr) == PR_SUCCESS);
+
+ PRIPv6Addr ipv6;
+ if (is_ipaddr) {
+ // convert parsed address to IPv6
+ if (addr.raw.family == PR_AF_INET) {
+ // convert to IPv4-mapped address
+ PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &ipv6);
+ } else if (addr.raw.family == PR_AF_INET6) {
+ // copy the address
+ memcpy(&ipv6, &addr.ipv6.ip, sizeof(PRIPv6Addr));
+ } else {
+ NS_WARNING("unknown address family");
+ return true; // allow proxying
+ }
+ }
+
+ // Don't use proxy for local hosts (plain hostname, no dots)
+ if ((!is_ipaddr && mFilterLocalHosts && !host.Contains('.')) ||
+ // This method detects if we have network.proxy.allow_hijacking_localhost
+ // pref enabled. If it's true then this method will always return false
+ // otherwise it returns true if the host matches an address that's
+ // hardcoded to the loopback address.
+ (!StaticPrefs::network_proxy_allow_hijacking_localhost() &&
+ nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(host))) {
+ LOG(("Not using proxy for this local host [%s]!\n", host.get()));
+ return false; // don't allow proxying
+ }
+
+ int32_t index = -1;
+ while (++index < int32_t(mHostFiltersArray.Length())) {
+ const auto& hinfo = mHostFiltersArray[index];
+
+ if (is_ipaddr != hinfo->is_ipaddr) continue;
+ if (hinfo->port && hinfo->port != port) continue;
+
+ if (is_ipaddr) {
+ // generate masked version of target IPv6 address
+ PRIPv6Addr masked;
+ memcpy(&masked, &ipv6, sizeof(PRIPv6Addr));
+ proxy_MaskIPv6Addr(masked, hinfo->ip.mask_len);
+
+ // check for a match
+ if (memcmp(&masked, &hinfo->ip.addr, sizeof(PRIPv6Addr)) == 0) {
+ return false; // proxy disallowed
+ }
+ } else {
+ uint32_t host_len = host.Length();
+ uint32_t filter_host_len = hinfo->name.host_len;
+
+ if (host_len >= filter_host_len) {
+ //
+ // compare last |filter_host_len| bytes of target hostname.
+ //
+ const char* host_tail = host.get() + host_len - filter_host_len;
+ if (!nsCRT::strncasecmp(host_tail, hinfo->name.host, filter_host_len)) {
+ // If the tail of the host string matches the filter
+
+ if (filter_host_len > 0 && hinfo->name.host[0] == '.') {
+ // If the filter was of the form .foo.bar.tld, all such
+ // matches are correct
+ return false; // proxy disallowed
+ }
+
+ // abc-def.example.org should not match def.example.org
+ // however, *.def.example.org should match .def.example.org
+ // We check that the filter doesn't start with a `.`. If it does,
+ // then the strncasecmp above should suffice. If it doesn't,
+ // then we should only consider it a match if the strncasecmp happened
+ // at a subdomain boundary
+ if (host_len > filter_host_len && *(host_tail - 1) == '.') {
+ // If the host was something.foo.bar.tld and the filter
+ // was foo.bar.tld, it's still a match.
+ // the character right before the tail must be a
+ // `.` for this to work
+ return false; // proxy disallowed
+ }
+
+ if (host_len == filter_host_len) {
+ // If the host and filter are of the same length,
+ // they should match
+ return false; // proxy disallowed
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+// kProxyType\* may be referred to externally in
+// nsProxyInfo in order to compare by string pointer
+const char kProxyType_HTTP[] = "http";
+const char kProxyType_HTTPS[] = "https";
+const char kProxyType_PROXY[] = "proxy";
+const char kProxyType_SOCKS[] = "socks";
+const char kProxyType_SOCKS4[] = "socks4";
+const char kProxyType_SOCKS5[] = "socks5";
+const char kProxyType_DIRECT[] = "direct";
+
+const char* nsProtocolProxyService::ExtractProxyInfo(const char* start,
+ uint32_t aResolveFlags,
+ nsProxyInfo** result) {
+ *result = nullptr;
+ uint32_t flags = 0;
+
+ // see BNF in ProxyAutoConfig.h and notes in nsISystemProxySettings.idl
+
+ // find end of proxy info delimiter
+ const char* end = start;
+ while (*end && *end != ';') ++end;
+
+ // find end of proxy type delimiter
+ const char* sp = start;
+ while (sp < end && *sp != ' ' && *sp != '\t') ++sp;
+
+ uint32_t len = sp - start;
+ const char* type = nullptr;
+ switch (len) {
+ case 4:
+ if (nsCRT::strncasecmp(start, kProxyType_HTTP, 4) == 0) {
+ type = kProxyType_HTTP;
+ }
+ break;
+ case 5:
+ if (nsCRT::strncasecmp(start, kProxyType_PROXY, 5) == 0) {
+ type = kProxyType_HTTP;
+ } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS, 5) == 0) {
+ type = kProxyType_SOCKS4; // assume v4 for 4x compat
+ if (StaticPrefs::network_proxy_default_pac_script_socks_version() ==
+ 5) {
+ type = kProxyType_SOCKS;
+ }
+ } else if (nsCRT::strncasecmp(start, kProxyType_HTTPS, 5) == 0) {
+ type = kProxyType_HTTPS;
+ }
+ break;
+ case 6:
+ if (nsCRT::strncasecmp(start, kProxyType_DIRECT, 6) == 0) {
+ type = kProxyType_DIRECT;
+ } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS4, 6) == 0) {
+ type = kProxyType_SOCKS4;
+ } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS5, 6) == 0) {
+ // map "SOCKS5" to "socks" to match contract-id of registered
+ // SOCKS-v5 socket provider.
+ type = kProxyType_SOCKS;
+ }
+ break;
+ }
+ if (type) {
+ int32_t port = -1;
+
+ // If it's a SOCKS5 proxy, do name resolution on the server side.
+ // We could use this with SOCKS4a servers too, but they might not
+ // support it.
+ if (type == kProxyType_SOCKS || mSOCKSProxyRemoteDNS) {
+ flags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+
+ // extract host:port
+ start = sp;
+ while ((*start == ' ' || *start == '\t') && start < end) start++;
+
+ // port defaults
+ if (type == kProxyType_HTTP) {
+ port = 80;
+ } else if (type == kProxyType_HTTPS) {
+ port = 443;
+ } else {
+ port = 1080;
+ }
+
+ RefPtr<nsProxyInfo> pi = new nsProxyInfo();
+ pi->mType = type;
+ pi->mFlags = flags;
+ pi->mResolveFlags = aResolveFlags;
+ pi->mTimeout = mFailedProxyTimeout;
+
+ // www.foo.com:8080 and http://www.foo.com:8080
+ nsDependentCSubstring maybeURL(start, end - start);
+ nsCOMPtr<nsIURI> pacURI;
+
+ nsAutoCString urlHost;
+ // First assume the scheme is present, e.g. http://www.example.com:8080
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(pacURI), maybeURL)) ||
+ NS_FAILED(pacURI->GetAsciiHost(urlHost)) || urlHost.IsEmpty()) {
+ // It isn't, assume www.example.com:8080
+ maybeURL.Insert("http://", 0);
+
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(pacURI), maybeURL))) {
+ pacURI->GetAsciiHost(urlHost);
+ }
+ }
+
+ if (!urlHost.IsEmpty()) {
+ pi->mHost = urlHost;
+
+ int32_t tPort;
+ if (NS_SUCCEEDED(pacURI->GetPort(&tPort)) && tPort != -1) {
+ port = tPort;
+ }
+ pi->mPort = port;
+ }
+
+ pi.forget(result);
+ }
+
+ while (*end == ';' || *end == ' ' || *end == '\t') ++end;
+ return end;
+}
+
+void nsProtocolProxyService::GetProxyKey(nsProxyInfo* pi, nsCString& key) {
+ key.AssignASCII(pi->mType);
+ if (!pi->mHost.IsEmpty()) {
+ key.Append(' ');
+ key.Append(pi->mHost);
+ key.Append(':');
+ key.AppendInt(pi->mPort);
+ }
+}
+
+uint32_t nsProtocolProxyService::SecondsSinceSessionStart() {
+ PRTime now = PR_Now();
+
+ // get time elapsed since session start
+ int64_t diff = now - mSessionStart;
+
+ // convert microseconds to seconds
+ diff /= PR_USEC_PER_SEC;
+
+ // return converted 32 bit value
+ return uint32_t(diff);
+}
+
+void nsProtocolProxyService::EnableProxy(nsProxyInfo* pi) {
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+ mFailedProxies.Remove(key);
+}
+
+void nsProtocolProxyService::DisableProxy(nsProxyInfo* pi) {
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+
+ uint32_t dsec = SecondsSinceSessionStart();
+
+ // Add timeout to interval (this is the time when the proxy can
+ // be tried again).
+ dsec += pi->mTimeout;
+
+ // NOTE: The classic codebase would increase the timeout value
+ // incrementally each time a subsequent failure occurred.
+ // We could do the same, but it would require that we not
+ // remove proxy entries in IsProxyDisabled or otherwise
+ // change the way we are recording disabled proxies.
+ // Simpler is probably better for now, and at least the
+ // user can tune the timeout setting via preferences.
+
+ LOG(("DisableProxy %s %d\n", key.get(), dsec));
+
+ // If this fails, oh well... means we don't have enough memory
+ // to remember the failed proxy.
+ mFailedProxies.InsertOrUpdate(key, dsec);
+}
+
+bool nsProtocolProxyService::IsProxyDisabled(nsProxyInfo* pi) {
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+
+ uint32_t val;
+ if (!mFailedProxies.Get(key, &val)) return false;
+
+ uint32_t dsec = SecondsSinceSessionStart();
+
+ // if time passed has exceeded interval, then try proxy again.
+ if (dsec > val) {
+ mFailedProxies.Remove(key);
+ return false;
+ }
+
+ return true;
+}
+
+nsresult nsProtocolProxyService::SetupPACThread(
+ nsISerialEventTarget* mainThreadEventTarget) {
+ if (mIsShutdown) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mPACMan) return NS_OK;
+
+ mPACMan = new nsPACMan(mainThreadEventTarget);
+
+ bool mainThreadOnly;
+ nsresult rv;
+ if (mSystemProxySettings &&
+ NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) &&
+ !mainThreadOnly) {
+ rv = mPACMan->Init(mSystemProxySettings);
+ } else {
+ rv = mPACMan->Init(nullptr);
+ }
+ if (NS_FAILED(rv)) {
+ mPACMan->Shutdown();
+ mPACMan = nullptr;
+ }
+ return rv;
+}
+
+nsresult nsProtocolProxyService::ResetPACThread() {
+ if (!mPACMan) return NS_OK;
+
+ mPACMan->Shutdown();
+ mPACMan = nullptr;
+ return SetupPACThread();
+}
+
+nsresult nsProtocolProxyService::ConfigureFromPAC(const nsCString& spec,
+ bool forceReload) {
+ nsresult rv = SetupPACThread();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool autodetect = spec.IsEmpty();
+ if (!forceReload && ((!autodetect && mPACMan->IsPACURI(spec)) ||
+ (autodetect && mPACMan->IsUsingWPAD()))) {
+ return NS_OK;
+ }
+
+ mFailedProxies.Clear();
+
+ mPACMan->SetWPADOverDHCPEnabled(mWPADOverDHCPEnabled);
+ return mPACMan->LoadPACFromURI(spec);
+}
+
+void nsProtocolProxyService::ProcessPACString(const nsCString& pacString,
+ uint32_t aResolveFlags,
+ nsIProxyInfo** result) {
+ if (pacString.IsEmpty()) {
+ *result = nullptr;
+ return;
+ }
+
+ const char* proxies = pacString.get();
+
+ nsProxyInfo *pi = nullptr, *first = nullptr, *last = nullptr;
+ while (*proxies) {
+ proxies = ExtractProxyInfo(proxies, aResolveFlags, &pi);
+ if (pi && (pi->mType == kProxyType_HTTPS) && !mProxyOverTLS) {
+ delete pi;
+ pi = nullptr;
+ }
+
+ if (pi) {
+ if (last) {
+ NS_ASSERTION(last->mNext == nullptr, "leaking nsProxyInfo");
+ last->mNext = pi;
+ } else {
+ first = pi;
+ }
+ last = pi;
+ }
+ }
+ *result = first;
+}
+
+// nsIProtocolProxyService2
+NS_IMETHODIMP
+nsProtocolProxyService::ReloadPAC() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) return NS_OK;
+
+ int32_t type;
+ nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsAutoCString pacSpec;
+ if (type == PROXYCONFIG_PAC) {
+ prefs->GetCharPref(PROXY_PREF("autoconfig_url"), pacSpec);
+ } else if (type == PROXYCONFIG_SYSTEM) {
+ if (mSystemProxySettings) {
+ AsyncConfigureFromPAC(true, true);
+ } else {
+ ResetPACThread();
+ }
+ }
+
+ if (!pacSpec.IsEmpty() || type == PROXYCONFIG_WPAD) {
+ ConfigureFromPAC(pacSpec, true);
+ }
+ return NS_OK;
+}
+
+// When sync interface is removed this can go away too
+// The nsPACManCallback portion of this implementation should be run
+// off the main thread, because it uses a condvar for signaling and
+// the main thread is blocking on that condvar -
+// so call nsPACMan::AsyncGetProxyForURI() with
+// a false mainThreadResponse parameter.
+class nsAsyncBridgeRequest final : public nsPACManCallback {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsAsyncBridgeRequest()
+ : mMutex("nsDeprecatedCallback"),
+ mCondVar(mMutex, "nsDeprecatedCallback") {}
+
+ void OnQueryComplete(nsresult status, const nsACString& pacString,
+ const nsACString& newPACURL) override {
+ MutexAutoLock lock(mMutex);
+ mCompleted = true;
+ mStatus = status;
+ mPACString = pacString;
+ mPACURL = newPACURL;
+ mCondVar.Notify();
+ }
+
+ void Lock() MOZ_CAPABILITY_ACQUIRE(mMutex) { mMutex.Lock(); }
+ void Unlock() MOZ_CAPABILITY_RELEASE(mMutex) { mMutex.Unlock(); }
+ void Wait() { mCondVar.Wait(TimeDuration::FromSeconds(3)); }
+
+ private:
+ ~nsAsyncBridgeRequest() = default;
+
+ friend class nsProtocolProxyService;
+
+ Mutex mMutex;
+ CondVar mCondVar;
+
+ nsresult mStatus MOZ_GUARDED_BY(mMutex){NS_OK};
+ nsCString mPACString MOZ_GUARDED_BY(mMutex);
+ nsCString mPACURL MOZ_GUARDED_BY(mMutex);
+ bool mCompleted MOZ_GUARDED_BY(mMutex){false};
+};
+NS_IMPL_ISUPPORTS0(nsAsyncBridgeRequest)
+
+nsresult nsProtocolProxyService::AsyncResolveInternal(
+ nsIChannel* channel, uint32_t flags, nsIProtocolProxyCallback* callback,
+ nsICancelable** result, bool isSyncOK,
+ nsISerialEventTarget* mainThreadEventTarget) {
+ NS_ENSURE_ARG_POINTER(channel);
+ NS_ENSURE_ARG_POINTER(callback);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetProxyURI(channel, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ *result = nullptr;
+ RefPtr<nsAsyncResolveRequest> ctx =
+ new nsAsyncResolveRequest(this, channel, flags, callback);
+
+ nsProtocolInfo info;
+ rv = GetProtocolInfo(uri, &info);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxyInfo> pi;
+ bool usePACThread;
+
+ // adapt to realtime changes in the system proxy service
+ if (mProxyConfig == PROXYCONFIG_SYSTEM) {
+ nsCOMPtr<nsISystemProxySettings> sp2 =
+ do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID);
+ if (sp2 != mSystemProxySettings) {
+ mSystemProxySettings = sp2;
+ ResetPACThread();
+ }
+ }
+
+ rv = SetupPACThread(mainThreadEventTarget);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // SystemProxySettings and PAC files can block the main thread
+ // but if neither of them are in use, we can just do the work
+ // right here and directly invoke the callback
+
+ rv =
+ Resolve_Internal(channel, info, flags, &usePACThread, getter_AddRefs(pi));
+ if (NS_FAILED(rv)) return rv;
+
+ if (!usePACThread || !mPACMan) {
+ // we can do it locally
+ rv = ctx->ProcessLocally(info, pi, isSyncOK);
+ if (NS_SUCCEEDED(rv) && !isSyncOK) {
+ ctx.forget(result);
+ }
+ return rv;
+ }
+
+ // else kick off a PAC thread query
+ rv = mPACMan->AsyncGetProxyForURI(uri, ctx, flags, true);
+ if (NS_SUCCEEDED(rv)) ctx.forget(result);
+ return rv;
+}
+
+// nsIProtocolProxyService
+NS_IMETHODIMP
+nsProtocolProxyService::AsyncResolve2(
+ nsIChannel* channel, uint32_t flags, nsIProtocolProxyCallback* callback,
+ nsISerialEventTarget* mainThreadEventTarget, nsICancelable** result) {
+ return AsyncResolveInternal(channel, flags, callback, result, true,
+ mainThreadEventTarget);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::AsyncResolve(
+ nsISupports* channelOrURI, uint32_t flags,
+ nsIProtocolProxyCallback* callback,
+ nsISerialEventTarget* mainThreadEventTarget, nsICancelable** result) {
+ nsresult rv;
+ // Check if we got a channel:
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(channelOrURI);
+ if (!channel) {
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(channelOrURI);
+ if (!uri) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ // creating a temporary channel from the URI which is not
+ // used to perform any network loads, hence its safe to
+ // use systemPrincipal as the loadingPrincipal.
+ rv = NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return AsyncResolveInternal(channel, flags, callback, result, false,
+ mainThreadEventTarget);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::NewProxyInfo(
+ const nsACString& aType, const nsACString& aHost, int32_t aPort,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey, uint32_t aFlags,
+ uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy,
+ nsIProxyInfo** aResult) {
+ return NewProxyInfoWithAuth(aType, aHost, aPort, ""_ns, ""_ns,
+ aProxyAuthorizationHeader,
+ aConnectionIsolationKey, aFlags, aFailoverTimeout,
+ aFailoverProxy, aResult);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::NewProxyInfoWithAuth(
+ const nsACString& aType, const nsACString& aHost, int32_t aPort,
+ const nsACString& aUsername, const nsACString& aPassword,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey, uint32_t aFlags,
+ uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy,
+ nsIProxyInfo** aResult) {
+ static const char* types[] = {kProxyType_HTTP, kProxyType_HTTPS,
+ kProxyType_SOCKS, kProxyType_SOCKS4,
+ kProxyType_DIRECT};
+
+ // resolve type; this allows us to avoid copying the type string into each
+ // proxy info instance. we just reference the string literals directly :)
+ const char* type = nullptr;
+ for (auto& t : types) {
+ if (aType.LowerCaseEqualsASCII(t)) {
+ type = t;
+ break;
+ }
+ }
+ NS_ENSURE_TRUE(type, NS_ERROR_INVALID_ARG);
+
+ // We have only implemented username/password for SOCKS proxies.
+ if ((!aUsername.IsEmpty() || !aPassword.IsEmpty()) &&
+ !aType.LowerCaseEqualsASCII(kProxyType_SOCKS) &&
+ !aType.LowerCaseEqualsASCII(kProxyType_SOCKS4)) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NewProxyInfo_Internal(type, aHost, aPort, aUsername, aPassword,
+ aProxyAuthorizationHeader,
+ aConnectionIsolationKey, aFlags,
+ aFailoverTimeout, aFailoverProxy, 0, aResult);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::GetFailoverForProxy(nsIProxyInfo* aProxy, nsIURI* aURI,
+ nsresult aStatus,
+ nsIProxyInfo** aResult) {
+ // Failover is supported through a variety of methods including:
+ // * PAC scripts (PROXYCONFIG_PAC and PROXYCONFIG_WPAD)
+ // * System proxy
+ // * Extensions
+ // With extensions the mProxyConfig can be any type and the extension
+ // is still involved in the proxy filtering. It may have also supplied
+ // any number of failover proxies. We cannot determine what the mix is
+ // here, so we will attempt to get a failover regardless of the config
+ // type. MANUAL configuration will not disable a proxy.
+
+ // Verify that |aProxy| is one of our nsProxyInfo objects.
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy);
+ NS_ENSURE_ARG(pi);
+ // OK, the QI checked out. We can proceed.
+
+ // Remember that this proxy is down. If the user has manually configured some
+ // proxies we do not want to disable them.
+ if (mProxyConfig != PROXYCONFIG_MANUAL) {
+ DisableProxy(pi);
+ }
+
+ // NOTE: At this point, we might want to prompt the user if we have
+ // not already tried going DIRECT. This is something that the
+ // classic codebase supported; however, IE6 does not prompt.
+
+ if (!pi->mNext) return NS_ERROR_NOT_AVAILABLE;
+
+ LOG(("PAC failover from %s %s:%d to %s %s:%d\n", pi->mType, pi->mHost.get(),
+ pi->mPort, pi->mNext->mType, pi->mNext->mHost.get(), pi->mNext->mPort));
+
+ *aResult = do_AddRef(pi->mNext).take();
+ return NS_OK;
+}
+
+namespace { // anon
+
+class ProxyFilterPositionComparator {
+ using FilterLinkRef = RefPtr<nsProtocolProxyService::FilterLink>;
+
+ public:
+ bool Equals(const FilterLinkRef& a, const FilterLinkRef& b) const {
+ return a->position == b->position;
+ }
+ bool LessThan(const FilterLinkRef& a, const FilterLinkRef& b) const {
+ return a->position < b->position;
+ }
+};
+
+class ProxyFilterObjectComparator {
+ using FilterLinkRef = RefPtr<nsProtocolProxyService::FilterLink>;
+
+ public:
+ bool Equals(const FilterLinkRef& link, const nsISupports* obj) const {
+ return obj == nsCOMPtr<nsISupports>(do_QueryInterface(link->filter)) ||
+ obj == nsCOMPtr<nsISupports>(do_QueryInterface(link->channelFilter));
+ }
+};
+
+} // namespace
+
+nsresult nsProtocolProxyService::InsertFilterLink(RefPtr<FilterLink>&& link) {
+ LOG(("nsProtocolProxyService::InsertFilterLink filter=%p", link.get()));
+
+ if (mIsShutdown) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we add a new element with the same position as an existing one, we want
+ // to preserve the insertion order to avoid surprises.
+ mFilters.InsertElementSorted(link, ProxyFilterPositionComparator());
+
+ NotifyProxyConfigChangedInternal();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::RegisterFilter(nsIProtocolProxyFilter* filter,
+ uint32_t position) {
+ UnregisterFilter(filter); // remove this filter if we already have it
+
+ RefPtr<FilterLink> link = new FilterLink(position, filter);
+ return InsertFilterLink(std::move(link));
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::RegisterChannelFilter(
+ nsIProtocolProxyChannelFilter* channelFilter, uint32_t position) {
+ UnregisterChannelFilter(
+ channelFilter); // remove this filter if we already have it
+
+ RefPtr<FilterLink> link = new FilterLink(position, channelFilter);
+ return InsertFilterLink(std::move(link));
+}
+
+nsresult nsProtocolProxyService::RemoveFilterLink(nsISupports* givenObject) {
+ LOG(("nsProtocolProxyService::RemoveFilterLink target=%p", givenObject));
+
+ nsresult rv =
+ mFilters.RemoveElement(givenObject, ProxyFilterObjectComparator())
+ ? NS_OK
+ : NS_ERROR_UNEXPECTED;
+ if (NS_SUCCEEDED(rv)) {
+ NotifyProxyConfigChangedInternal();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::UnregisterFilter(nsIProtocolProxyFilter* filter) {
+ // QI to nsISupports so we can safely test object identity.
+ nsCOMPtr<nsISupports> givenObject = do_QueryInterface(filter);
+ return RemoveFilterLink(givenObject);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::UnregisterChannelFilter(
+ nsIProtocolProxyChannelFilter* channelFilter) {
+ // QI to nsISupports so we can safely test object identity.
+ nsCOMPtr<nsISupports> givenObject = do_QueryInterface(channelFilter);
+ return RemoveFilterLink(givenObject);
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::GetProxyConfigType(uint32_t* aProxyConfigType) {
+ *aProxyConfigType = mProxyConfig;
+ return NS_OK;
+}
+
+void nsProtocolProxyService::LoadHostFilters(const nsACString& aFilters) {
+ if (mIsShutdown) {
+ return;
+ }
+
+ // check to see the owners flag? /!?/ TODO
+ if (mHostFiltersArray.Length() > 0) {
+ mHostFiltersArray.Clear();
+ }
+
+ // Reset mFilterLocalHosts - will be set to true if "<local>" is in pref
+ // string
+ mFilterLocalHosts = false;
+
+ if (aFilters.IsEmpty()) {
+ return;
+ }
+
+ //
+ // filter = ( host | domain | ipaddr ["/" mask] ) [":" port]
+ // filters = filter *( "," LWS filter)
+ //
+ mozilla::Tokenizer t(aFilters);
+ mozilla::Tokenizer::Token token;
+ bool eof = false;
+ // while (*filters) {
+ while (!eof) {
+ // skip over spaces and ,
+ t.SkipWhites();
+ while (t.CheckChar(',')) {
+ t.SkipWhites();
+ }
+
+ nsAutoCString portStr;
+ nsAutoCString hostStr;
+ nsAutoCString maskStr;
+ t.Record();
+
+ bool parsingIPv6 = false;
+ bool parsingPort = false;
+ bool parsingMask = false;
+ while (t.Next(token)) {
+ if (token.Equals(mozilla::Tokenizer::Token::EndOfFile())) {
+ eof = true;
+ break;
+ }
+ if (token.Equals(mozilla::Tokenizer::Token::Char(',')) ||
+ token.Type() == mozilla::Tokenizer::TOKEN_WS) {
+ break;
+ }
+
+ if (token.Equals(mozilla::Tokenizer::Token::Char('['))) {
+ parsingIPv6 = true;
+ continue;
+ }
+
+ if (!parsingIPv6 && token.Equals(mozilla::Tokenizer::Token::Char(':'))) {
+ // Port is starting. Claim the previous as host.
+ if (parsingMask) {
+ t.Claim(maskStr);
+ } else {
+ t.Claim(hostStr);
+ }
+ t.Record();
+ parsingPort = true;
+ continue;
+ }
+
+ if (token.Equals(mozilla::Tokenizer::Token::Char('/'))) {
+ t.Claim(hostStr);
+ t.Record();
+ parsingMask = true;
+ continue;
+ }
+
+ if (token.Equals(mozilla::Tokenizer::Token::Char(']'))) {
+ parsingIPv6 = false;
+ continue;
+ }
+ }
+ if (!parsingPort && !parsingMask) {
+ t.Claim(hostStr);
+ } else if (parsingPort) {
+ t.Claim(portStr);
+ } else if (parsingMask) {
+ t.Claim(maskStr);
+ } else {
+ NS_WARNING("Could not parse this rule");
+ continue;
+ }
+
+ if (hostStr.IsEmpty()) {
+ continue;
+ }
+
+ // If the current host filter is "<local>", then all local (i.e.
+ // no dots in the hostname) hosts should bypass the proxy
+ if (hostStr.EqualsIgnoreCase("<local>")) {
+ mFilterLocalHosts = true;
+ LOG(
+ ("loaded filter for local hosts "
+ "(plain host names, no dots)\n"));
+ // Continue to next host filter;
+ continue;
+ }
+
+ // For all other host filters, create HostInfo object and add to list
+ HostInfo* hinfo = new HostInfo();
+ nsresult rv = NS_OK;
+
+ int32_t port = portStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ port = 0;
+ }
+ hinfo->port = port;
+
+ int32_t maskLen = maskStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ maskLen = 128;
+ }
+
+ // PR_StringToNetAddr can't parse brackets enclosed IPv6
+ nsAutoCString addrString = hostStr;
+ if (hostStr.First() == '[' && hostStr.Last() == ']') {
+ addrString = Substring(hostStr, 1, hostStr.Length() - 2);
+ }
+
+ PRNetAddr addr;
+ if (PR_StringToNetAddr(addrString.get(), &addr) == PR_SUCCESS) {
+ hinfo->is_ipaddr = true;
+ hinfo->ip.family = PR_AF_INET6; // we always store address as IPv6
+ hinfo->ip.mask_len = maskLen;
+
+ if (hinfo->ip.mask_len == 0) {
+ NS_WARNING("invalid mask");
+ goto loser;
+ }
+
+ if (addr.raw.family == PR_AF_INET) {
+ // convert to IPv4-mapped address
+ PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &hinfo->ip.addr);
+ // adjust mask_len accordingly
+ if (hinfo->ip.mask_len <= 32) hinfo->ip.mask_len += 96;
+ } else if (addr.raw.family == PR_AF_INET6) {
+ // copy the address
+ memcpy(&hinfo->ip.addr, &addr.ipv6.ip, sizeof(PRIPv6Addr));
+ } else {
+ NS_WARNING("unknown address family");
+ goto loser;
+ }
+
+ // apply mask to IPv6 address
+ proxy_MaskIPv6Addr(hinfo->ip.addr, hinfo->ip.mask_len);
+ } else {
+ nsAutoCString host;
+ if (hostStr.First() == '*') {
+ host = Substring(hostStr, 1);
+ } else {
+ host = hostStr;
+ }
+
+ if (host.IsEmpty()) {
+ hinfo->name.host = nullptr;
+ goto loser;
+ }
+
+ hinfo->name.host_len = host.Length();
+
+ hinfo->is_ipaddr = false;
+ hinfo->name.host = ToNewCString(host, mozilla::fallible);
+
+ if (!hinfo->name.host) goto loser;
+ }
+
+// #define DEBUG_DUMP_FILTERS
+#ifdef DEBUG_DUMP_FILTERS
+ printf("loaded filter[%zu]:\n", mHostFiltersArray.Length());
+ printf(" is_ipaddr = %u\n", hinfo->is_ipaddr);
+ printf(" port = %u\n", hinfo->port);
+ printf(" host = %s\n", hostStr.get());
+ if (hinfo->is_ipaddr) {
+ printf(" ip.family = %x\n", hinfo->ip.family);
+ printf(" ip.mask_len = %u\n", hinfo->ip.mask_len);
+
+ PRNetAddr netAddr;
+ PR_SetNetAddr(PR_IpAddrNull, PR_AF_INET6, 0, &netAddr);
+ memcpy(&netAddr.ipv6.ip, &hinfo->ip.addr, sizeof(hinfo->ip.addr));
+
+ char buf[256];
+ PR_NetAddrToString(&netAddr, buf, sizeof(buf));
+
+ printf(" ip.addr = %s\n", buf);
+ } else {
+ printf(" name.host = %s\n", hinfo->name.host);
+ }
+#endif
+
+ mHostFiltersArray.AppendElement(hinfo);
+ hinfo = nullptr;
+ loser:
+ delete hinfo;
+ }
+}
+
+nsresult nsProtocolProxyService::GetProtocolInfo(nsIURI* uri,
+ nsProtocolInfo* info) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(uri, "URI is null");
+ MOZ_ASSERT(info, "info is null");
+
+ nsresult rv;
+
+ rv = uri->GetScheme(info->scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ios->GetDynamicProtocolFlags(uri, &info->flags);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = ios->GetDefaultPort(info->scheme.get(), &info->defaultPort);
+ return rv;
+}
+
+nsresult nsProtocolProxyService::NewProxyInfo_Internal(
+ const char* aType, const nsACString& aHost, int32_t aPort,
+ const nsACString& aUsername, const nsACString& aPassword,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey, uint32_t aFlags,
+ uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy,
+ uint32_t aResolveFlags, nsIProxyInfo** aResult) {
+ if (aPort <= 0) aPort = -1;
+
+ nsCOMPtr<nsProxyInfo> failover;
+ if (aFailoverProxy) {
+ failover = do_QueryInterface(aFailoverProxy);
+ NS_ENSURE_ARG(failover);
+ }
+
+ RefPtr<nsProxyInfo> proxyInfo = new nsProxyInfo();
+
+ proxyInfo->mType = aType;
+ proxyInfo->mHost = aHost;
+ proxyInfo->mPort = aPort;
+ proxyInfo->mUsername = aUsername;
+ proxyInfo->mPassword = aPassword;
+ proxyInfo->mFlags = aFlags;
+ proxyInfo->mResolveFlags = aResolveFlags;
+ proxyInfo->mTimeout =
+ aFailoverTimeout == UINT32_MAX ? mFailedProxyTimeout : aFailoverTimeout;
+ proxyInfo->mProxyAuthorizationHeader = aProxyAuthorizationHeader;
+ proxyInfo->mConnectionIsolationKey = aConnectionIsolationKey;
+ failover.swap(proxyInfo->mNext);
+
+ proxyInfo.forget(aResult);
+ return NS_OK;
+}
+
+nsresult nsProtocolProxyService::Resolve_Internal(nsIChannel* channel,
+ const nsProtocolInfo& info,
+ uint32_t flags,
+ bool* usePACThread,
+ nsIProxyInfo** result) {
+ NS_ENSURE_ARG_POINTER(channel);
+
+ *usePACThread = false;
+ *result = nullptr;
+
+ if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY)) {
+ return NS_OK; // Can't proxy this (filters may not override)
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetProxyURI(channel, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ // See bug #586908.
+ // Avoid endless loop if |uri| is the current PAC-URI. Returning OK
+ // here means that we will not use a proxy for this connection.
+ if (mPACMan && mPACMan->IsPACURI(uri)) return NS_OK;
+
+ // if proxies are enabled and this host:port combo is supposed to use a
+ // proxy, check for a proxy.
+ if ((mProxyConfig == PROXYCONFIG_DIRECT) ||
+ !CanUseProxy(uri, info.defaultPort)) {
+ return NS_OK;
+ }
+
+ bool mainThreadOnly;
+ if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM &&
+ NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) &&
+ !mainThreadOnly) {
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM) {
+ // If the system proxy setting implementation is not threadsafe (e.g
+ // linux gconf), we'll do it inline here. Such implementations promise
+ // not to block
+ // bug 1366133: this block uses GetPACURI & GetProxyForURI, which may
+ // hang on Windows platform. Fortunately, current implementation on
+ // Windows is not main thread only, so we are safe here.
+
+ nsAutoCString PACURI;
+ nsAutoCString pacString;
+
+ if (NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) &&
+ !PACURI.IsEmpty()) {
+ // There is a PAC URI configured. If it is unchanged, then
+ // just execute the PAC thread. If it is changed then load
+ // the new value
+
+ if (mPACMan && mPACMan->IsPACURI(PACURI)) {
+ // unchanged
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ ConfigureFromPAC(PACURI, false);
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ nsAutoCString host;
+ nsAutoCString scheme;
+ int32_t port = -1;
+
+ uri->GetAsciiSpec(spec);
+ uri->GetAsciiHost(host);
+ uri->GetScheme(scheme);
+ uri->GetPort(&port);
+
+ if (flags & RESOLVE_PREFER_SOCKS_PROXY) {
+ LOG(("Ignoring RESOLVE_PREFER_SOCKS_PROXY for system proxy setting\n"));
+ } else if (flags & RESOLVE_PREFER_HTTPS_PROXY) {
+ scheme.AssignLiteral("https");
+ } else if (flags & RESOLVE_IGNORE_URI_SCHEME) {
+ scheme.AssignLiteral("http");
+ }
+
+ // now try the system proxy settings for this particular url
+ if (NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(spec, scheme, host,
+ port, pacString))) {
+ nsCOMPtr<nsIProxyInfo> pi;
+ ProcessPACString(pacString, 0, getter_AddRefs(pi));
+
+ if (flags & RESOLVE_PREFER_SOCKS_PROXY &&
+ flags & RESOLVE_PREFER_HTTPS_PROXY) {
+ nsAutoCString type;
+ pi->GetType(type);
+ // DIRECT from ProcessPACString indicates that system proxy settings
+ // are not configured to use SOCKS proxy. Try https proxy as a
+ // secondary preferrable proxy. This is mainly for websocket whose
+ // proxy precedence is SOCKS > HTTPS > DIRECT.
+ if (type.EqualsLiteral(kProxyType_DIRECT)) {
+ scheme.AssignLiteral(kProxyType_HTTPS);
+ if (NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(
+ spec, scheme, host, port, pacString))) {
+ ProcessPACString(pacString, 0, getter_AddRefs(pi));
+ }
+ }
+ }
+ pi.forget(result);
+ return NS_OK;
+ }
+ }
+
+ // if proxies are enabled and this host:port combo is supposed to use a
+ // proxy, check for a proxy.
+ if (mProxyConfig == PROXYCONFIG_DIRECT ||
+ (mProxyConfig == PROXYCONFIG_MANUAL &&
+ !CanUseProxy(uri, info.defaultPort))) {
+ return NS_OK;
+ }
+
+ // Proxy auto config magic...
+ if (mProxyConfig == PROXYCONFIG_PAC || mProxyConfig == PROXYCONFIG_WPAD) {
+ // Do not query PAC now.
+ *usePACThread = true;
+ return NS_OK;
+ }
+
+ // If we aren't in manual proxy configuration mode then we don't
+ // want to honor any manual specific prefs that might be still set
+ if (mProxyConfig != PROXYCONFIG_MANUAL) return NS_OK;
+
+ // proxy info values for manual configuration mode
+ const char* type = nullptr;
+ const nsACString* host = nullptr;
+ int32_t port = -1;
+
+ uint32_t proxyFlags = 0;
+
+ if ((flags & RESOLVE_PREFER_SOCKS_PROXY) && !mSOCKSProxyTarget.IsEmpty() &&
+ (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) {
+ host = &mSOCKSProxyTarget;
+ if (mSOCKSProxyVersion == 4) {
+ type = kProxyType_SOCKS4;
+ } else {
+ type = kProxyType_SOCKS;
+ }
+ port = mSOCKSProxyPort;
+ if (mSOCKSProxyRemoteDNS) {
+ proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+ } else if ((flags & RESOLVE_PREFER_HTTPS_PROXY) &&
+ !mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0) {
+ host = &mHTTPSProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPSProxyPort;
+ } else if (!mHTTPProxyHost.IsEmpty() && mHTTPProxyPort > 0 &&
+ ((flags & RESOLVE_IGNORE_URI_SCHEME) ||
+ info.scheme.EqualsLiteral("http"))) {
+ host = &mHTTPProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPProxyPort;
+ } else if (!mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0 &&
+ !(flags & RESOLVE_IGNORE_URI_SCHEME) &&
+ info.scheme.EqualsLiteral("https")) {
+ host = &mHTTPSProxyHost;
+ type = kProxyType_HTTP;
+ port = mHTTPSProxyPort;
+ } else if (!mSOCKSProxyTarget.IsEmpty() &&
+ (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) {
+ host = &mSOCKSProxyTarget;
+ if (mSOCKSProxyVersion == 4) {
+ type = kProxyType_SOCKS4;
+ } else {
+ type = kProxyType_SOCKS;
+ }
+ port = mSOCKSProxyPort;
+ if (mSOCKSProxyRemoteDNS) {
+ proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+ }
+
+ if (type) {
+ rv = NewProxyInfo_Internal(type, *host, port, ""_ns, ""_ns, ""_ns, ""_ns,
+ proxyFlags, UINT32_MAX, nullptr, flags, result);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+void nsProtocolProxyService::MaybeDisableDNSPrefetch(nsIProxyInfo* aProxy) {
+ // Disable Prefetch in the DNS service if a proxy is in use.
+ if (!aProxy) return;
+
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy);
+ if (!pi || !pi->mType || pi->mType == kProxyType_DIRECT) return;
+
+ // To avoid getting DNS service recursively, we directly use
+ // GetXPCOMSingleton().
+ nsCOMPtr<nsIDNSService> dns = nsDNSService::GetXPCOMSingleton();
+ if (!dns) return;
+ nsCOMPtr<nsPIDNSService> pdns = do_QueryInterface(dns);
+ if (!pdns) return;
+
+ // We lose the prefetch optimization for the life of the dns service.
+ pdns->SetPrefetchEnabled(false);
+}
+
+void nsProtocolProxyService::CopyFilters(nsTArray<RefPtr<FilterLink>>& aCopy) {
+ MOZ_ASSERT(aCopy.Length() == 0);
+ aCopy.AppendElements(mFilters);
+}
+
+bool nsProtocolProxyService::ApplyFilter(
+ FilterLink const* filterLink, nsIChannel* channel,
+ const nsProtocolInfo& info, nsCOMPtr<nsIProxyInfo> list,
+ nsIProxyProtocolFilterResult* callback) {
+ nsresult rv;
+
+ // We prune the proxy list prior to invoking each filter. This may be
+ // somewhat inefficient, but it seems like a good idea since we want each
+ // filter to "see" a valid proxy list.
+ PruneProxyInfo(info, list);
+
+ if (filterLink->filter) {
+ nsCOMPtr<nsIURI> uri;
+ Unused << GetProxyURI(channel, getter_AddRefs(uri));
+ if (!uri) {
+ return false;
+ }
+
+ rv = filterLink->filter->ApplyFilter(uri, list, callback);
+ return NS_SUCCEEDED(rv);
+ }
+
+ if (filterLink->channelFilter) {
+ rv = filterLink->channelFilter->ApplyFilter(channel, list, callback);
+ return NS_SUCCEEDED(rv);
+ }
+
+ return false;
+}
+
+void nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo& info,
+ nsIProxyInfo** list) {
+ if (!*list) return;
+
+ LOG(("nsProtocolProxyService::PruneProxyInfo ENTER list=%p", *list));
+
+ nsProxyInfo* head = nullptr;
+ CallQueryInterface(*list, &head);
+ if (!head) {
+ MOZ_ASSERT_UNREACHABLE("nsIProxyInfo must QI to nsProxyInfo");
+ return;
+ }
+ NS_RELEASE(*list);
+
+ // Pruning of disabled proxies works like this:
+ // - If all proxies are disabled, return the full list
+ // - Otherwise, remove the disabled proxies.
+ //
+ // Pruning of disallowed proxies works like this:
+ // - If the protocol handler disallows the proxy, then we disallow it.
+
+ // Start by removing all disallowed proxies if required:
+ if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY_HTTP)) {
+ nsProxyInfo *last = nullptr, *iter = head;
+ while (iter) {
+ if ((iter->Type() == kProxyType_HTTP) ||
+ (iter->Type() == kProxyType_HTTPS)) {
+ // reject!
+ if (last) {
+ last->mNext = iter->mNext;
+ } else {
+ head = iter->mNext;
+ }
+ nsProxyInfo* next = iter->mNext;
+ iter->mNext = nullptr;
+ iter->Release();
+ iter = next;
+ } else {
+ last = iter;
+ iter = iter->mNext;
+ }
+ }
+ if (!head) {
+ return;
+ }
+ }
+
+ // Scan to see if all remaining non-direct proxies are disabled. If so, then
+ // we'll just bail and return them all. Otherwise, we'll go and prune the
+ // disabled ones.
+
+ bool allNonDirectProxiesDisabled = true;
+
+ nsProxyInfo* iter;
+ for (iter = head; iter; iter = iter->mNext) {
+ if (!IsProxyDisabled(iter) && iter->mType != kProxyType_DIRECT) {
+ allNonDirectProxiesDisabled = false;
+ break;
+ }
+ }
+
+ if (allNonDirectProxiesDisabled &&
+ StaticPrefs::network_proxy_retry_failed_proxies()) {
+ LOG(("All proxies are disabled, so trying all again"));
+ } else {
+ // remove any disabled proxies.
+ nsProxyInfo* last = nullptr;
+ for (iter = head; iter;) {
+ if (IsProxyDisabled(iter)) {
+ // reject!
+ nsProxyInfo* reject = iter;
+
+ iter = iter->mNext;
+ if (last) {
+ last->mNext = iter;
+ } else {
+ head = iter;
+ }
+
+ reject->mNext = nullptr;
+ NS_RELEASE(reject);
+ continue;
+ }
+
+ // since we are about to use this proxy, make sure it is not on
+ // the disabled proxy list. we'll add it back to that list if
+ // we have to (in GetFailoverForProxy).
+ //
+ // XXX(darin): It might be better to do this as a final pass.
+ //
+ EnableProxy(iter);
+
+ last = iter;
+ iter = iter->mNext;
+ }
+ }
+
+ // if only DIRECT was specified then return no proxy info, and we're done.
+ if (head && !head->mNext && head->mType == kProxyType_DIRECT) {
+ NS_RELEASE(head);
+ }
+
+ *list = head; // Transfer ownership
+
+ LOG(("nsProtocolProxyService::PruneProxyInfo LEAVE list=%p", *list));
+}
+
+bool nsProtocolProxyService::GetIsPACLoading() {
+ return mPACMan && mPACMan->IsLoading();
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::AddProxyConfigCallback(
+ nsIProxyConfigChangedCallback* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!aCallback) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mProxyConfigChangedCallbacks.AppendElement(aCallback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::RemoveProxyConfigCallback(
+ nsIProxyConfigChangedCallback* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mProxyConfigChangedCallbacks.RemoveElement(aCallback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProtocolProxyService::NotifyProxyConfigChangedInternal() {
+ LOG(("nsProtocolProxyService::NotifyProxyConfigChangedInternal"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (const auto& callback : mProxyConfigChangedCallbacks) {
+ callback->OnProxyConfigChanged();
+ }
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsProtocolProxyService.h b/netwerk/base/nsProtocolProxyService.h
new file mode 100644
index 0000000000..394450ca6e
--- /dev/null
+++ b/netwerk/base/nsProtocolProxyService.h
@@ -0,0 +1,420 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsProtocolProxyService_h__
+#define nsProtocolProxyService_h__
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIProtocolProxyService2.h"
+#include "nsIProtocolProxyFilter.h"
+#include "nsIProxyInfo.h"
+#include "nsIObserver.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsITimer.h"
+#include "prio.h"
+#include "mozilla/Attributes.h"
+
+class nsIPrefBranch;
+class nsISystemProxySettings;
+
+namespace mozilla {
+namespace net {
+
+using nsFailedProxyTable = nsTHashMap<nsCStringHashKey, uint32_t>;
+
+class nsPACMan;
+class nsProxyInfo;
+struct nsProtocolInfo;
+
+// CID for the nsProtocolProxyService class
+// 091eedd8-8bae-4fe3-ad62-0c87351e640d
+#define NS_PROTOCOL_PROXY_SERVICE_IMPL_CID \
+ { \
+ 0x091eedd8, 0x8bae, 0x4fe3, { \
+ 0xad, 0x62, 0x0c, 0x87, 0x35, 0x1e, 0x64, 0x0d \
+ } \
+ }
+
+class nsProtocolProxyService final : public nsIProtocolProxyService2,
+ public nsIObserver,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLPROXYSERVICE2
+ NS_DECL_NSIPROTOCOLPROXYSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
+
+ nsProtocolProxyService();
+
+ nsresult Init();
+
+ public:
+ // An instance of this struct is allocated for each registered
+ // nsIProtocolProxyFilter and each nsIProtocolProxyChannelFilter.
+ class FilterLink {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(FilterLink)
+
+ uint32_t position;
+ nsCOMPtr<nsIProtocolProxyFilter> filter;
+ nsCOMPtr<nsIProtocolProxyChannelFilter> channelFilter;
+
+ FilterLink(uint32_t p, nsIProtocolProxyFilter* f);
+ FilterLink(uint32_t p, nsIProtocolProxyChannelFilter* cf);
+
+ private:
+ ~FilterLink();
+ };
+
+ protected:
+ friend class nsAsyncResolveRequest;
+ friend class TestProtocolProxyService_LoadHostFilters_Test; // for gtest
+
+ ~nsProtocolProxyService();
+
+ /**
+ * This method is called whenever a preference may have changed or
+ * to initialize all preferences.
+ *
+ * @param prefs
+ * This must be a pointer to the root pref branch.
+ * @param name
+ * This can be the name of a fully-qualified preference, or it can
+ * be null, in which case all preferences will be initialized.
+ */
+ void PrefsChanged(nsIPrefBranch* prefBranch, const char* pref);
+
+ /**
+ * This method is called to create a nsProxyInfo instance from the given
+ * PAC-style proxy string. It parses up to the end of the string, or to
+ * the next ';' character.
+ *
+ * @param proxy
+ * The PAC-style proxy string to parse. This must not be null.
+ * @param aResolveFlags
+ * The flags passed to Resolve or AsyncResolve that are stored in
+ * proxyInfo.
+ * @param result
+ * Upon return this points to a newly allocated nsProxyInfo or null
+ * if the proxy string was invalid.
+ *
+ * @return A pointer beyond the parsed proxy string (never null).
+ */
+ const char* ExtractProxyInfo(const char* start, uint32_t aResolveFlags,
+ nsProxyInfo** result);
+
+ /**
+ * Load the specified PAC file.
+ *
+ * @param pacURI
+ * The URI spec of the PAC file to load.
+ */
+ nsresult ConfigureFromPAC(const nsCString& spec, bool forceReload);
+
+ /**
+ * This method builds a list of nsProxyInfo objects from the given PAC-
+ * style string.
+ *
+ * @param pacString
+ * The PAC-style proxy string to parse. This may be empty.
+ * @param aResolveFlags
+ * The flags passed to Resolve or AsyncResolve that are stored in
+ * proxyInfo.
+ * @param result
+ * The resulting list of proxy info objects.
+ */
+ void ProcessPACString(const nsCString& pacString, uint32_t aResolveFlags,
+ nsIProxyInfo** result);
+
+ /**
+ * This method generates a string valued identifier for the given
+ * nsProxyInfo object.
+ *
+ * @param pi
+ * The nsProxyInfo object from which to generate the key.
+ * @param result
+ * Upon return, this parameter holds the generated key.
+ */
+ void GetProxyKey(nsProxyInfo* pi, nsCString& key);
+
+ /**
+ * @return Seconds since start of session.
+ */
+ uint32_t SecondsSinceSessionStart();
+
+ /**
+ * This method removes the specified proxy from the disabled list.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to enable.
+ */
+ void EnableProxy(nsProxyInfo* pi);
+
+ /**
+ * This method adds the specified proxy to the disabled list.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to disable.
+ */
+ void DisableProxy(nsProxyInfo* pi);
+
+ /**
+ * This method tests to see if the given proxy is disabled.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to test.
+ *
+ * @return True if the specified proxy is disabled.
+ */
+ bool IsProxyDisabled(nsProxyInfo* pi);
+
+ /**
+ * This method queries the protocol handler for the given scheme to check
+ * for the protocol flags and default port.
+ *
+ * @param uri
+ * The URI to query.
+ * @param info
+ * Holds information about the protocol upon return. Pass address
+ * of structure when you call this method. This parameter must not
+ * be null.
+ */
+ nsresult GetProtocolInfo(nsIURI* uri, nsProtocolInfo* info);
+
+ /**
+ * This method is an internal version nsIProtocolProxyService::newProxyInfo
+ * that expects a string literal for the type.
+ *
+ * @param type
+ * The proxy type.
+ * @param host
+ * The proxy host name (UTF-8 ok).
+ * @param port
+ * The proxy port number.
+ * @param username
+ * The username for the proxy (ASCII). May be "", but not null.
+ * @param password
+ * The password for the proxy (ASCII). May be "", but not null.
+ * @param flags
+ * The proxy flags (nsIProxyInfo::flags).
+ * @param timeout
+ * The failover timeout for this proxy.
+ * @param next
+ * The next proxy to try if this one fails.
+ * @param aResolveFlags
+ * The flags passed to resolve (from nsIProtocolProxyService).
+ * @param result
+ * The resulting nsIProxyInfo object.
+ */
+ nsresult NewProxyInfo_Internal(const char* type, const nsACString& host,
+ int32_t port, const nsACString& username,
+ const nsACString& password,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey,
+ uint32_t flags, uint32_t timeout,
+ nsIProxyInfo* aFailoverProxy,
+ uint32_t aResolveFlags, nsIProxyInfo** result);
+
+ /**
+ * This method is an internal version of Resolve that does not query PAC.
+ * It performs all of the built-in processing, and reports back to the
+ * caller with either the proxy info result or a flag to instruct the
+ * caller to use PAC instead.
+ *
+ * @param channel
+ * The channel to test.
+ * @param info
+ * Information about the URI's protocol.
+ * @param flags
+ * The flags passed to either the resolve or the asyncResolve method.
+ * @param usePAC
+ * If this flag is set upon return, then PAC should be queried to
+ * resolve the proxy info.
+ * @param result
+ * The resulting proxy info or null.
+ */
+ nsresult Resolve_Internal(nsIChannel* channel, const nsProtocolInfo& info,
+ uint32_t flags, bool* usePAC,
+ nsIProxyInfo** result);
+
+ /**
+ * Shallow copy of the current list of registered filters so that
+ * we can safely let them asynchronously process a single proxy
+ * resolution request.
+ */
+ void CopyFilters(nsTArray<RefPtr<FilterLink>>& aCopy);
+
+ /**
+ * This method applies the provided filter to the given proxy info
+ * list, and expects |callback| be called on (synchronously or
+ * asynchronously) to provide the updated proxyinfo list.
+ */
+ bool ApplyFilter(FilterLink const* filterLink, nsIChannel* channel,
+ const nsProtocolInfo& info, nsCOMPtr<nsIProxyInfo> list,
+ nsIProxyProtocolFilterResult* callback);
+
+ /**
+ * This method prunes out disabled and disallowed proxies from a given
+ * proxy info list.
+ *
+ * @param info
+ * Information about the URI's protocol.
+ * @param proxyInfo
+ * The proxy info list to be modified. This is an inout param.
+ */
+ void PruneProxyInfo(const nsProtocolInfo& info, nsIProxyInfo** list);
+
+ /**
+ * This method is a simple wrapper around PruneProxyInfo that takes the
+ * proxy info list inout param as a nsCOMPtr.
+ */
+ void PruneProxyInfo(const nsProtocolInfo& info,
+ nsCOMPtr<nsIProxyInfo>& proxyInfo) {
+ nsIProxyInfo* pi = nullptr;
+ proxyInfo.swap(pi);
+ PruneProxyInfo(info, &pi);
+ proxyInfo.swap(pi);
+ }
+
+ /**
+ * This method populates mHostFiltersArray from the given string.
+ *
+ * @param hostFilters
+ * A "no-proxy-for" exclusion list.
+ */
+ void LoadHostFilters(const nsACString& aFilters);
+
+ /**
+ * This method checks the given URI against mHostFiltersArray.
+ *
+ * @param uri
+ * The URI to test.
+ * @param defaultPort
+ * The default port for the given URI.
+ *
+ * @return True if the URI can use the specified proxy.
+ */
+ bool CanUseProxy(nsIURI* uri, int32_t defaultPort);
+
+ /**
+ * Disable Prefetch in the DNS service if a proxy is in use.
+ *
+ * @param aProxy
+ * The proxy information
+ */
+ void MaybeDisableDNSPrefetch(nsIProxyInfo* aProxy);
+
+ private:
+ nsresult SetupPACThread(
+ nsISerialEventTarget* mainThreadEventTarget = nullptr);
+ nsresult ResetPACThread();
+ nsresult ReloadNetworkPAC();
+
+ nsresult AsyncConfigureFromPAC(bool aForceReload, bool aResetPACThread);
+ nsresult OnAsyncGetPACURI(bool aForceReload, bool aResetPACThread,
+ nsresult aResult, const nsACString& aUri);
+
+ public:
+ // The Sun Forte compiler and others implement older versions of the
+ // C++ standard's rules on access and nested classes. These structs
+ // need to be public in order to deal with those compilers.
+
+ struct HostInfoIP {
+ uint16_t family;
+ uint16_t mask_len;
+ PRIPv6Addr addr; // possibly IPv4-mapped address
+ };
+
+ struct HostInfoName {
+ char* host;
+ uint32_t host_len;
+ };
+
+ protected:
+ // simplified array of filters defined by this struct
+ struct HostInfo {
+ bool is_ipaddr{false};
+ int32_t port{0};
+ // other members intentionally uninitialized
+ union {
+ HostInfoIP ip;
+ HostInfoName name;
+ };
+
+ HostInfo() = default;
+ ~HostInfo() {
+ if (!is_ipaddr && name.host) {
+ free(name.host);
+ }
+ }
+ };
+
+ private:
+ // Private methods to insert and remove FilterLinks from the FilterLink chain.
+ nsresult InsertFilterLink(RefPtr<FilterLink>&& link);
+ nsresult RemoveFilterLink(nsISupports* givenObject);
+
+ protected:
+ // Indicates if local hosts (plain hostnames, no dots) should use the proxy
+ bool mFilterLocalHosts{false};
+
+ // Holds an array of HostInfo objects
+ nsTArray<UniquePtr<HostInfo>> mHostFiltersArray;
+
+ // Filters, always sorted by the position.
+ nsTArray<RefPtr<FilterLink>> mFilters;
+
+ nsTArray<nsCOMPtr<nsIProxyConfigChangedCallback>>
+ mProxyConfigChangedCallbacks;
+
+ uint32_t mProxyConfig{PROXYCONFIG_DIRECT};
+
+ nsCString mHTTPProxyHost;
+ int32_t mHTTPProxyPort{-1};
+
+ nsCString mHTTPSProxyHost;
+ int32_t mHTTPSProxyPort{-1};
+
+ // mSOCKSProxyTarget could be a host, a domain socket path,
+ // or a named-pipe name.
+ nsCString mSOCKSProxyTarget;
+ int32_t mSOCKSProxyPort{-1};
+ int32_t mSOCKSProxyVersion{4};
+ bool mSOCKSProxyRemoteDNS{false};
+ bool mProxyOverTLS{true};
+ bool mWPADOverDHCPEnabled{false};
+
+ RefPtr<nsPACMan> mPACMan; // non-null if we are using PAC
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+
+ PRTime mSessionStart;
+ nsFailedProxyTable mFailedProxies;
+ // 30 minute default
+ int32_t mFailedProxyTimeout{30 * 60};
+
+ private:
+ nsresult AsyncResolveInternal(nsIChannel* channel, uint32_t flags,
+ nsIProtocolProxyCallback* callback,
+ nsICancelable** result, bool isSyncOK,
+ nsISerialEventTarget* mainThreadEventTarget);
+ bool mIsShutdown{false};
+ nsCOMPtr<nsITimer> mReloadPACTimer;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsProtocolProxyService,
+ NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsProtocolProxyService_h__
diff --git a/netwerk/base/nsProxyInfo.cpp b/netwerk/base/nsProxyInfo.cpp
new file mode 100644
index 0000000000..6859845739
--- /dev/null
+++ b/netwerk/base/nsProxyInfo.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsProxyInfo.h"
+
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+// Yes, we support QI to nsProxyInfo
+NS_IMPL_ISUPPORTS(nsProxyInfo, nsProxyInfo, nsIProxyInfo)
+
+// These pointers are declared in nsProtocolProxyService.cpp and
+// comparison of mType by string pointer is valid within necko
+extern const char kProxyType_HTTP[];
+extern const char kProxyType_HTTPS[];
+extern const char kProxyType_SOCKS[];
+extern const char kProxyType_SOCKS4[];
+extern const char kProxyType_SOCKS5[];
+extern const char kProxyType_DIRECT[];
+extern const char kProxyType_PROXY[];
+
+nsProxyInfo::nsProxyInfo(const nsACString& aType, const nsACString& aHost,
+ int32_t aPort, const nsACString& aUsername,
+ const nsACString& aPassword, uint32_t aFlags,
+ uint32_t aTimeout, uint32_t aResolveFlags,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey)
+ : mHost(aHost),
+ mUsername(aUsername),
+ mPassword(aPassword),
+ mProxyAuthorizationHeader(aProxyAuthorizationHeader),
+ mConnectionIsolationKey(aConnectionIsolationKey),
+ mPort(aPort),
+ mFlags(aFlags),
+ mResolveFlags(aResolveFlags),
+ mTimeout(aTimeout),
+ mNext(nullptr) {
+ if (aType.EqualsASCII(kProxyType_HTTP)) {
+ mType = kProxyType_HTTP;
+ } else if (aType.EqualsASCII(kProxyType_HTTPS)) {
+ mType = kProxyType_HTTPS;
+ } else if (aType.EqualsASCII(kProxyType_SOCKS)) {
+ mType = kProxyType_SOCKS;
+ } else if (aType.EqualsASCII(kProxyType_SOCKS4)) {
+ mType = kProxyType_SOCKS4;
+ } else if (aType.EqualsASCII(kProxyType_SOCKS5)) {
+ mType = kProxyType_SOCKS5;
+ } else if (aType.EqualsASCII(kProxyType_PROXY)) {
+ mType = kProxyType_PROXY;
+ } else {
+ mType = kProxyType_DIRECT;
+ }
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetHost(nsACString& result) {
+ result = mHost;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetPort(int32_t* result) {
+ *result = mPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetType(nsACString& result) {
+ result = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFlags(uint32_t* result) {
+ *result = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetResolveFlags(uint32_t* result) {
+ *result = mResolveFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetUsername(nsACString& result) {
+ result = mUsername;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetPassword(nsACString& result) {
+ result = mPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetProxyAuthorizationHeader(nsACString& result) {
+ result = mProxyAuthorizationHeader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetConnectionIsolationKey(nsACString& result) {
+ result = mConnectionIsolationKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFailoverTimeout(uint32_t* result) {
+ *result = mTimeout;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetFailoverProxy(nsIProxyInfo** result) {
+ NS_IF_ADDREF(*result = mNext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::SetFailoverProxy(nsIProxyInfo* proxy) {
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(proxy);
+ NS_ENSURE_ARG(pi);
+
+ pi.swap(mNext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetSourceId(nsACString& result) {
+ result = mSourceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::SetSourceId(const nsACString& sourceId) {
+ mSourceId = sourceId;
+ return NS_OK;
+}
+
+bool nsProxyInfo::IsDirect() {
+ if (!mType) return true;
+ return mType == kProxyType_DIRECT;
+}
+
+bool nsProxyInfo::IsHTTP() { return mType == kProxyType_HTTP; }
+
+bool nsProxyInfo::IsHTTPS() { return mType == kProxyType_HTTPS; }
+
+bool nsProxyInfo::IsSOCKS() {
+ return mType == kProxyType_SOCKS || mType == kProxyType_SOCKS4 ||
+ mType == kProxyType_SOCKS5;
+}
+
+/* static */
+void nsProxyInfo::SerializeProxyInfo(nsProxyInfo* aProxyInfo,
+ nsTArray<ProxyInfoCloneArgs>& aResult) {
+ for (nsProxyInfo* iter = aProxyInfo; iter; iter = iter->mNext) {
+ ProxyInfoCloneArgs* arg = aResult.AppendElement();
+ arg->type() = nsCString(iter->Type());
+ arg->host() = iter->Host();
+ arg->port() = iter->Port();
+ arg->username() = iter->Username();
+ arg->password() = iter->Password();
+ arg->proxyAuthorizationHeader() = iter->ProxyAuthorizationHeader();
+ arg->connectionIsolationKey() = iter->ConnectionIsolationKey();
+ arg->flags() = iter->Flags();
+ arg->timeout() = iter->Timeout();
+ arg->resolveFlags() = iter->ResolveFlags();
+ }
+}
+
+/* static */
+nsProxyInfo* nsProxyInfo::DeserializeProxyInfo(
+ const nsTArray<ProxyInfoCloneArgs>& aArgs) {
+ nsProxyInfo *pi = nullptr, *first = nullptr, *last = nullptr;
+ for (const ProxyInfoCloneArgs& info : aArgs) {
+ pi = new nsProxyInfo(info.type(), info.host(), info.port(), info.username(),
+ info.password(), info.flags(), info.timeout(),
+ info.resolveFlags(), info.proxyAuthorizationHeader(),
+ info.connectionIsolationKey());
+ if (last) {
+ last->mNext = pi;
+ // |mNext| will be released in |last|'s destructor.
+ NS_IF_ADDREF(last->mNext);
+ } else {
+ first = pi;
+ }
+ last = pi;
+ }
+
+ return first;
+}
+
+already_AddRefed<nsProxyInfo> nsProxyInfo::CloneProxyInfoWithNewResolveFlags(
+ uint32_t aResolveFlags) {
+ nsTArray<ProxyInfoCloneArgs> args;
+ SerializeProxyInfo(this, args);
+
+ for (auto& arg : args) {
+ arg.resolveFlags() = aResolveFlags;
+ }
+
+ RefPtr<nsProxyInfo> result = DeserializeProxyInfo(args);
+ return result.forget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsProxyInfo.h b/netwerk/base/nsProxyInfo.h
new file mode 100644
index 0000000000..ca8602edc0
--- /dev/null
+++ b/netwerk/base/nsProxyInfo.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsProxyInfo_h__
+#define nsProxyInfo_h__
+
+#include "nsIProxyInfo.h"
+#include "nsString.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+
+// Use to support QI nsIProxyInfo to nsProxyInfo
+#define NS_PROXYINFO_IID \
+ { /* ed42f751-825e-4cc2-abeb-3670711a8b85 */ \
+ 0xed42f751, 0x825e, 0x4cc2, { \
+ 0xab, 0xeb, 0x36, 0x70, 0x71, 0x1a, 0x8b, 0x85 \
+ } \
+ }
+
+namespace mozilla {
+namespace net {
+
+class ProxyInfoCloneArgs;
+
+// This class is exposed to other classes inside Necko for fast access
+// to the nsIProxyInfo attributes.
+class nsProxyInfo final : public nsIProxyInfo {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROXYINFO_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROXYINFO
+
+ // Cheap accessors for use within Necko
+ const nsCString& Host() const { return mHost; }
+ int32_t Port() const { return mPort; }
+ const char* Type() const { return mType; }
+ uint32_t Flags() const { return mFlags; }
+ const nsCString& Username() const { return mUsername; }
+ const nsCString& Password() const { return mPassword; }
+ uint32_t Timeout() { return mTimeout; }
+ uint32_t ResolveFlags() { return mResolveFlags; }
+ const nsCString& ProxyAuthorizationHeader() const {
+ return mProxyAuthorizationHeader;
+ }
+ const nsCString& ConnectionIsolationKey() const {
+ return mConnectionIsolationKey;
+ }
+
+ bool IsDirect();
+ bool IsHTTP();
+ bool IsHTTPS();
+ bool IsSOCKS();
+
+ static void SerializeProxyInfo(nsProxyInfo* aProxyInfo,
+ nsTArray<ProxyInfoCloneArgs>& aResult);
+ static nsProxyInfo* DeserializeProxyInfo(
+ const nsTArray<ProxyInfoCloneArgs>& aArgs);
+
+ already_AddRefed<nsProxyInfo> CloneProxyInfoWithNewResolveFlags(
+ uint32_t aResolveFlags);
+
+ private:
+ friend class nsProtocolProxyService;
+
+ explicit nsProxyInfo(const char* type = nullptr) : mType(type) {}
+
+ nsProxyInfo(const nsACString& aType, const nsACString& aHost, int32_t aPort,
+ const nsACString& aUsername, const nsACString& aPassword,
+ uint32_t aFlags, uint32_t aTimeout, uint32_t aResolveFlags,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey);
+
+ ~nsProxyInfo() { NS_IF_RELEASE(mNext); }
+
+ const char* mType; // pointer to statically allocated value
+ nsCString mHost;
+ nsCString mUsername;
+ nsCString mPassword;
+ nsCString mProxyAuthorizationHeader;
+ nsCString mConnectionIsolationKey;
+ nsCString mSourceId;
+ int32_t mPort{-1};
+ uint32_t mFlags{0};
+ // We need to read on multiple threads, but don't need to sync on anything
+ // else
+ Atomic<uint32_t, Relaxed> mResolveFlags{0};
+ uint32_t mTimeout{UINT32_MAX};
+ nsProxyInfo* mNext{nullptr};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsProxyInfo, NS_PROXYINFO_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsProxyInfo_h__
diff --git a/netwerk/base/nsReadLine.h b/netwerk/base/nsReadLine.h
new file mode 100644
index 0000000000..f1e4693f92
--- /dev/null
+++ b/netwerk/base/nsReadLine.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsReadLine_h__
+#define nsReadLine_h__
+
+#include "nsIInputStream.h"
+#include "mozilla/Likely.h"
+
+/**
+ * @file
+ * Functions to read complete lines from an input stream.
+ *
+ * To properly use the helper function in here (NS_ReadLine) the caller should
+ * create a nsLineBuffer<T> with new, and pass it to NS_ReadLine every time it
+ * wants a line out.
+ *
+ * When done, the object should be deleted.
+ */
+
+/**
+ * @internal
+ * Buffer size. This many bytes will be buffered. If a line is longer than this,
+ * the partial line will be appended to the out parameter of NS_ReadLine and the
+ * buffer will be emptied.
+ * Note: if you change this constant, please update the regression test in
+ * netwerk/test/unit/test_readline.js accordingly (bug 397850).
+ */
+#define kLineBufferSize 4096
+
+/**
+ * @internal
+ * Line buffer structure, buffers data from an input stream.
+ * The buffer is empty when |start| == |end|.
+ * Invariant: |start| <= |end|
+ */
+template <typename CharT>
+class nsLineBuffer {
+ public:
+ nsLineBuffer() : start(buf), end(buf) {}
+
+ CharT buf[kLineBufferSize + 1];
+ CharT* start;
+ CharT* end;
+};
+
+/**
+ * Read a line from an input stream. Lines are separated by '\r' (0x0D) or '\n'
+ * (0x0A), or "\r\n" or "\n\r".
+ *
+ * @param aStream
+ * The stream to read from
+ * @param aBuffer
+ * The line buffer to use. A single line buffer must not be used with
+ * different input streams.
+ * @param aLine [out]
+ * The string where the line will be stored.
+ * @param more [out]
+ * Whether more data is available in the buffer. If true, NS_ReadLine may
+ * be called again to read further lines. Otherwise, further calls to
+ * NS_ReadLine will return an error.
+ *
+ * @retval NS_OK
+ * Read successful
+ * @retval error
+ * Input stream returned an error upon read. See
+ * nsIInputStream::read.
+ */
+template <typename CharT, class StreamType, class StringType>
+nsresult NS_ReadLine(StreamType* aStream, nsLineBuffer<CharT>* aBuffer,
+ StringType& aLine, bool* more) {
+ CharT eolchar = 0; // the first eol char or 1 after \r\n or \n\r is found
+
+ aLine.Truncate();
+
+ while (true) { // will be returning out of this loop on eol or eof
+ if (aBuffer->start == aBuffer->end) { // buffer is empty. Read into it.
+ uint32_t bytesRead;
+ nsresult rv = aStream->Read(aBuffer->buf, kLineBufferSize, &bytesRead);
+ if (NS_FAILED(rv) || MOZ_UNLIKELY(bytesRead == 0)) {
+ *more = false;
+ return rv;
+ }
+ aBuffer->start = aBuffer->buf;
+ aBuffer->end = aBuffer->buf + bytesRead;
+ *(aBuffer->end) = '\0';
+ }
+
+ /*
+ * Walk the buffer looking for an end-of-line.
+ * There are 3 cases to consider:
+ * 1. the eol char is the last char in the buffer
+ * 2. the eol char + one more char at the end of the buffer
+ * 3. the eol char + two or more chars at the end of the buffer
+ * we need at least one char after the first eol char to determine if
+ * it's a \r\n or \n\r sequence (and skip over it), and we need one
+ * more char after the end-of-line to set |more| correctly.
+ */
+ CharT* current = aBuffer->start;
+ if (MOZ_LIKELY(eolchar == 0)) {
+ for (; current < aBuffer->end; ++current) {
+ if (*current == '\n' || *current == '\r') {
+ eolchar = *current;
+ *current++ = '\0';
+ aLine.Append(aBuffer->start);
+ break;
+ }
+ }
+ }
+ if (MOZ_LIKELY(eolchar != 0)) {
+ for (; current < aBuffer->end; ++current) {
+ if ((eolchar == '\r' && *current == '\n') ||
+ (eolchar == '\n' && *current == '\r')) {
+ eolchar = 1;
+ continue;
+ }
+ aBuffer->start = current;
+ *more = true;
+ return NS_OK;
+ }
+ }
+
+ if (eolchar == 0) aLine.Append(aBuffer->start);
+ aBuffer->start = aBuffer->end; // mark the buffer empty
+ }
+}
+
+#endif // nsReadLine_h__
diff --git a/netwerk/base/nsRedirectHistoryEntry.cpp b/netwerk/base/nsRedirectHistoryEntry.cpp
new file mode 100644
index 0000000000..da1a18cb95
--- /dev/null
+++ b/netwerk/base/nsRedirectHistoryEntry.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRedirectHistoryEntry.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsRedirectHistoryEntry, nsIRedirectHistoryEntry)
+
+nsRedirectHistoryEntry::nsRedirectHistoryEntry(nsIPrincipal* aPrincipal,
+ nsIURI* aReferrer,
+ const nsACString& aRemoteAddress)
+ : mPrincipal(aPrincipal),
+ mReferrer(aReferrer),
+ mRemoteAddress(aRemoteAddress) {}
+
+NS_IMETHODIMP
+nsRedirectHistoryEntry::GetRemoteAddress(nsACString& result) {
+ result = mRemoteAddress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRedirectHistoryEntry::GetReferrerURI(nsIURI** referrer) {
+ *referrer = do_AddRef(mReferrer).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRedirectHistoryEntry::GetPrincipal(nsIPrincipal** principal) {
+ *principal = do_AddRef(mPrincipal).take();
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsRedirectHistoryEntry.h b/netwerk/base/nsRedirectHistoryEntry.h
new file mode 100644
index 0000000000..bc916062a8
--- /dev/null
+++ b/netwerk/base/nsRedirectHistoryEntry.h
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRedirectHistoryEntry_h__
+#define nsRedirectHistoryEntry_h__
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIRedirectHistoryEntry.h"
+
+class nsIURI;
+class nsIPrincipal;
+
+namespace mozilla {
+namespace net {
+
+class nsRedirectHistoryEntry final : public nsIRedirectHistoryEntry {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREDIRECTHISTORYENTRY
+
+ nsRedirectHistoryEntry(nsIPrincipal* aPrincipal, nsIURI* aReferrer,
+ const nsACString& aRemoteAddress);
+
+ private:
+ ~nsRedirectHistoryEntry() = default;
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIURI> mReferrer;
+ nsCString mRemoteAddress;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsRedirectHistoryEntry_h__
diff --git a/netwerk/base/nsRequestObserverProxy.cpp b/netwerk/base/nsRequestObserverProxy.cpp
new file mode 100644
index 0000000000..fda235488a
--- /dev/null
+++ b/netwerk/base/nsRequestObserverProxy.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nscore.h"
+#include "nsRequestObserverProxy.h"
+#include "nsIRequest.h"
+#include "mozilla/Logging.h"
+#include "mozilla/IntegerPrintfMacros.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gRequestObserverProxyLog("nsRequestObserverProxy");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gRequestObserverProxyLog, LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// nsARequestObserverEvent internal class...
+//-----------------------------------------------------------------------------
+
+nsARequestObserverEvent::nsARequestObserverEvent(nsIRequest* request)
+ : Runnable("net::nsARequestObserverEvent"), mRequest(request) {
+ MOZ_ASSERT(mRequest, "null pointer");
+}
+
+//-----------------------------------------------------------------------------
+// nsOnStartRequestEvent internal class...
+//-----------------------------------------------------------------------------
+
+class nsOnStartRequestEvent : public nsARequestObserverEvent {
+ RefPtr<nsRequestObserverProxy> mProxy;
+
+ public:
+ nsOnStartRequestEvent(nsRequestObserverProxy* proxy, nsIRequest* request)
+ : nsARequestObserverEvent(request), mProxy(proxy) {
+ MOZ_ASSERT(mProxy, "null pointer");
+ }
+
+ NS_IMETHOD Run() override {
+ LOG(("nsOnStartRequestEvent::HandleEvent [req=%p]\n", mRequest.get()));
+
+ if (!mProxy->mObserver) {
+ MOZ_ASSERT_UNREACHABLE(
+ "already handled onStopRequest event "
+ "(observer is null)");
+ return NS_OK;
+ }
+
+ LOG(("handle startevent=%p\n", this));
+ nsresult rv = mProxy->mObserver->OnStartRequest(mRequest);
+ if (NS_FAILED(rv)) {
+ LOG(("OnStartRequest failed [rv=%" PRIx32 "] canceling request!\n",
+ static_cast<uint32_t>(rv)));
+ rv = mRequest->Cancel(rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed for request!");
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ virtual ~nsOnStartRequestEvent() = default;
+};
+
+//-----------------------------------------------------------------------------
+// nsOnStopRequestEvent internal class...
+//-----------------------------------------------------------------------------
+
+class nsOnStopRequestEvent : public nsARequestObserverEvent {
+ RefPtr<nsRequestObserverProxy> mProxy;
+
+ public:
+ nsOnStopRequestEvent(nsRequestObserverProxy* proxy, nsIRequest* request)
+ : nsARequestObserverEvent(request), mProxy(proxy) {
+ MOZ_ASSERT(mProxy, "null pointer");
+ }
+
+ NS_IMETHOD Run() override {
+ LOG(("nsOnStopRequestEvent::HandleEvent [req=%p]\n", mRequest.get()));
+
+ nsMainThreadPtrHandle<nsIRequestObserver> observer = mProxy->mObserver;
+ if (!observer) {
+ MOZ_ASSERT_UNREACHABLE(
+ "already handled onStopRequest event "
+ "(observer is null)");
+ return NS_OK;
+ }
+ // Do not allow any more events to be handled after OnStopRequest
+ mProxy->mObserver = nullptr;
+
+ nsresult status = NS_OK;
+ DebugOnly<nsresult> rv = mRequest->GetStatus(&status);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetStatus failed for request!");
+
+ LOG(("handle stopevent=%p\n", this));
+ (void)observer->OnStopRequest(mRequest, status);
+
+ return NS_OK;
+ }
+
+ private:
+ virtual ~nsOnStopRequestEvent() = default;
+};
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsISupports implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsRequestObserverProxy, nsIRequestObserver,
+ nsIRequestObserverProxy)
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsIRequestObserver implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsRequestObserverProxy::OnStartRequest(nsIRequest* request) {
+ LOG(("nsRequestObserverProxy::OnStartRequest [this=%p req=%p]\n", this,
+ request));
+
+ RefPtr<nsOnStartRequestEvent> ev = new nsOnStartRequestEvent(this, request);
+
+ LOG(("post startevent=%p\n", ev.get()));
+ return FireEvent(ev);
+}
+
+NS_IMETHODIMP
+nsRequestObserverProxy::OnStopRequest(nsIRequest* request, nsresult status) {
+ LOG(("nsRequestObserverProxy: OnStopRequest [this=%p req=%p status=%" PRIx32
+ "]\n",
+ this, request, static_cast<uint32_t>(status)));
+
+ // The status argument is ignored because, by the time the OnStopRequestEvent
+ // is actually processed, the status of the request may have changed :-(
+ // To make sure that an accurate status code is always used, GetStatus() is
+ // called when the OnStopRequestEvent is actually processed (see above).
+
+ RefPtr<nsOnStopRequestEvent> ev = new nsOnStopRequestEvent(this, request);
+
+ LOG(("post stopevent=%p\n", ev.get()));
+ return FireEvent(ev);
+}
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy::nsIRequestObserverProxy implementation...
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsRequestObserverProxy::Init(nsIRequestObserver* observer,
+ nsISupports* context) {
+ NS_ENSURE_ARG_POINTER(observer);
+ mObserver = new nsMainThreadPtrHolder<nsIRequestObserver>(
+ "nsRequestObserverProxy::mObserver", observer);
+ mContext = new nsMainThreadPtrHolder<nsISupports>(
+ "nsRequestObserverProxy::mContext", context);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsRequestObserverProxy implementation...
+//-----------------------------------------------------------------------------
+
+nsresult nsRequestObserverProxy::FireEvent(nsARequestObserverEvent* event) {
+ nsCOMPtr<nsIEventTarget> mainThread(GetMainThreadSerialEventTarget());
+ return mainThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsRequestObserverProxy.h b/netwerk/base/nsRequestObserverProxy.h
new file mode 100644
index 0000000000..e1ccec0c4c
--- /dev/null
+++ b/netwerk/base/nsRequestObserverProxy.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRequestObserverProxy_h__
+#define nsRequestObserverProxy_h__
+
+#include "nsIRequestObserver.h"
+#include "nsIRequestObserverProxy.h"
+#include "nsIRequest.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+class nsARequestObserverEvent;
+
+class nsRequestObserverProxy final : public nsIRequestObserverProxy {
+ ~nsRequestObserverProxy() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIREQUESTOBSERVERPROXY
+
+ nsRequestObserverProxy() = default;
+
+ nsIRequestObserver* Observer() { return mObserver; }
+
+ nsresult FireEvent(nsARequestObserverEvent*);
+
+ protected:
+ nsMainThreadPtrHandle<nsIRequestObserver> mObserver;
+ nsMainThreadPtrHandle<nsISupports> mContext;
+
+ friend class nsOnStartRequestEvent;
+ friend class nsOnStopRequestEvent;
+};
+
+class nsARequestObserverEvent : public Runnable {
+ public:
+ explicit nsARequestObserverEvent(nsIRequest*);
+
+ protected:
+ virtual ~nsARequestObserverEvent() = default;
+
+ nsCOMPtr<nsIRequest> mRequest;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsRequestObserverProxy_h__
diff --git a/netwerk/base/nsSerializationHelper.cpp b/netwerk/base/nsSerializationHelper.cpp
new file mode 100644
index 0000000000..6e2efb7c57
--- /dev/null
+++ b/netwerk/base/nsSerializationHelper.cpp
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSerializationHelper.h"
+
+#include "mozilla/Base64.h"
+#include "nsISerializable.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIObjectInputStream.h"
+#include "nsString.h"
+#include "nsBase64Encoder.h"
+#include "nsComponentManagerUtils.h"
+#include "nsStringStream.h"
+
+using namespace mozilla;
+
+nsresult NS_SerializeToString(nsISerializable* obj, nsACString& str) {
+ RefPtr<nsBase64Encoder> stream(new nsBase64Encoder());
+ if (!stream) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIObjectOutputStream> objstream = NS_NewObjectOutputStream(stream);
+ nsresult rv =
+ objstream->WriteCompoundObject(obj, NS_GET_IID(nsISupports), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return stream->Finish(str);
+}
+
+nsresult NS_DeserializeObject(const nsACString& str, nsISupports** obj) {
+ nsCString decodedData;
+ nsresult rv = Base64Decode(str, decodedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(stream), std::move(decodedData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObjectInputStream> objstream = NS_NewObjectInputStream(stream);
+ return objstream->ReadObject(true, obj);
+}
+
+NS_IMPL_ISUPPORTS(nsSerializationHelper, nsISerializationHelper)
+
+NS_IMETHODIMP
+nsSerializationHelper::SerializeToString(nsISerializable* serializable,
+ nsACString& _retval) {
+ return NS_SerializeToString(serializable, _retval);
+}
+
+NS_IMETHODIMP
+nsSerializationHelper::DeserializeObject(const nsACString& input,
+ nsISupports** _retval) {
+ return NS_DeserializeObject(input, _retval);
+}
diff --git a/netwerk/base/nsSerializationHelper.h b/netwerk/base/nsSerializationHelper.h
new file mode 100644
index 0000000000..4e21a4c2df
--- /dev/null
+++ b/netwerk/base/nsSerializationHelper.h
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/** @file
+ * Helper functions for (de)serializing objects to/from ASCII strings.
+ */
+
+#ifndef NSSERIALIZATIONHELPER_H_
+#define NSSERIALIZATIONHELPER_H_
+
+#include "nsStringFwd.h"
+#include "nsISerializationHelper.h"
+#include "mozilla/Attributes.h"
+
+class nsISerializable;
+
+/**
+ * Serialize an object to an ASCII string.
+ */
+nsresult NS_SerializeToString(nsISerializable* obj, nsACString& str);
+
+/**
+ * Deserialize an object.
+ */
+nsresult NS_DeserializeObject(const nsACString& str, nsISupports** obj);
+
+class nsSerializationHelper final : public nsISerializationHelper {
+ ~nsSerializationHelper() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISERIALIZATIONHELPER
+};
+
+#endif
diff --git a/netwerk/base/nsServerSocket.cpp b/netwerk/base/nsServerSocket.cpp
new file mode 100644
index 0000000000..cf8fc7b619
--- /dev/null
+++ b/netwerk/base/nsServerSocket.cpp
@@ -0,0 +1,582 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransport2.h"
+#include "nsServerSocket.h"
+#include "nsProxyRelease.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "prnetdb.h"
+#include "prio.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Unused.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+#if defined(XP_WIN)
+# include "private/pprio.h"
+# include <winsock2.h>
+# include <mstcpip.h>
+
+# ifndef IPV6_V6ONLY
+# define IPV6_V6ONLY 27
+# endif
+
+#endif
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+using nsServerSocketFunc = void (nsServerSocket::*)();
+
+static nsresult PostEvent(nsServerSocket* s, nsServerSocketFunc func) {
+ nsCOMPtr<nsIRunnable> ev = NewRunnableMethod("net::PostEvent", s, func);
+ if (!gSocketTransportService) return NS_ERROR_FAILURE;
+
+ return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL);
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket
+//-----------------------------------------------------------------------------
+
+nsServerSocket::nsServerSocket() {
+ // we want to be able to access the STS directly, and it may not have been
+ // constructed yet. the STS constructor sets gSocketTransportService.
+ if (!gSocketTransportService) {
+ // This call can fail if we're offline, for example.
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ }
+ // make sure the STS sticks around as long as we do
+ NS_IF_ADDREF(gSocketTransportService);
+}
+
+nsServerSocket::~nsServerSocket() {
+ Close(); // just in case :)
+
+ // release our reference to the STS
+ nsSocketTransportService* serv = gSocketTransportService;
+ NS_IF_RELEASE(serv);
+}
+
+void nsServerSocket::OnMsgClose() {
+ SOCKET_LOG(("nsServerSocket::OnMsgClose [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition)) return;
+
+ // tear down socket. this signals the STS to detach our socket handler.
+ mCondition = NS_BINDING_ABORTED;
+
+ // if we are attached, then we'll close the socket in our OnSocketDetached.
+ // otherwise, call OnSocketDetached from here.
+ if (!mAttached) OnSocketDetached(mFD);
+}
+
+void nsServerSocket::OnMsgAttach() {
+ SOCKET_LOG(("nsServerSocket::OnMsgAttach [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition)) return;
+
+ mCondition = TryAttach();
+
+ // if we hit an error while trying to attach then bail...
+ if (NS_FAILED(mCondition)) {
+ NS_ASSERTION(!mAttached, "should not be attached already");
+ OnSocketDetached(mFD);
+ }
+}
+
+nsresult nsServerSocket::TryAttach() {
+ nsresult rv;
+
+ if (!gSocketTransportService) return NS_ERROR_FAILURE;
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!gSocketTransportService->CanAttachSocket()) {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "net::nsServerSocket::OnMsgAttach", this, &nsServerSocket::OnMsgAttach);
+ if (!event) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ //
+ // ok, we can now attach our socket to the STS for polling
+ //
+ rv = gSocketTransportService->AttachSocket(mFD, this);
+ if (NS_FAILED(rv)) return rv;
+
+ mAttached = true;
+
+ //
+ // now, configure our poll flags for listening...
+ //
+ mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
+ return NS_OK;
+}
+
+void nsServerSocket::CreateClientTransport(PRFileDesc* aClientFD,
+ const NetAddr& aClientAddr) {
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport;
+ if (NS_WARN_IF(!trans)) {
+ mCondition = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ nsresult rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mCondition = rv;
+ return;
+ }
+
+ mListener->OnSocketAccepted(this, trans);
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsASocketHandler
+//-----------------------------------------------------------------------------
+
+void nsServerSocket::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
+ NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops");
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached");
+
+ if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL)) {
+ NS_WARNING("error polling on listening socket");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ PRFileDesc* clientFD;
+ PRNetAddr prClientAddr;
+
+ // NSPR doesn't tell us the peer address's length (as provided by the
+ // 'accept' system call), so we can't distinguish between named,
+ // unnamed, and abstract peer addresses. Clear prClientAddr first, so
+ // that the path will at least be reliably empty for unnamed and
+ // abstract addresses, and not garbage when the peer is unnamed.
+ memset(&prClientAddr, 0, sizeof(prClientAddr));
+
+ clientFD = PR_Accept(mFD, &prClientAddr, PR_INTERVAL_NO_WAIT);
+ if (!clientFD) {
+ NS_WARNING("PR_Accept failed");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+ PR_SetFDInheritable(clientFD, false);
+
+ NetAddr clientAddr(&prClientAddr);
+ // Accept succeeded, create socket transport and notify consumer
+ CreateClientTransport(clientFD, clientAddr);
+}
+
+void nsServerSocket::OnSocketDetached(PRFileDesc* fd) {
+ // force a failure condition if none set; maybe the STS is shutting down :-/
+ if (NS_SUCCEEDED(mCondition)) mCondition = NS_ERROR_ABORT;
+
+ if (mFD) {
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+
+ if (mListener) {
+ mListener->OnStopListening(this, mCondition);
+
+ // need to atomically clear mListener. see our Close() method.
+ RefPtr<nsIServerSocketListener> listener = nullptr;
+ {
+ MutexAutoLock lock(mLock);
+ listener = ToRefPtr(std::move(mListener));
+ }
+
+ // XXX we need to proxy the release to the listener's target thread to work
+ // around bug 337492.
+ if (listener) {
+ NS_ProxyRelease("nsServerSocket::mListener", mListenerTarget,
+ listener.forget());
+ }
+ }
+}
+
+void nsServerSocket::IsLocal(bool* aIsLocal) {
+#if defined(XP_UNIX)
+ // Unix-domain sockets are always local.
+ if (mAddr.raw.family == PR_AF_LOCAL) {
+ *aIsLocal = true;
+ return;
+ }
+#endif
+
+ // If bound to loopback, this server socket only accepts local connections.
+ *aIsLocal = PR_IsNetAddrType(&mAddr, PR_IpAddrLoopback);
+}
+
+void nsServerSocket::KeepWhenOffline(bool* aKeepWhenOffline) {
+ *aKeepWhenOffline = mKeepWhenOffline;
+}
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsServerSocket, nsIServerSocket)
+
+//-----------------------------------------------------------------------------
+// nsServerSocket::nsIServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsServerSocket::Init(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog) {
+ return InitSpecialConnection(aPort, aLoopbackOnly ? LoopbackOnly : 0,
+ aBackLog);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitIPv6(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog) {
+ PRNetAddrValue val;
+ PRNetAddr addr;
+
+ if (aPort < 0) {
+ aPort = 0;
+ }
+ if (aLoopbackOnly) {
+ val = PR_IpAddrLoopback;
+ } else {
+ val = PR_IpAddrAny;
+ }
+ PR_SetNetAddr(val, PR_AF_INET6, aPort, &addr);
+
+ mKeepWhenOffline = false;
+ return InitWithAddress(&addr, aBackLog);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitDualStack(int32_t aPort, int32_t aBackLog) {
+ if (aPort < 0) {
+ aPort = 0;
+ }
+ PRNetAddr addr;
+ PR_SetNetAddr(PR_IpAddrAny, PR_AF_INET6, aPort, &addr);
+ return InitWithAddressInternal(&addr, aBackLog, true);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitWithFilename(nsIFile* aPath, uint32_t aPermissions,
+ int32_t aBacklog) {
+#if defined(XP_UNIX)
+ nsresult rv;
+
+ nsAutoCString path;
+ rv = aPath->GetNativePath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // Create a Unix domain PRNetAddr referring to the given path.
+ PRNetAddr addr;
+ if (path.Length() > sizeof(addr.local.path) - 1) {
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ }
+ addr.local.family = PR_AF_LOCAL;
+ memcpy(addr.local.path, path.get(), path.Length());
+ addr.local.path[path.Length()] = '\0';
+
+ rv = InitWithAddress(&addr, aBacklog);
+ if (NS_FAILED(rv)) return rv;
+
+ return aPath->SetPermissions(aPermissions);
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitWithAbstractAddress(const nsACString& aName,
+ int32_t aBacklog) {
+ // Abstract socket address is supported on Linux and Android only.
+ // If not Linux, we should return error.
+#if defined(XP_LINUX)
+ // Create an abstract socket address PRNetAddr referring to the name
+ PRNetAddr addr;
+ if (aName.Length() > sizeof(addr.local.path) - 2) {
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ }
+ addr.local.family = PR_AF_LOCAL;
+ addr.local.path[0] = 0;
+ memcpy(addr.local.path + 1, aName.BeginReading(), aName.Length());
+ addr.local.path[aName.Length() + 1] = 0;
+
+ return InitWithAddress(&addr, aBacklog);
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitSpecialConnection(int32_t aPort, nsServerSocketFlag aFlags,
+ int32_t aBackLog) {
+ PRNetAddrValue val;
+ PRNetAddr addr;
+
+ if (aPort < 0) aPort = 0;
+ if (aFlags & nsIServerSocket::LoopbackOnly) {
+ val = PR_IpAddrLoopback;
+ } else {
+ val = PR_IpAddrAny;
+ }
+ PR_SetNetAddr(val, PR_AF_INET, aPort, &addr);
+
+ mKeepWhenOffline = ((aFlags & nsIServerSocket::KeepWhenOffline) != 0);
+ return InitWithAddress(&addr, aBackLog);
+}
+
+NS_IMETHODIMP
+nsServerSocket::InitWithAddress(const PRNetAddr* aAddr, int32_t aBackLog) {
+ return InitWithAddressInternal(aAddr, aBackLog);
+}
+
+nsresult nsServerSocket::InitWithAddressInternal(const PRNetAddr* aAddr,
+ int32_t aBackLog,
+ bool aDualStack) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ nsresult rv;
+
+ //
+ // configure listening socket...
+ //
+
+ mFD = PR_OpenTCPSocket(aAddr->raw.family);
+ if (!mFD) {
+ NS_WARNING("unable to create server socket");
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+
+#if defined(XP_WIN)
+ // https://docs.microsoft.com/en-us/windows/win32/winsock/dual-stack-sockets
+ // To create a Dual-Stack Socket, we have to disable IPV6_V6ONLY.
+ if (aDualStack) {
+ PROsfd osfd = PR_FileDesc2NativeHandle(mFD);
+ if (osfd != -1) {
+ int disable = 0;
+ setsockopt(osfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&disable,
+ sizeof(disable));
+ }
+ }
+#else
+ mozilla::Unused << aDualStack;
+#endif
+
+ PR_SetFDInheritable(mFD, false);
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_Reuseaddr;
+ opt.value.reuse_addr = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ if (PR_Bind(mFD, aAddr) != PR_SUCCESS) {
+ NS_WARNING("failed to bind socket");
+ goto fail;
+ }
+
+ if (aBackLog < 0) aBackLog = 5; // seems like a reasonable default
+
+ if (PR_Listen(mFD, aBackLog) != PR_SUCCESS) {
+ NS_WARNING("cannot listen on socket");
+ goto fail;
+ }
+
+ // get the resulting socket address, which may be different than what
+ // we passed to bind.
+ if (PR_GetSockName(mFD, &mAddr) != PR_SUCCESS) {
+ NS_WARNING("cannot get socket name");
+ goto fail;
+ }
+
+ // Set any additional socket defaults needed by child classes
+ rv = SetSocketDefaults();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ goto fail;
+ }
+
+ // wait until AsyncListen is called before polling the socket for
+ // client connections.
+ return NS_OK;
+
+fail:
+ rv = ErrorAccordingToNSPR(PR_GetError());
+ Close();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsServerSocket::Close() {
+ {
+ MutexAutoLock lock(mLock);
+ // we want to proxy the close operation to the socket thread if a listener
+ // has been set. otherwise, we should just close the socket here...
+ if (!mListener) {
+ if (mFD) {
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+ return NS_OK;
+ }
+ }
+ return PostEvent(this, &nsServerSocket::OnMsgClose);
+}
+
+namespace {
+
+class ServerSocketListenerProxy final : public nsIServerSocketListener {
+ ~ServerSocketListenerProxy() = default;
+
+ public:
+ explicit ServerSocketListenerProxy(nsIServerSocketListener* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsIServerSocketListener>(
+ "ServerSocketListenerProxy::mListener", aListener)),
+ mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKETLISTENER
+
+ class OnSocketAcceptedRunnable : public Runnable {
+ public:
+ OnSocketAcceptedRunnable(
+ const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener,
+ nsIServerSocket* aServ, nsISocketTransport* aTransport)
+ : Runnable("net::ServerSocketListenerProxy::OnSocketAcceptedRunnable"),
+ mListener(aListener),
+ mServ(aServ),
+ mTransport(aTransport) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIServerSocket> mServ;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ };
+
+ class OnStopListeningRunnable : public Runnable {
+ public:
+ OnStopListeningRunnable(
+ const nsMainThreadPtrHandle<nsIServerSocketListener>& aListener,
+ nsIServerSocket* aServ, nsresult aStatus)
+ : Runnable("net::ServerSocketListenerProxy::OnStopListeningRunnable"),
+ mListener(aListener),
+ mServ(aServ),
+ mStatus(aStatus) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIServerSocket> mServ;
+ nsresult mStatus;
+ };
+
+ private:
+ nsMainThreadPtrHandle<nsIServerSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS(ServerSocketListenerProxy, nsIServerSocketListener)
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnSocketAccepted(nsIServerSocket* aServ,
+ nsISocketTransport* aTransport) {
+ RefPtr<OnSocketAcceptedRunnable> r =
+ new OnSocketAcceptedRunnable(mListener, aServ, aTransport);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnStopListening(nsIServerSocket* aServ,
+ nsresult aStatus) {
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aServ, aStatus);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnSocketAcceptedRunnable::Run() {
+ mListener->OnSocketAccepted(mServ, mTransport);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ServerSocketListenerProxy::OnStopListeningRunnable::Run() {
+ mListener->OnStopListening(mServ, mStatus);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+nsServerSocket::AsyncListen(nsIServerSocketListener* aListener) {
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ {
+ MutexAutoLock lock(mLock);
+ mListener = new ServerSocketListenerProxy(aListener);
+ mListenerTarget = GetCurrentSerialEventTarget();
+ }
+
+ // Child classes may need to do additional setup just before listening begins
+ nsresult rv = OnSocketListen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return PostEvent(this, &nsServerSocket::OnMsgAttach);
+}
+
+NS_IMETHODIMP
+nsServerSocket::GetPort(int32_t* aResult) {
+ // no need to enter the lock here
+ uint16_t port;
+ if (mAddr.raw.family == PR_AF_INET) {
+ port = mAddr.inet.port;
+ } else if (mAddr.raw.family == PR_AF_INET6) {
+ port = mAddr.ipv6.port;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = static_cast<int32_t>(NetworkEndian::readUint16(&port));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsServerSocket::GetAddress(PRNetAddr* aResult) {
+ // no need to enter the lock here
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsServerSocket.h b/netwerk/base/nsServerSocket.h
new file mode 100644
index 0000000000..76908697fe
--- /dev/null
+++ b/netwerk/base/nsServerSocket.h
@@ -0,0 +1,70 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsServerSocket_h__
+#define nsServerSocket_h__
+
+#include "prio.h"
+#include "nsASocketHandler.h"
+#include "nsCOMPtr.h"
+#include "nsIServerSocket.h"
+#include "mozilla/Mutex.h"
+
+//-----------------------------------------------------------------------------
+
+class nsIEventTarget;
+namespace mozilla {
+namespace net {
+union NetAddr;
+
+class nsServerSocket : public nsASocketHandler, public nsIServerSocket {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKET
+
+ // nsASocketHandler methods:
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override;
+ virtual void OnSocketDetached(PRFileDesc* fd) override;
+ virtual void IsLocal(bool* aIsLocal) override;
+ virtual void KeepWhenOffline(bool* aKeepWhenOffline) override;
+
+ virtual uint64_t ByteCountSent() override { return 0; }
+ virtual uint64_t ByteCountReceived() override { return 0; }
+ nsServerSocket();
+
+ virtual void CreateClientTransport(PRFileDesc* clientFD,
+ const mozilla::net::NetAddr& clientAddr);
+ virtual nsresult SetSocketDefaults() { return NS_OK; }
+ virtual nsresult OnSocketListen() { return NS_OK; }
+
+ protected:
+ virtual ~nsServerSocket();
+ PRFileDesc* mFD{nullptr};
+ nsCOMPtr<nsIServerSocketListener> mListener;
+
+ private:
+ void OnMsgClose();
+ void OnMsgAttach();
+
+ // try attaching our socket (mFD) to the STS's poll list.
+ nsresult TryAttach();
+
+ nsresult InitWithAddressInternal(const PRNetAddr* aAddr, int32_t aBackLog,
+ bool aDualStack = false);
+
+ // lock protects access to mListener; so it is not cleared while being used.
+ mozilla::Mutex mLock MOZ_UNANNOTATED{"nsServerSocket.mLock"};
+ PRNetAddr mAddr = {.raw = {0, {0}}};
+ nsCOMPtr<nsIEventTarget> mListenerTarget;
+ bool mAttached{false};
+ bool mKeepWhenOffline{false};
+};
+
+} // namespace net
+} // namespace mozilla
+
+//-----------------------------------------------------------------------------
+
+#endif // nsServerSocket_h__
diff --git a/netwerk/base/nsSimpleNestedURI.cpp b/netwerk/base/nsSimpleNestedURI.cpp
new file mode 100644
index 0000000000..54f57b4625
--- /dev/null
+++ b/netwerk/base/nsSimpleNestedURI.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIClassInfoImpl.h"
+#include "nsSimpleNestedURI.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+
+#include "mozilla/ipc/URIUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_CLASSINFO(nsSimpleNestedURI, nullptr, nsIClassInfo::THREADSAFE,
+ NS_SIMPLENESTEDURI_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(nsSimpleNestedURI)
+
+NS_IMPL_ADDREF_INHERITED(nsSimpleNestedURI, nsSimpleURI)
+NS_IMPL_RELEASE_INHERITED(nsSimpleNestedURI, nsSimpleURI)
+NS_IMPL_QUERY_INTERFACE_CI_INHERITED(nsSimpleNestedURI, nsSimpleURI,
+ nsINestedURI)
+
+nsSimpleNestedURI::nsSimpleNestedURI(nsIURI* innerURI) : mInnerURI(innerURI) {
+ NS_ASSERTION(innerURI, "Must have inner URI");
+}
+
+nsresult nsSimpleNestedURI::SetPathQueryRef(const nsACString& aPathQueryRef) {
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIURI> inner;
+ nsresult rv =
+ NS_MutateURI(mInnerURI).SetPathQueryRef(aPathQueryRef).Finalize(inner);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsSimpleURI::SetPathQueryRef(aPathQueryRef);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // If the regular SetPathQueryRef worked, also set it on the inner URI
+ mInnerURI = inner;
+ return NS_OK;
+}
+
+nsresult nsSimpleNestedURI::SetQuery(const nsACString& aQuery) {
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIURI> inner;
+ nsresult rv = NS_MutateURI(mInnerURI).SetQuery(aQuery).Finalize(inner);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsSimpleURI::SetQuery(aQuery);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // If the regular SetQuery worked, also set it on the inner URI
+ mInnerURI = inner;
+ return NS_OK;
+}
+
+nsresult nsSimpleNestedURI::SetRef(const nsACString& aRef) {
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIURI> inner;
+ nsresult rv = NS_MutateURI(mInnerURI).SetRef(aRef).Finalize(inner);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsSimpleURI::SetRef(aRef);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // If the regular SetRef worked, also set it on the inner URI
+ mInnerURI = inner;
+ return NS_OK;
+}
+
+// nsISerializable
+
+NS_IMETHODIMP
+nsSimpleNestedURI::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsSimpleNestedURI::ReadPrivate(nsIObjectInputStream* aStream) {
+ nsresult rv = nsSimpleURI::ReadPrivate(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISupports> supports;
+ rv = aStream->ReadObject(true, getter_AddRefs(supports));
+ if (NS_FAILED(rv)) return rv;
+
+ mInnerURI = do_QueryInterface(supports, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSimpleNestedURI::Write(nsIObjectOutputStream* aStream) {
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(mInnerURI);
+ if (!serializable) {
+ // We can't serialize ourselves
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = nsSimpleURI::Write(aStream);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteCompoundObject(mInnerURI, NS_GET_IID(nsIURI), true);
+ return rv;
+}
+
+NS_IMETHODIMP_(void)
+nsSimpleNestedURI::Serialize(mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ SimpleNestedURIParams params;
+ URIParams simpleParams;
+
+ nsSimpleURI::Serialize(simpleParams);
+ params.simpleParams() = simpleParams;
+
+ SerializeURI(mInnerURI, params.innerURI());
+
+ aParams = params;
+}
+
+bool nsSimpleNestedURI::Deserialize(const mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ if (aParams.type() != URIParams::TSimpleNestedURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const SimpleNestedURIParams& params = aParams.get_SimpleNestedURIParams();
+ if (!nsSimpleURI::Deserialize(params.simpleParams())) return false;
+
+ mInnerURI = DeserializeURI(params.innerURI());
+ return true;
+}
+
+// nsINestedURI
+
+NS_IMETHODIMP
+nsSimpleNestedURI::GetInnerURI(nsIURI** aURI) {
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIURI> uri = mInnerURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleNestedURI::GetInnermostURI(nsIURI** uri) {
+ return NS_ImplGetInnermostURI(this, uri);
+}
+
+// nsSimpleURI overrides
+/* virtual */
+nsresult nsSimpleNestedURI::EqualsInternal(
+ nsIURI* other, nsSimpleURI::RefHandlingEnum refHandlingMode, bool* result) {
+ *result = false;
+ NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
+
+ if (other) {
+ bool correctScheme;
+ nsresult rv = other->SchemeIs(mScheme.get(), &correctScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (correctScheme) {
+ nsCOMPtr<nsINestedURI> nest = do_QueryInterface(other);
+ if (nest) {
+ nsCOMPtr<nsIURI> otherInner;
+ rv = nest->GetInnerURI(getter_AddRefs(otherInner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return (refHandlingMode == eHonorRef)
+ ? otherInner->Equals(mInnerURI, result)
+ : otherInner->EqualsExceptRef(mInnerURI, result);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/* virtual */
+nsSimpleURI* nsSimpleNestedURI::StartClone(
+ nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef) {
+ NS_ENSURE_TRUE(mInnerURI, nullptr);
+
+ nsCOMPtr<nsIURI> innerClone;
+ nsresult rv = NS_OK;
+ if (refHandlingMode == eHonorRef) {
+ innerClone = mInnerURI;
+ } else if (refHandlingMode == eReplaceRef) {
+ rv = NS_GetURIWithNewRef(mInnerURI, newRef, getter_AddRefs(innerClone));
+ } else {
+ rv = NS_GetURIWithoutRef(mInnerURI, getter_AddRefs(innerClone));
+ }
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsSimpleNestedURI* url = new nsSimpleNestedURI(innerClone);
+ SetRefOnClone(url, refHandlingMode, newRef);
+
+ return url;
+}
+
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsSimpleNestedURI::Mutator, nsIURISetters,
+ nsIURIMutator, nsISerializable,
+ nsINestedURIMutator)
+
+NS_IMETHODIMP
+nsSimpleNestedURI::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsSimpleNestedURI::Mutator> mutator = new nsSimpleNestedURI::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleNestedURI.h b/netwerk/base/nsSimpleNestedURI.h
new file mode 100644
index 0000000000..0887431421
--- /dev/null
+++ b/netwerk/base/nsSimpleNestedURI.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * URI class to be used for cases when a simple URI actually resolves to some
+ * other sort of URI, with the latter being what's loaded when the load
+ * happens.
+ */
+
+#ifndef nsSimpleNestedURI_h__
+#define nsSimpleNestedURI_h__
+
+#include "nsCOMPtr.h"
+#include "nsSimpleURI.h"
+#include "nsINestedURI.h"
+#include "nsIURIMutator.h"
+
+namespace mozilla {
+namespace net {
+
+class nsSimpleNestedURI : public nsSimpleURI, public nsINestedURI {
+ protected:
+ nsSimpleNestedURI() = default;
+ explicit nsSimpleNestedURI(nsIURI* innerURI);
+
+ ~nsSimpleNestedURI() = default;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINESTEDURI
+
+ // Overrides for various methods nsSimpleURI implements follow.
+
+ // nsSimpleURI overrides
+ virtual nsresult EqualsInternal(nsIURI* other,
+ RefHandlingEnum refHandlingMode,
+ bool* result) override;
+ virtual nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef) override;
+ NS_IMETHOD Mutate(nsIURIMutator** _retval) override;
+ NS_IMETHOD_(void) Serialize(ipc::URIParams& aParams) override;
+
+ // nsISerializable overrides
+ NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+ NS_IMETHOD Write(nsIObjectOutputStream* aStream) override;
+
+ protected:
+ nsCOMPtr<nsIURI> mInnerURI;
+
+ nsresult SetPathQueryRef(const nsACString& aPathQueryRef) override;
+ nsresult SetQuery(const nsACString& aQuery) override;
+ nsresult SetRef(const nsACString& aRef) override;
+ bool Deserialize(const mozilla::ipc::URIParams&);
+ nsresult ReadPrivate(nsIObjectInputStream* stream);
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<nsSimpleNestedURI>,
+ public nsISerializable,
+ public nsINestedURIMutator {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ [[nodiscard]] NS_IMETHOD Deserialize(
+ const mozilla::ipc::URIParams& aParams) override {
+ return InitFromIPCParams(aParams);
+ }
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return InitFromInputStream(aStream);
+ }
+
+ [[nodiscard]] NS_IMETHOD Finalize(nsIURI** aURI) override {
+ mURI.forget(aURI);
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHOD SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ NS_ADDREF(*aMutator = this);
+ }
+ return InitFromSpec(aSpec);
+ }
+
+ [[nodiscard]] NS_IMETHOD Init(nsIURI* innerURI) override {
+ mURI = new nsSimpleNestedURI(innerURI);
+ return NS_OK;
+ }
+
+ friend class nsSimpleNestedURI;
+ };
+
+ friend BaseURIMutator<nsSimpleNestedURI>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* nsSimpleNestedURI_h__ */
diff --git a/netwerk/base/nsSimpleStreamListener.cpp b/netwerk/base/nsSimpleStreamListener.cpp
new file mode 100644
index 0000000000..beed8a9f40
--- /dev/null
+++ b/netwerk/base/nsSimpleStreamListener.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSimpleStreamListener.h"
+
+namespace mozilla {
+namespace net {
+
+//
+//----------------------------------------------------------------------------
+// nsISupports implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMPL_ISUPPORTS(nsSimpleStreamListener, nsISimpleStreamListener,
+ nsIStreamListener, nsIRequestObserver)
+
+//
+//----------------------------------------------------------------------------
+// nsIRequestObserver implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::OnStartRequest(nsIRequest* aRequest) {
+ return mObserver ? mObserver->OnStartRequest(aRequest) : NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleStreamListener::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ return mObserver ? mObserver->OnStopRequest(request, aStatus) : NS_OK;
+}
+
+//
+//----------------------------------------------------------------------------
+// nsIStreamListener implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* aSource,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t writeCount;
+ nsresult rv = mSink->WriteFrom(aSource, aCount, &writeCount);
+ //
+ // Equate zero bytes read and NS_SUCCEEDED to stopping the read.
+ //
+ if (NS_SUCCEEDED(rv) && (writeCount == 0)) return NS_BASE_STREAM_CLOSED;
+ return rv;
+}
+
+//
+//----------------------------------------------------------------------------
+// nsISimpleStreamListener implementation...
+//----------------------------------------------------------------------------
+//
+NS_IMETHODIMP
+nsSimpleStreamListener::Init(nsIOutputStream* aSink,
+ nsIRequestObserver* aObserver) {
+ MOZ_ASSERT(aSink, "null output stream");
+
+ mSink = aSink;
+ mObserver = aObserver;
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleStreamListener.h b/netwerk/base/nsSimpleStreamListener.h
new file mode 100644
index 0000000000..890b49f61b
--- /dev/null
+++ b/netwerk/base/nsSimpleStreamListener.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSimpleStreamListener_h__
+#define nsSimpleStreamListener_h__
+
+#include "nsISimpleStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace net {
+
+class nsSimpleStreamListener : public nsISimpleStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISIMPLESTREAMLISTENER
+
+ nsSimpleStreamListener() = default;
+
+ protected:
+ virtual ~nsSimpleStreamListener() = default;
+
+ nsCOMPtr<nsIOutputStream> mSink;
+ nsCOMPtr<nsIRequestObserver> mObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsSimpleURI.cpp b/netwerk/base/nsSimpleURI.cpp
new file mode 100644
index 0000000000..56e3300ffe
--- /dev/null
+++ b/netwerk/base/nsSimpleURI.cpp
@@ -0,0 +1,794 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#undef LOG
+#include "ipc/IPCMessageUtils.h"
+
+#include "nsSimpleURI.h"
+#include "nscore.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "nsURLHelper.h"
+#include "nsNetCID.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsEscape.h"
+#include "nsError.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIURIMutator.h"
+#include "mozilla/net/MozURL.h"
+#include "mozilla/StaticPrefs_network.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kThisSimpleURIImplementationCID,
+ NS_THIS_SIMPLEURI_IMPLEMENTATION_CID);
+
+/* static */
+already_AddRefed<nsSimpleURI> nsSimpleURI::From(nsIURI* aURI) {
+ RefPtr<nsSimpleURI> uri;
+ nsresult rv = aURI->QueryInterface(kThisSimpleURIImplementationCID,
+ getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+NS_IMPL_CLASSINFO(nsSimpleURI, nullptr, nsIClassInfo::THREADSAFE,
+ NS_SIMPLEURI_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(nsSimpleURI)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsSimpleURI methods:
+
+NS_IMPL_ADDREF(nsSimpleURI)
+NS_IMPL_RELEASE(nsSimpleURI)
+NS_INTERFACE_TABLE_HEAD(nsSimpleURI)
+ NS_INTERFACE_TABLE(nsSimpleURI, nsIURI, nsISerializable)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE
+ NS_IMPL_QUERY_CLASSINFO(nsSimpleURI)
+ if (aIID.Equals(kThisSimpleURIImplementationCID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsISizeOf)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISerializable methods:
+
+NS_IMETHODIMP
+nsSimpleURI::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsSimpleURI::ReadPrivate(nsIObjectInputStream* aStream) {
+ nsresult rv;
+
+ bool isMutable;
+ rv = aStream->ReadBoolean(&isMutable);
+ if (NS_FAILED(rv)) return rv;
+ Unused << isMutable;
+
+ rv = aStream->ReadCString(mScheme);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->ReadCString(mPath);
+ if (NS_FAILED(rv)) return rv;
+
+ bool isRefValid;
+ rv = aStream->ReadBoolean(&isRefValid);
+ if (NS_FAILED(rv)) return rv;
+ mIsRefValid = isRefValid;
+
+ if (isRefValid) {
+ rv = aStream->ReadCString(mRef);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ mRef.Truncate(); // invariant: mRef should be empty when it's not valid
+ }
+
+ bool isQueryValid;
+ rv = aStream->ReadBoolean(&isQueryValid);
+ if (NS_FAILED(rv)) return rv;
+ mIsQueryValid = isQueryValid;
+
+ if (isQueryValid) {
+ rv = aStream->ReadCString(mQuery);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Write(nsIObjectOutputStream* aStream) {
+ nsresult rv;
+
+ rv = aStream->WriteBoolean(false); // former mMutable
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteStringZ(mScheme.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteStringZ(mPath.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aStream->WriteBoolean(mIsRefValid);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mIsRefValid) {
+ rv = aStream->WriteStringZ(mRef.get());
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = aStream->WriteBoolean(mIsQueryValid);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mIsQueryValid) {
+ rv = aStream->WriteStringZ(mQuery.get());
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+void nsSimpleURI::Serialize(URIParams& aParams) {
+ SimpleURIParams params;
+
+ params.scheme() = mScheme;
+ params.path() = mPath;
+
+ if (mIsRefValid) {
+ params.ref() = mRef;
+ } else {
+ params.ref().SetIsVoid(true);
+ }
+
+ if (mIsQueryValid) {
+ params.query() = mQuery;
+ } else {
+ params.query().SetIsVoid(true);
+ }
+
+ aParams = params;
+}
+
+bool nsSimpleURI::Deserialize(const URIParams& aParams) {
+ if (aParams.type() != URIParams::TSimpleURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const SimpleURIParams& params = aParams.get_SimpleURIParams();
+
+ mScheme = params.scheme();
+ mPath = params.path();
+
+ if (params.ref().IsVoid()) {
+ mRef.Truncate();
+ mIsRefValid = false;
+ } else {
+ mRef = params.ref();
+ mIsRefValid = true;
+ }
+
+ if (params.query().IsVoid()) {
+ mQuery.Truncate();
+ mIsQueryValid = false;
+ } else {
+ mQuery = params.query();
+ mIsQueryValid = true;
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIURI methods:
+
+NS_IMETHODIMP
+nsSimpleURI::GetSpec(nsACString& result) {
+ if (!result.Assign(mScheme, fallible) || !result.Append(":"_ns, fallible) ||
+ !result.Append(mPath, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mIsQueryValid) {
+ if (!result.Append("?"_ns, fallible) || !result.Append(mQuery, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
+ }
+
+ if (mIsRefValid) {
+ if (!result.Append("#"_ns, fallible) || !result.Append(mRef, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
+ }
+
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsSimpleURI::GetSpecIgnoringRef(nsACString& result) {
+ result = mScheme + ":"_ns + mPath;
+ if (mIsQueryValid) {
+ result += "?"_ns + mQuery;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetDisplaySpec(nsACString& aUnicodeSpec) {
+ return GetSpec(aUnicodeSpec);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
+ return GetHostPort(aUnicodeHostPort);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetDisplayHost(nsACString& aUnicodeHost) {
+ return GetHost(aUnicodeHost);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetDisplayPrePath(nsACString& aPrePath) {
+ return GetPrePath(aPrePath);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHasRef(bool* result) {
+ *result = mIsRefValid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHasUserPass(bool* result) {
+ *result = false;
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetSpecInternal(const nsACString& aSpec,
+ bool aStripWhitespace) {
+ if (StaticPrefs::network_url_max_length() &&
+ aSpec.Length() > StaticPrefs::network_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsresult rv = net_ExtractURLScheme(aSpec, mScheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString spec;
+ rv = net_FilterAndEscapeURI(
+ aSpec, esc_OnlyNonASCII,
+ aStripWhitespace ? ASCIIMask::MaskWhitespace() : ASCIIMask::MaskCRLFTab(),
+ spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int32_t colonPos = spec.FindChar(':');
+ MOZ_ASSERT(colonPos != kNotFound, "A colon should be in this string");
+ // This sets mPath, mQuery and mRef.
+ return SetPathQueryRefInternal(Substring(spec, colonPos + 1));
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetScheme(nsACString& result) {
+ result = mScheme;
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetScheme(const nsACString& input) {
+ // Strip tabs, newlines, carriage returns from input
+ nsAutoCString scheme(input);
+ scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ if (!net_IsValidScheme(scheme)) {
+ NS_WARNING("the given url scheme contains invalid characters");
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ mScheme = scheme;
+ ToLowerCase(mScheme);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPrePath(nsACString& result) {
+ result = mScheme + ":"_ns;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetUserPass(nsACString& result) { return NS_ERROR_FAILURE; }
+
+nsresult nsSimpleURI::SetUserPass(const nsACString& userPass) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetUsername(nsACString& result) { return NS_ERROR_FAILURE; }
+
+nsresult nsSimpleURI::SetUsername(const nsACString& userName) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPassword(nsACString& result) { return NS_ERROR_FAILURE; }
+
+nsresult nsSimpleURI::SetPassword(const nsACString& password) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHostPort(nsACString& result) {
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code may depend on this throwing.
+ // Note: If this is changed, change GetAsciiHostPort as well.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsSimpleURI::SetHostPort(const nsACString& aValue) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHost(nsACString& result) {
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code depend on this throwing.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsSimpleURI::SetHost(const nsACString& host) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPort(int32_t* result) {
+ // Note: Audit all callers before changing this to return an empty
+ // string -- CAPS and UI code may depend on this throwing.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsSimpleURI::SetPort(int32_t port) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+nsSimpleURI::GetPathQueryRef(nsACString& result) {
+ result = mPath;
+ if (mIsQueryValid) {
+ result += "?"_ns + mQuery;
+ }
+ if (mIsRefValid) {
+ result += "#"_ns + mRef;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetPathQueryRef(const nsACString& aPath) {
+ if (StaticPrefs::network_url_max_length() &&
+ aPath.Length() > StaticPrefs::network_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString path;
+ nsresult rv = NS_EscapeURL(aPath, esc_OnlyNonASCII, path, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return SetPathQueryRefInternal(path);
+}
+
+nsresult nsSimpleURI::SetPathQueryRefInternal(const nsACString& aPath) {
+ nsresult rv;
+ const auto* start = aPath.BeginReading();
+ const auto* end = aPath.EndReading();
+
+ // Find the first instance of ? or # that marks the end of the path.
+ auto hashOrQueryFilter = [](char c) { return c == '?' || c == '#'; };
+ const auto* pathEnd = std::find_if(start, end, hashOrQueryFilter);
+
+ mIsQueryValid = false;
+ mQuery.Truncate();
+
+ mIsRefValid = false;
+ mRef.Truncate();
+
+ // The path
+ if (!mPath.Assign(Substring(start, pathEnd), fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (pathEnd == end) {
+ return NS_OK;
+ }
+
+ const auto* queryEnd =
+ std::find_if(pathEnd, end, [](char c) { return c == '#'; });
+
+ rv = SetQuery(Substring(pathEnd, queryEnd));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (queryEnd == end) {
+ return NS_OK;
+ }
+
+ return SetRef(Substring(queryEnd, end));
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetRef(nsACString& result) {
+ if (!mIsRefValid) {
+ MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
+ result.Truncate();
+ } else {
+ result = mRef;
+ }
+
+ return NS_OK;
+}
+
+// NOTE: SetRef("") removes our ref, whereas SetRef("#") sets it to the empty
+// string (and will result in .spec and .path having a terminal #).
+nsresult nsSimpleURI::SetRef(const nsACString& aRef) {
+ if (StaticPrefs::network_url_max_length() &&
+ aRef.Length() > StaticPrefs::network_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString ref;
+ nsresult rv =
+ NS_EscapeURL(aRef, esc_OnlyNonASCII | esc_Spaces, ref, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (ref.IsEmpty()) {
+ // Empty string means to remove ref completely.
+ mIsRefValid = false;
+ mRef.Truncate(); // invariant: mRef should be empty when it's not valid
+
+ // Trim trailing invalid chars when ref and query are removed
+ if (mScheme != "javascript" && mRef.IsEmpty() && mQuery.IsEmpty()) {
+ TrimTrailingCharactersFromPath();
+ }
+
+ return NS_OK;
+ }
+
+ mIsRefValid = true;
+
+ // Gracefully skip initial hash
+ if (ref[0] == '#') {
+ mRef = Substring(ref, 1);
+ } else {
+ mRef = ref;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Equals(nsIURI* other, bool* result) {
+ return EqualsInternal(other, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::EqualsExceptRef(nsIURI* other, bool* result) {
+ return EqualsInternal(other, eIgnoreRef, result);
+}
+
+/* virtual */
+nsresult nsSimpleURI::EqualsInternal(
+ nsIURI* other, nsSimpleURI::RefHandlingEnum refHandlingMode, bool* result) {
+ NS_ENSURE_ARG_POINTER(other);
+ MOZ_ASSERT(result, "null pointer");
+
+ RefPtr<nsSimpleURI> otherUri;
+ nsresult rv = other->QueryInterface(kThisSimpleURIImplementationCID,
+ getter_AddRefs(otherUri));
+ if (NS_FAILED(rv)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ *result = EqualsInternal(otherUri, refHandlingMode);
+ return NS_OK;
+}
+
+bool nsSimpleURI::EqualsInternal(nsSimpleURI* otherUri,
+ RefHandlingEnum refHandlingMode) {
+ bool result = (mScheme == otherUri->mScheme && mPath == otherUri->mPath);
+
+ if (result) {
+ result = (mIsQueryValid == otherUri->mIsQueryValid &&
+ (!mIsQueryValid || mQuery == otherUri->mQuery));
+ }
+
+ if (result && refHandlingMode == eHonorRef) {
+ result = (mIsRefValid == otherUri->mIsRefValid &&
+ (!mIsRefValid || mRef == otherUri->mRef));
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::SchemeIs(const char* i_Scheme, bool* o_Equals) {
+ MOZ_ASSERT(o_Equals, "null pointer");
+ if (!i_Scheme) {
+ *o_Equals = false;
+ return NS_OK;
+ }
+
+ const char* this_scheme = mScheme.get();
+
+ // mScheme is guaranteed to be lower case.
+ if (*i_Scheme == *this_scheme || *i_Scheme == (*this_scheme - ('a' - 'A'))) {
+ *o_Equals = nsCRT::strcasecmp(this_scheme, i_Scheme) == 0;
+ } else {
+ *o_Equals = false;
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ nsSimpleURI* nsSimpleURI::StartClone(
+ nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef) {
+ nsSimpleURI* url = new nsSimpleURI();
+ SetRefOnClone(url, refHandlingMode, newRef);
+ return url;
+}
+
+/* virtual */
+void nsSimpleURI::SetRefOnClone(nsSimpleURI* url,
+ nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef) {
+ if (refHandlingMode == eHonorRef) {
+ url->mRef = mRef;
+ url->mIsRefValid = mIsRefValid;
+ } else if (refHandlingMode == eReplaceRef) {
+ url->SetRef(newRef);
+ }
+}
+
+nsresult nsSimpleURI::Clone(nsIURI** result) {
+ return CloneInternal(eHonorRef, ""_ns, result);
+}
+
+nsresult nsSimpleURI::CloneInternal(
+ nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef,
+ nsIURI** result) {
+ RefPtr<nsSimpleURI> url = StartClone(refHandlingMode, newRef);
+ if (!url) return NS_ERROR_OUT_OF_MEMORY;
+
+ url->mScheme = mScheme;
+ url->mPath = mPath;
+
+ url->mIsQueryValid = mIsQueryValid;
+ if (url->mIsQueryValid) {
+ url->mQuery = mQuery;
+ }
+
+ url.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::Resolve(const nsACString& relativePath, nsACString& result) {
+ nsAutoCString scheme;
+ nsresult rv = net_ExtractURLScheme(relativePath, scheme);
+ if (NS_SUCCEEDED(rv)) {
+ result = relativePath;
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ rv = GetAsciiSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If getting the spec fails for some reason, preserve behaviour and just
+ // return the relative path.
+ result = relativePath;
+ return NS_OK;
+ }
+
+ RefPtr<MozURL> url;
+ rv = MozURL::Init(getter_AddRefs(url), spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If parsing the current url fails, we revert to the previous behaviour
+ // and just return the relative path.
+ result = relativePath;
+ return NS_OK;
+ }
+
+ RefPtr<MozURL> url2;
+ rv = MozURL::Init(getter_AddRefs(url2), relativePath, url);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If parsing the relative url fails, we revert to the previous behaviour
+ // and just return the relative path.
+ result = relativePath;
+ return NS_OK;
+ }
+
+ result = url2->Spec();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiSpec(nsACString& aResult) {
+ nsresult rv = GetSpec(aResult);
+ if (NS_FAILED(rv)) return rv;
+ MOZ_ASSERT(IsAscii(aResult), "The spec should be ASCII");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiHostPort(nsACString& result) {
+ // XXX This behavior mimics GetHostPort.
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetAsciiHost(nsACString& result) {
+ result.Truncate();
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsISizeOf
+//----------------------------------------------------------------------------
+
+size_t nsSimpleURI::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mScheme.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mQuery.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mRef.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+}
+
+size_t nsSimpleURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetFilePath(nsACString& aFilePath) {
+ aFilePath = mPath;
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetFilePath(const nsACString& aFilePath) {
+ if (mPath.IsEmpty() || mPath.First() != '/') {
+ // cannot-be-a-base
+ return NS_ERROR_MALFORMED_URI;
+ }
+ const char* current = aFilePath.BeginReading();
+ const char* end = aFilePath.EndReading();
+
+ // Only go up to the first ? or # symbol
+ for (; current < end; ++current) {
+ if (*current == '?' || *current == '#') {
+ break;
+ }
+ }
+ return SetPathQueryRef(
+ nsDependentCSubstring(aFilePath.BeginReading(), current));
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetQuery(nsACString& aQuery) {
+ if (!mIsQueryValid) {
+ MOZ_ASSERT(mQuery.IsEmpty(), "mIsQueryValid/mQuery invariant broken");
+ aQuery.Truncate();
+ } else {
+ aQuery = mQuery;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetHasQuery(bool* result) {
+ *result = mIsQueryValid;
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetQuery(const nsACString& aQuery) {
+ if (StaticPrefs::network_url_max_length() &&
+ aQuery.Length() > StaticPrefs::network_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ nsAutoCString query;
+ nsresult rv = NS_EscapeURL(aQuery, esc_OnlyNonASCII, query, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (query.IsEmpty()) {
+ // Empty string means to remove query completely.
+ mIsQueryValid = false;
+ mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+
+ // Trim trailing invalid chars when ref and query are removed
+ if (mScheme != "javascript" && mRef.IsEmpty() && mQuery.IsEmpty()) {
+ TrimTrailingCharactersFromPath();
+ }
+
+ return NS_OK;
+ }
+
+ mIsQueryValid = true;
+
+ // Gracefully skip initial question mark
+ if (query[0] == '?') {
+ mQuery = Substring(query, 1);
+ } else {
+ mQuery = query;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetQueryWithEncoding(const nsACString& aQuery,
+ const Encoding* aEncoding) {
+ return SetQuery(aQuery);
+}
+
+void nsSimpleURI::TrimTrailingCharactersFromPath() {
+ const auto* start = mPath.BeginReading();
+ const auto* end = mPath.EndReading();
+
+ auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
+ const auto* newEnd =
+ std::find_if(std::reverse_iterator<decltype(end)>(end),
+ std::reverse_iterator<decltype(start)>(start), charFilter)
+ .base();
+
+ auto trailCount = std::distance(newEnd, end);
+ if (trailCount) {
+ mPath.Truncate(mPath.Length() - trailCount);
+ }
+}
+
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsSimpleURI::Mutator, nsIURISetters,
+ nsIURIMutator, nsISerializable,
+ nsISimpleURIMutator)
+
+NS_IMETHODIMP
+nsSimpleURI::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsSimpleURI::Mutator> mutator = new nsSimpleURI::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSimpleURI.h b/netwerk/base/nsSimpleURI.h
new file mode 100644
index 0000000000..560dd56040
--- /dev/null
+++ b/netwerk/base/nsSimpleURI.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSimpleURI_h__
+#define nsSimpleURI_h__
+
+#include "mozilla/MemoryReporting.h"
+#include "nsIURI.h"
+#include "nsISerializable.h"
+#include "nsString.h"
+#include "nsIClassInfo.h"
+#include "nsISizeOf.h"
+#include "nsIURIMutator.h"
+#include "nsISimpleURIMutator.h"
+
+namespace mozilla {
+namespace net {
+
+#define NS_THIS_SIMPLEURI_IMPLEMENTATION_CID \
+ { /* 0b9bb0c2-fee6-470b-b9b9-9fd9462b5e19 */ \
+ 0x0b9bb0c2, 0xfee6, 0x470b, { \
+ 0xb9, 0xb9, 0x9f, 0xd9, 0x46, 0x2b, 0x5e, 0x19 \
+ } \
+ }
+
+class nsSimpleURI : public nsIURI, public nsISerializable, public nsISizeOf {
+ protected:
+ nsSimpleURI() = default;
+ virtual ~nsSimpleURI() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSISERIALIZABLE
+
+ static already_AddRefed<nsSimpleURI> From(nsIURI* aURI);
+
+ // nsSimpleURI methods:
+
+ bool Equals(nsSimpleURI* aOther) { return EqualsInternal(aOther, eHonorRef); }
+
+ // nsISizeOf
+ // Among the sub-classes that inherit (directly or indirectly) from
+ // nsSimpleURI, measurement of the following members may be added later if
+ // DMD finds it is worthwhile:
+ // - nsJSURI: mBaseURI
+ // - nsSimpleNestedURI: mInnerURI
+ // - nsBlobURI: mPrincipal
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ protected:
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum { eIgnoreRef, eHonorRef, eReplaceRef };
+
+ virtual nsresult Clone(nsIURI** result);
+ virtual nsresult SetSpecInternal(const nsACString& aSpec,
+ bool aStripWhitespace = false);
+ virtual nsresult SetScheme(const nsACString& input);
+ virtual nsresult SetUserPass(const nsACString& input);
+ nsresult SetUsername(const nsACString& input);
+ virtual nsresult SetPassword(const nsACString& input);
+ virtual nsresult SetHostPort(const nsACString& aValue);
+ virtual nsresult SetHost(const nsACString& input);
+ virtual nsresult SetPort(int32_t port);
+ virtual nsresult SetPathQueryRef(const nsACString& aPath);
+ virtual nsresult SetRef(const nsACString& aRef);
+ virtual nsresult SetFilePath(const nsACString& aFilePath);
+ virtual nsresult SetQuery(const nsACString& aQuery);
+ virtual nsresult SetQueryWithEncoding(const nsACString& aQuery,
+ const Encoding* encoding);
+ nsresult ReadPrivate(nsIObjectInputStream* stream);
+
+ // Helper to share code between Equals methods.
+ virtual nsresult EqualsInternal(nsIURI* other,
+ RefHandlingEnum refHandlingMode,
+ bool* result);
+
+ // Helper to be used by inherited classes who want to test
+ // equality given an assumed nsSimpleURI. This must NOT check
+ // the passed-in other for QI to our CID.
+ bool EqualsInternal(nsSimpleURI* otherUri, RefHandlingEnum refHandlingMode);
+
+ // Used by StartClone (and versions of StartClone in subclasses) to
+ // handle the ref in the right way for clones.
+ void SetRefOnClone(nsSimpleURI* url, RefHandlingEnum refHandlingMode,
+ const nsACString& newRef);
+
+ // NOTE: This takes the refHandlingMode as an argument because
+ // nsSimpleNestedURI's specialized version needs to know how to clone
+ // its inner URI.
+ virtual nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef);
+
+ // Helper to share code between Clone methods.
+ virtual nsresult CloneInternal(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef, nsIURI** result);
+
+ void TrimTrailingCharactersFromPath();
+ nsresult EscapeAndSetPathQueryRef(const nsACString& aPath);
+ nsresult SetPathQueryRefInternal(const nsACString& aPath);
+
+ bool Deserialize(const mozilla::ipc::URIParams&);
+
+ nsCString mScheme;
+ nsCString mPath; // NOTE: mPath does not include ref, as an optimization
+ nsCString mRef; // so that URIs with different refs can share string data.
+ nsCString
+ mQuery; // so that URLs with different querys can share string data.
+ bool mIsRefValid{false}; // To distinguish between empty-ref and no-ref.
+ // To distinguish between empty-query and no-query.
+ bool mIsQueryValid{false};
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<nsSimpleURI>,
+ public nsISimpleURIMutator,
+ public nsISerializable {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+ NS_DEFINE_NSIMUTATOR_COMMON
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return InitFromInputStream(aStream);
+ }
+
+ [[nodiscard]] NS_IMETHOD SetSpecAndFilterWhitespace(
+ const nsACString& aSpec, nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ *aMutator = do_AddRef(this).take();
+ }
+
+ nsresult rv = NS_OK;
+ RefPtr<nsSimpleURI> uri = new nsSimpleURI();
+ rv = uri->SetSpecInternal(aSpec, /* filterWhitespace */ true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURI = std::move(uri);
+ return NS_OK;
+ }
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+
+ friend class nsSimpleURI;
+ };
+
+ friend BaseURIMutator<nsSimpleURI>;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsSimpleURI_h__
diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp
new file mode 100644
index 0000000000..e12a376788
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -0,0 +1,3411 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "nsSocketTransport2.h"
+
+#include "IOActivityMonitor.h"
+#include "NSSErrorsService.h"
+#include "NetworkDataCountLayer.h"
+#include "QuicSocketControl.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/SSLTokensCache.h"
+#include "mozilla/ProfilerBandwidthCounter.h"
+#include "nsCOMPtr.h"
+#include "nsICancelable.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIOService.h"
+#include "nsIPipe.h"
+#include "nsISocketProvider.h"
+#include "nsITLSSocketControl.h"
+#include "nsNetAddr.h"
+#include "nsNetCID.h"
+#include "nsNetSegmentUtils.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsProxyInfo.h"
+#include "nsSocketProviderService.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsTransportUtils.h"
+#include "nsURLHelper.h"
+#include "prerr.h"
+#include "sslexp.h"
+#include "xpcpublic.h"
+
+#if defined(FUZZING)
+# include "FuzzyLayer.h"
+# include "FuzzySocketControl.h"
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+
+#if defined(XP_WIN)
+# include "ShutdownLayer.h"
+#endif
+
+/* Following inclusions required for keepalive config not supported by NSPR. */
+#include "private/pprio.h"
+#if defined(XP_WIN)
+# include <winsock2.h>
+# include <mstcpip.h>
+#elif defined(XP_UNIX)
+# include <errno.h>
+# include <netinet/tcp.h>
+#endif
+/* End keepalive config inclusions. */
+
+#define SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 0
+#define UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 1
+#define SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 2
+#define UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 3
+
+//-----------------------------------------------------------------------------
+
+static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsSocketEvent : public Runnable {
+ public:
+ nsSocketEvent(nsSocketTransport* transport, uint32_t type,
+ nsresult status = NS_OK, nsISupports* param = nullptr,
+ std::function<void()>&& task = nullptr)
+ : Runnable("net::nsSocketEvent"),
+ mTransport(transport),
+ mType(type),
+ mStatus(status),
+ mParam(param),
+ mTask(std::move(task)) {}
+
+ NS_IMETHOD Run() override {
+ mTransport->OnSocketEvent(mType, mStatus, mParam, std::move(mTask));
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsSocketTransport> mTransport;
+
+ uint32_t mType;
+ nsresult mStatus;
+ nsCOMPtr<nsISupports> mParam;
+ std::function<void()> mTask;
+};
+
+//-----------------------------------------------------------------------------
+
+// #define TEST_CONNECT_ERRORS
+#ifdef TEST_CONNECT_ERRORS
+# include <stdlib.h>
+static PRErrorCode RandomizeConnectError(PRErrorCode code) {
+ //
+ // To test out these errors, load http://www.yahoo.com/. It should load
+ // correctly despite the random occurrence of these errors.
+ //
+ int n = rand();
+ if (n > RAND_MAX / 2) {
+ struct {
+ PRErrorCode err_code;
+ const char* err_name;
+ } errors[] = {
+ //
+ // These errors should be recoverable provided there is another
+ // IP address in mDNSRecord.
+ //
+ {PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR"},
+ {PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR"},
+ //
+ // This error will cause this socket transport to error out;
+ // however, if the consumer is HTTP, then the HTTP transaction
+ // should be restarted when this error occurs.
+ //
+ {PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR"},
+ };
+ n = n % (sizeof(errors) / sizeof(errors[0]));
+ code = errors[n].err_code;
+ SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name));
+ }
+ return code;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) {
+ nsresult rv = NS_ERROR_FAILURE;
+ switch (errorCode) {
+ case PR_WOULD_BLOCK_ERROR:
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ break;
+ case PR_CONNECT_ABORTED_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ rv = NS_ERROR_NET_RESET;
+ break;
+ case PR_END_OF_FILE_ERROR: // XXX document this correlation
+ rv = NS_ERROR_NET_INTERRUPT;
+ break;
+ case PR_CONNECT_REFUSED_ERROR:
+ // We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
+ // could get better diagnostics by adding distinct XPCOM error codes for
+ // each of these, but there are a lot of places in Gecko that check
+ // specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
+ // be checked.
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_HOST_UNREACHABLE_ERROR:
+ case PR_ADDRESS_NOT_AVAILABLE_ERROR:
+ // Treat EACCES as a soft error since (at least on Linux) connect() returns
+ // EACCES when an IPv6 connection is blocked by a firewall. See bug 270784.
+ case PR_NO_ACCESS_RIGHTS_ERROR:
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ case PR_ADDRESS_NOT_SUPPORTED_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+ break;
+ case PR_IO_TIMEOUT_ERROR:
+ case PR_CONNECT_TIMEOUT_ERROR:
+ rv = NS_ERROR_NET_TIMEOUT;
+ break;
+ case PR_OUT_OF_MEMORY_ERROR:
+ // These really indicate that the descriptor table filled up, or that the
+ // kernel ran out of network buffers - but nobody really cares which part of
+ // the system ran out of memory.
+ case PR_PROC_DESC_TABLE_FULL_ERROR:
+ case PR_SYS_DESC_TABLE_FULL_ERROR:
+ case PR_INSUFFICIENT_RESOURCES_ERROR:
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ case PR_ADDRESS_IN_USE_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_IN_USE;
+ break;
+ // These filename-related errors can arise when using Unix-domain sockets.
+ case PR_FILE_NOT_FOUND_ERROR:
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ break;
+ case PR_IS_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_IS_DIRECTORY;
+ break;
+ case PR_LOOP_ERROR:
+ rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+ break;
+ case PR_NAME_TOO_LONG_ERROR:
+ rv = NS_ERROR_FILE_NAME_TOO_LONG;
+ break;
+ case PR_NO_DEVICE_SPACE_ERROR:
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ break;
+ case PR_NOT_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_NOT_DIRECTORY;
+ break;
+ case PR_READ_ONLY_FILESYSTEM_ERROR:
+ rv = NS_ERROR_FILE_READ_ONLY;
+ break;
+ case PR_BAD_ADDRESS_ERROR:
+ rv = NS_ERROR_UNKNOWN_HOST;
+ break;
+ default:
+ if (psm::IsNSSErrorCode(errorCode)) {
+ rv = psm::GetXPCOMFromNSSError(errorCode);
+ }
+ break;
+
+ // NSPR's socket code can return these, but they're not worth breaking out
+ // into their own error codes, distinct from NS_ERROR_FAILURE:
+ //
+ // PR_BAD_DESCRIPTOR_ERROR
+ // PR_INVALID_ARGUMENT_ERROR
+ // PR_NOT_SOCKET_ERROR
+ // PR_NOT_TCP_SOCKET_ERROR
+ // These would indicate a bug internal to the component.
+ //
+ // PR_PROTOCOL_NOT_SUPPORTED_ERROR
+ // This means that we can't use the given "protocol" (like
+ // IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As
+ // above, this indicates an internal bug.
+ //
+ // PR_IS_CONNECTED_ERROR
+ // This indicates that we've applied a system call like 'bind' or
+ // 'connect' to a socket that is already connected. The socket
+ // components manage each file descriptor's state, and in some cases
+ // handle this error result internally. We shouldn't be returning
+ // this to our callers.
+ //
+ // PR_IO_ERROR
+ // This is so vague that NS_ERROR_FAILURE is just as good.
+ }
+ SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%" PRIx32 "]\n", errorCode,
+ static_cast<uint32_t>(rv)));
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// socket input stream impl
+//-----------------------------------------------------------------------------
+
+nsSocketInputStream::nsSocketInputStream(nsSocketTransport* trans)
+ : mTransport(trans) {}
+
+// called on the socket transport thread...
+//
+// condition : failure code if socket has been closed
+//
+void nsSocketInputStream::OnSocketReady(nsresult condition) {
+ SOCKET_LOG(("nsSocketInputStream::OnSocketReady [this=%p cond=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(condition)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition)) mCondition = condition;
+
+ // ignore event if only waiting for closure and not closed.
+ if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ callback = std::move(mCallback);
+ mCallbackFlags = 0;
+ }
+ }
+
+ if (callback) callback->OnInputStreamReady(this);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSocketInputStream, nsIInputStream,
+ nsIAsyncInputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketInputStream::AddRef() {
+ ++mReaderRefCnt;
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketInputStream::Release() {
+ if (--mReaderRefCnt == 0) Close();
+ return mTransport->Release();
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
+
+NS_IMETHODIMP
+nsSocketInputStream::Available(uint64_t* avail) {
+ SOCKET_LOG(("nsSocketInputStream::Available [this=%p]\n", this));
+
+ *avail = 0;
+
+ PRFileDesc* fd;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition)) return mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd) return NS_OK;
+ }
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Available(fd);
+
+ // PSM does not implement PR_Available() so do a best approximation of it
+ // with MSG_PEEK
+ if ((n == -1) && (PR_GetError() == PR_NOT_IMPLEMENTED_ERROR)) {
+ char c;
+
+ n = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
+ SOCKET_LOG(
+ ("nsSocketInputStream::Available [this=%p] "
+ "using PEEK backup n=%d]\n",
+ this, n));
+ }
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n >= 0) {
+ *avail = n;
+ } else {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) return NS_OK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::StreamStatus() {
+ SOCKET_LOG(("nsSocketInputStream::StreamStatus [this=%p]\n", this));
+
+ MutexAutoLock lock(mTransport->mLock);
+ return mCondition;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Read(char* buf, uint32_t count, uint32_t* countRead) {
+ SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition)) {
+ return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition;
+ }
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Read [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Read(fd, buf, count);
+
+ SOCKET_LOG((" PR_Read returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0) mTransport->TraceInBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0) {
+ mByteCount += (*countRead = n);
+ profiler_count_bandwidth_read_bytes(n);
+ } else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
+
+ // only send this notification if we have indeed read some data.
+ // see bug 196827 for an example of why this is important.
+ if (n > 0) mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* countRead) {
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::IsNonBlocking(bool* nonblocking) {
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::CloseWithStatus(nsresult reason) {
+ SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(reason)));
+
+ // may be called from any thread
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_SUCCEEDED(mCondition)) {
+ rv = mCondition = reason;
+ } else {
+ rv = NS_OK;
+ }
+ }
+ if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
+ uint32_t amount, nsIEventTarget* target) {
+ SOCKET_LOG(("nsSocketInputStream::AsyncWait [this=%p]\n", this));
+
+ bool hasError = false;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (callback && target) {
+ //
+ // build event proxy
+ //
+ mCallback = NS_NewInputStreamReadyEvent("nsSocketInputStream::AsyncWait",
+ callback, target);
+ } else {
+ mCallback = callback;
+ }
+ mCallbackFlags = flags;
+
+ hasError = NS_FAILED(mCondition);
+ } // unlock mTransport->mLock
+
+ if (hasError) {
+ // OnSocketEvent will call OnInputStreamReady with an error code after
+ // going through the event loop. We do this because most socket callers
+ // do not expect AsyncWait() to synchronously execute the OnInputStreamReady
+ // callback.
+ mTransport->PostEvent(nsSocketTransport::MSG_INPUT_PENDING);
+ } else {
+ mTransport->OnInputPending();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// socket output stream impl
+//-----------------------------------------------------------------------------
+
+nsSocketOutputStream::nsSocketOutputStream(nsSocketTransport* trans)
+ : mTransport(trans) {}
+
+// called on the socket transport thread...
+//
+// condition : failure code if socket has been closed
+//
+void nsSocketOutputStream::OnSocketReady(nsresult condition) {
+ SOCKET_LOG(("nsSocketOutputStream::OnSocketReady [this=%p cond=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(condition)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIOutputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition)) mCondition = condition;
+
+ // ignore event if only waiting for closure and not closed.
+ if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ callback = std::move(mCallback);
+ mCallbackFlags = 0;
+ }
+ }
+
+ if (callback) callback->OnOutputStreamReady(this);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSocketOutputStream, nsIOutputStream,
+ nsIAsyncOutputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketOutputStream::AddRef() {
+ ++mWriterRefCnt;
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketOutputStream::Release() {
+ if (--mWriterRefCnt == 0) Close();
+ return mTransport->Release();
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
+
+NS_IMETHODIMP
+nsSocketOutputStream::Flush() { return NS_OK; }
+
+NS_IMETHODIMP
+nsSocketOutputStream::StreamStatus() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mCondition;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Write(const char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ SOCKET_LOG(("nsSocketOutputStream::Write [this=%p count=%u]\n", this, count));
+
+ *countWritten = 0;
+
+ // A write of 0 bytes can be used to force the initial SSL handshake, so do
+ // not reject that.
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition)) return mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Write [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Write(fd, buf, count);
+
+ SOCKET_LOG((" PR_Write returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0) mTransport->TraceOutBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0) {
+ mByteCount += (*countWritten = n);
+ profiler_count_bandwidth_written_bytes(n);
+ } else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv);
+
+ // only send this notification if we have indeed written some data.
+ // see bug 196827 for an example of why this is important.
+ if ((n > 0)) {
+ mTransport->SendStatus(NS_NET_STATUS_SENDING_TO);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* countRead) {
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsSocketOutputStream::WriteFromSegments(
+ nsIInputStream* input, void* closure, const char* fromSegment,
+ uint32_t offset, uint32_t count, uint32_t* countRead) {
+ nsSocketOutputStream* self = (nsSocketOutputStream*)closure;
+ return self->Write(fromSegment, count, countRead);
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::WriteFrom(nsIInputStream* stream, uint32_t count,
+ uint32_t* countRead) {
+ return stream->ReadSegments(WriteFromSegments, this, count, countRead);
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::IsNonBlocking(bool* nonblocking) {
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::CloseWithStatus(nsresult reason) {
+ SOCKET_LOG(("nsSocketOutputStream::CloseWithStatus [this=%p reason=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(reason)));
+
+ // may be called from any thread
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_SUCCEEDED(mCondition)) {
+ rv = mCondition = reason;
+ } else {
+ rv = NS_OK;
+ }
+ }
+ if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::AsyncWait(nsIOutputStreamCallback* callback,
+ uint32_t flags, uint32_t amount,
+ nsIEventTarget* target) {
+ SOCKET_LOG(("nsSocketOutputStream::AsyncWait [this=%p]\n", this));
+
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (callback && target) {
+ //
+ // build event proxy
+ //
+ mCallback = NS_NewOutputStreamReadyEvent(callback, target);
+ } else {
+ mCallback = callback;
+ }
+
+ mCallbackFlags = flags;
+ }
+ mTransport->OnOutputPending();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// socket transport impl
+//-----------------------------------------------------------------------------
+
+nsSocketTransport::nsSocketTransport()
+ : mFD(this),
+ mSocketTransportService(gSocketTransportService),
+ mInput(new nsSocketInputStream(this)),
+ mOutput(new nsSocketOutputStream(this)) {
+ SOCKET_LOG(("creating nsSocketTransport @%p\n", this));
+
+ mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX; // no timeout
+ mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout
+}
+
+nsSocketTransport::~nsSocketTransport() {
+ MOZ_RELEASE_ASSERT(!mAttached);
+ SOCKET_LOG(("destroying nsSocketTransport @%p\n", this));
+}
+
+nsresult nsSocketTransport::Init(const nsTArray<nsCString>& types,
+ const nsACString& host, uint16_t port,
+ const nsACString& hostRoute,
+ uint16_t portRoute,
+ nsIProxyInfo* givenProxyInfo,
+ nsIDNSRecord* dnsRecord) {
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (givenProxyInfo) {
+ proxyInfo = do_QueryInterface(givenProxyInfo);
+ NS_ENSURE_ARG(proxyInfo);
+ }
+
+ if (dnsRecord) {
+ mExternalDNSResolution = true;
+ mDNSRecord = do_QueryInterface(dnsRecord);
+ mDNSRecord->IsTRR(&mResolvedByTRR);
+ mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
+ }
+
+ // init socket type info
+
+ mOriginHost = host;
+ mOriginPort = port;
+ if (!hostRoute.IsEmpty()) {
+ mHost = hostRoute;
+ mPort = portRoute;
+ } else {
+ mHost = host;
+ mPort = port;
+ }
+
+ // A subtle check we don't enter this method more than once for the socket
+ // transport lifetime. Disable on TSan builds to prevent race checking, we
+ // don't want an atomic here for perf reasons!
+#ifndef MOZ_TSAN
+ MOZ_ASSERT(!mPortRemappingApplied);
+#endif // !MOZ_TSAN
+
+ if (proxyInfo) {
+ mHttpsProxy = proxyInfo->IsHTTPS();
+ }
+
+ const char* proxyType = nullptr;
+ mProxyInfo = proxyInfo;
+ if (proxyInfo) {
+ mProxyPort = proxyInfo->Port();
+ mProxyHost = proxyInfo->Host();
+ // grab proxy type (looking for "socks" for example)
+ proxyType = proxyInfo->Type();
+ if (proxyType && (proxyInfo->IsHTTP() || proxyInfo->IsHTTPS() ||
+ proxyInfo->IsDirect() || !strcmp(proxyType, "unknown"))) {
+ proxyType = nullptr;
+ }
+ }
+
+ SOCKET_LOG1(
+ ("nsSocketTransport::Init [this=%p host=%s:%hu origin=%s:%d "
+ "proxy=%s:%hu]\n",
+ this, mHost.get(), mPort, mOriginHost.get(), mOriginPort,
+ mProxyHost.get(), mProxyPort));
+
+ // include proxy type as a socket type if proxy type is not "http"
+ uint32_t typeCount = types.Length() + (proxyType != nullptr);
+ if (!typeCount) return NS_OK;
+
+ // if we have socket types, then the socket provider service had
+ // better exist!
+ nsresult rv;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ nsSocketProviderService::GetOrCreate();
+
+ if (!mTypes.SetCapacity(typeCount, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // now verify that each socket type has a registered socket provider.
+ for (uint32_t i = 0, type = 0; i < typeCount; ++i) {
+ // store socket types
+ if (i == 0 && proxyType) {
+ mTypes.AppendElement(proxyType);
+ } else {
+ mTypes.AppendElement(types[type++]);
+ }
+
+ nsCOMPtr<nsISocketProvider> provider;
+ rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("no registered socket provider");
+ return rv;
+ }
+
+ // note if socket type corresponds to a transparent proxy
+ // XXX don't hardcode SOCKS here (use proxy info's flags instead).
+ if (mTypes[i].EqualsLiteral("socks") || mTypes[i].EqualsLiteral("socks4")) {
+ mProxyTransparent = true;
+
+ if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
+ // we want the SOCKS layer to send the hostname
+ // and port to the proxy and let it do the DNS.
+ mProxyTransparentResolvesHost = true;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+#if defined(XP_UNIX)
+nsresult nsSocketTransport::InitWithFilename(const char* filename) {
+ return InitWithName(filename, strlen(filename));
+}
+
+nsresult nsSocketTransport::InitWithName(const char* name, size_t length) {
+ if (length > sizeof(mNetAddr.local.path) - 1) {
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ }
+
+ if (!name[0] && length > 1) {
+ // name is abstract address name that is supported on Linux only
+# if defined(XP_LINUX)
+ mHost.Assign(name + 1, length - 1);
+# else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+# endif
+ } else {
+ // The name isn't abstract socket address. So this is Unix domain
+ // socket that has file path.
+ mHost.Assign(name, length);
+ }
+ mPort = 0;
+
+ mNetAddr.local.family = AF_LOCAL;
+ memcpy(mNetAddr.local.path, name, length);
+ mNetAddr.local.path[length] = '\0';
+ mNetAddrIsSet = true;
+
+ return NS_OK;
+}
+#endif
+
+nsresult nsSocketTransport::InitWithConnectedSocket(PRFileDesc* fd,
+ const NetAddr* addr) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ char buf[kNetAddrMaxCStrBufSize];
+ addr->ToStringBuffer(buf, sizeof(buf));
+ mHost.Assign(buf);
+
+ uint16_t port;
+ if (addr->raw.family == AF_INET) {
+ port = addr->inet.port;
+ } else if (addr->raw.family == AF_INET6) {
+ port = addr->inet6.port;
+ } else {
+ port = 0;
+ }
+ mPort = ntohs(port);
+
+ memcpy(&mNetAddr, addr, sizeof(NetAddr));
+
+ mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
+ mState = STATE_TRANSFERRING;
+ SetSocketName(fd);
+ mNetAddrIsSet = true;
+
+ {
+ MutexAutoLock lock(mLock);
+ NS_ASSERTION(!mFD.IsInitialized(), "already initialized");
+ mFD = fd;
+ mFDref = 1;
+ mFDconnected = true;
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ }
+
+ // make sure new socket is non-blocking
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(fd, &opt);
+
+ SOCKET_LOG(
+ ("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n",
+ this, mHost.get(), mPort));
+
+ // jump to InitiateSocket to get ourselves attached to the STS poll list.
+ return PostEvent(MSG_RETRY_INIT_SOCKET);
+}
+
+nsresult nsSocketTransport::InitWithConnectedSocket(
+ PRFileDesc* aFD, const NetAddr* aAddr, nsIInterfaceRequestor* aCallbacks) {
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = aCallbacks;
+ }
+ return InitWithConnectedSocket(aFD, aAddr);
+}
+
+nsresult nsSocketTransport::PostEvent(uint32_t type, nsresult status,
+ nsISupports* param,
+ std::function<void()>&& task) {
+ SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%" PRIx32
+ " param=%p]\n",
+ this, type, static_cast<uint32_t>(status), param));
+
+ nsCOMPtr<nsIRunnable> event =
+ new nsSocketEvent(this, type, status, param, std::move(task));
+ if (!event) return NS_ERROR_OUT_OF_MEMORY;
+
+ return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void nsSocketTransport::SendStatus(nsresult status) {
+ SOCKET_LOG1(("nsSocketTransport::SendStatus [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(status)));
+
+ nsCOMPtr<nsITransportEventSink> sink;
+ uint64_t progress;
+ {
+ MutexAutoLock lock(mLock);
+ sink = mEventSink;
+ switch (status) {
+ case NS_NET_STATUS_SENDING_TO:
+ progress = mOutput->ByteCount(lock);
+ break;
+ case NS_NET_STATUS_RECEIVING_FROM:
+ progress = mInput->ByteCount(lock);
+ break;
+ default:
+ progress = 0;
+ break;
+ }
+ }
+ if (sink) {
+ sink->OnTransportStatus(this, status, progress, -1);
+ }
+}
+
+nsresult nsSocketTransport::ResolveHost() {
+ SOCKET_LOG((
+ "nsSocketTransport::ResolveHost [this=%p %s:%d%s] "
+ "mProxyTransparentResolvesHost=%d\n",
+ this, SocketHost().get(), SocketPort(),
+ mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? " bypass cache" : "",
+ mProxyTransparentResolvesHost));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv;
+
+ if (!mProxyHost.IsEmpty()) {
+ if (!mProxyTransparent || mProxyTransparentResolvesHost) {
+#if defined(XP_UNIX)
+ MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
+ "Unix domain sockets can't be used with proxies");
+#endif
+ // When not resolving mHost locally, we still want to ensure that
+ // it only contains valid characters. See bug 304904 for details.
+ // Sometimes the end host is not yet known and mHost is *
+ if (!net_IsValidHostName(mHost) && !mHost.EqualsLiteral("*")) {
+ SOCKET_LOG((" invalid hostname %s\n", mHost.get()));
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ }
+ if (mProxyTransparentResolvesHost) {
+ // Name resolution is done on the server side. Just pretend
+ // client resolution is complete, this will get picked up later.
+ // since we don't need to do DNS now, we bypass the resolving
+ // step by initializing mNetAddr to an empty address, but we
+ // must keep the port. The SOCKS IO layer will use the hostname
+ // we send it when it's created, rather than the empty address
+ // we send with the connect call.
+ mState = STATE_RESOLVING;
+ mNetAddr.raw.family = AF_INET;
+ mNetAddr.inet.port = htons(SocketPort());
+ mNetAddr.inet.ip = htonl(INADDR_ANY);
+ return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
+ }
+ }
+
+ if (mExternalDNSResolution) {
+ MOZ_ASSERT(mDNSRecord);
+ mState = STATE_RESOLVING;
+ return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
+ }
+
+ nsCOMPtr<nsIDNSService> dns = nullptr;
+ auto initTask = [&dns]() { dns = do_GetService(kDNSServiceCID); };
+ if (!NS_IsMainThread()) {
+ // Forward to the main thread synchronously.
+ RefPtr<nsIThread> mainThread = do_GetMainThread();
+ if (!mainThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SyncRunnable::DispatchToThread(
+ mainThread,
+ NS_NewRunnableFunction("nsSocketTransport::ResolveHost->GetDNSService",
+ initTask));
+ } else {
+ initTask();
+ }
+ if (!dns) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mResolving = true;
+
+ nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE) {
+ dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ if (mConnectionFlags & nsSocketTransport::REFRESH_CACHE) {
+ dnsFlags = nsIDNSService::RESOLVE_REFRESH_CACHE;
+ }
+ if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ }
+ if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
+ }
+ if (mConnectionFlags & nsSocketTransport::DISABLE_TRR) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR;
+ }
+
+ if (mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) {
+ dnsFlags |= nsIDNSService::RESOLVE_IP_HINT;
+ }
+
+ dnsFlags |= nsIDNSService::GetFlagsFromTRRMode(
+ nsISocketTransport::GetTRRModeFromFlags(mConnectionFlags));
+
+ // When we get here, we are not resolving using any configured proxy likely
+ // because of individual proxy setting on the request or because the host is
+ // excluded from proxying. Hence, force resolution despite global proxy-DNS
+ // configuration.
+ dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS;
+
+ NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
+ !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
+ "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
+
+ SendStatus(NS_NET_STATUS_RESOLVING_HOST);
+
+ if (!SocketHost().Equals(mOriginHost)) {
+ SOCKET_LOG(("nsSocketTransport %p origin %s doing dns for %s\n", this,
+ mOriginHost.get(), SocketHost().get()));
+ }
+ rv =
+ dns->AsyncResolveNative(SocketHost(), nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ dnsFlags, nullptr, this, mSocketTransportService,
+ mOriginAttributes, getter_AddRefs(mDNSRequest));
+
+ if (NS_SUCCEEDED(rv)) {
+ SOCKET_LOG((" advancing to STATE_RESOLVING\n"));
+ mState = STATE_RESOLVING;
+ }
+ return rv;
+}
+
+nsresult nsSocketTransport::BuildSocket(PRFileDesc*& fd, bool& proxyTransparent,
+ bool& usingSSL) {
+ SOCKET_LOG(("nsSocketTransport::BuildSocket [this=%p]\n", this));
+
+ nsresult rv = NS_OK;
+
+ proxyTransparent = false;
+ usingSSL = false;
+
+ if (mTypes.IsEmpty()) {
+ fd = PR_OpenTCPSocket(mNetAddr.raw.family);
+ if (!fd) {
+ SOCKET_LOG((" error creating TCP nspr socket [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+ }
+
+#if defined(XP_UNIX)
+ MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
+ "Unix domain sockets can't be used with socket types");
+#endif
+
+ fd = nullptr;
+
+ uint32_t controlFlags = 0;
+ if (mProxyTransparentResolvesHost) {
+ controlFlags |= nsISocketProvider::PROXY_RESOLVES_HOST;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT) {
+ controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE) {
+ controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE) {
+ controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::DONT_TRY_ECH) {
+ controlFlags |= nsISocketProvider::DONT_TRY_ECH;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::IS_RETRY) {
+ controlFlags |= nsISocketProvider::IS_RETRY;
+ }
+
+ if (mConnectionFlags &
+ nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) {
+ controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::IS_SPECULATIVE_CONNECTION) {
+ controlFlags |= nsISocketProvider::IS_SPECULATIVE_CONNECTION;
+ }
+
+ if (mResolvedByTRR) {
+ controlFlags |= nsISocketProvider::USED_PRIVATE_DNS;
+ }
+
+ // by setting host to mOriginHost, instead of mHost we send the
+ // SocketProvider (e.g. PSM) the origin hostname but can still do DNS
+ // on an explicit alternate service host name
+ const char* host = mOriginHost.get();
+ int32_t port = (int32_t)mOriginPort;
+
+ nsCOMPtr<nsISocketProviderService> spserv =
+ nsSocketProviderService::GetOrCreate();
+ nsCOMPtr<nsIProxyInfo> proxyInfo = mProxyInfo;
+
+ uint32_t i;
+ for (i = 0; i < mTypes.Length(); ++i) {
+ nsCOMPtr<nsISocketProvider> provider;
+
+ SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i].get()));
+
+ rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
+ if (NS_FAILED(rv)) break;
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ if (i == 0) {
+ // if this is the first type, we'll want the
+ // service to allocate a new socket
+
+ // Most layers _ESPECIALLY_ PSM want the origin name here as they
+ // will use it for secure checks, etc.. and any connection management
+ // differences between the origin name and the routed name can be
+ // taken care of via DNS. However, SOCKS is a special case as there is
+ // no DNS. in the case of SOCKS and PSM the PSM is a separate layer
+ // and receives the origin name.
+ const char* socketProviderHost = host;
+ int32_t socketProviderPort = port;
+ if (mProxyTransparentResolvesHost &&
+ (mTypes[0].EqualsLiteral("socks") ||
+ mTypes[0].EqualsLiteral("socks4"))) {
+ SOCKET_LOG(("SOCKS %d Host/Route override: %s:%d -> %s:%d\n",
+ mHttpsProxy, socketProviderHost, socketProviderPort,
+ mHost.get(), mPort));
+ socketProviderHost = mHost.get();
+ socketProviderPort = mPort;
+ }
+
+ // when https proxying we want to just connect to the proxy as if
+ // it were the end host (i.e. expect the proxy's cert)
+
+ rv = provider->NewSocket(
+ mNetAddr.raw.family,
+ mHttpsProxy ? mProxyHost.get() : socketProviderHost,
+ mHttpsProxy ? mProxyPort : socketProviderPort, proxyInfo,
+ mOriginAttributes, controlFlags, mTlsFlags, &fd,
+ getter_AddRefs(tlsSocketControl));
+
+ if (NS_SUCCEEDED(rv) && !fd) {
+ MOZ_ASSERT_UNREACHABLE(
+ "NewSocket succeeded but failed to "
+ "create a PRFileDesc");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ // the socket has already been allocated,
+ // so we just want the service to add itself
+ // to the stack (such as pushing an io layer)
+ rv = provider->AddToSocket(mNetAddr.raw.family, host, port, proxyInfo,
+ mOriginAttributes, controlFlags, mTlsFlags, fd,
+ getter_AddRefs(tlsSocketControl));
+ }
+
+ // controlFlags = 0; not used below this point...
+ if (NS_FAILED(rv)) break;
+
+ // if the service was ssl or starttls, we want to hold onto the socket
+ // info
+ bool isSSL = mTypes[i].EqualsLiteral("ssl");
+ if (isSSL || mTypes[i].EqualsLiteral("starttls")) {
+ // remember security info
+ {
+ MutexAutoLock lock(mLock);
+ mTLSSocketControl = tlsSocketControl;
+ SOCKET_LOG((" [tlsSocketControl=%p callbacks=%p]\n",
+ mTLSSocketControl.get(), mCallbacks.get()));
+ }
+ // remember if socket type is SSL so we can ProxyStartSSL if need be.
+ usingSSL = isSSL;
+ } else if (mTypes[i].EqualsLiteral("socks") ||
+ mTypes[i].EqualsLiteral("socks4")) {
+ // since socks is transparent, any layers above
+ // it do not have to worry about proxy stuff
+ proxyInfo = nullptr;
+ proxyTransparent = true;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ SOCKET_LOG((" error pushing io layer [%u:%s rv=%" PRIx32 "]\n", i,
+ mTypes[i].get(), static_cast<uint32_t>(rv)));
+ if (fd) {
+ CloseSocket(
+ fd, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ }
+ }
+ return rv;
+}
+
+nsresult nsSocketTransport::InitiateSocket() {
+ SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv;
+ bool isLocal;
+ IsLocal(&isLocal);
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_ABORT;
+ }
+ if (gIOService->IsOffline()) {
+ if (StaticPrefs::network_disable_localhost_when_offline() || !isLocal) {
+ return NS_ERROR_OFFLINE;
+ }
+ } else if (!isLocal) {
+#ifdef DEBUG
+ // all IP networking has to be done from the parent
+ if (NS_SUCCEEDED(mCondition) && ((mNetAddr.raw.family == AF_INET) ||
+ (mNetAddr.raw.family == AF_INET6))) {
+ MOZ_ASSERT(!IsNeckoChild());
+ }
+#endif
+
+ if (NS_SUCCEEDED(mCondition) && xpc::AreNonLocalConnectionsDisabled() &&
+ !(mNetAddr.IsIPAddrAny() || mNetAddr.IsIPAddrLocal() ||
+ mNetAddr.IsIPAddrShared())) {
+ nsAutoCString ipaddr;
+ RefPtr<nsNetAddr> netaddr = new nsNetAddr(&mNetAddr);
+ netaddr->GetAddress(ipaddr);
+ fprintf_stderr(
+ stderr,
+ "FATAL ERROR: Non-local network connections are disabled and a "
+ "connection "
+ "attempt to %s (%s) was made.\nYou should only access hostnames "
+ "available via the test networking proxy (if running mochitests) "
+ "or from a test-specific httpd.js server (if running xpcshell "
+ "tests). "
+ "Browser services should be disabled or redirected to a local "
+ "server.\n",
+ mHost.get(), ipaddr.get());
+ return NS_ERROR_NON_LOCAL_CONNECTION_REFUSED;
+ }
+ }
+
+ // Hosts/Proxy Hosts that are Local IP Literals should not be speculatively
+ // connected - Bug 853423.
+ if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 &&
+ mNetAddr.IsIPAddrLocal()) {
+ if (SOCKET_LOG_ENABLED()) {
+ nsAutoCString netAddrCString;
+ netAddrCString.SetLength(kIPv6CStrBufSize);
+ if (!mNetAddr.ToStringBuffer(netAddrCString.BeginWriting(),
+ kIPv6CStrBufSize)) {
+ netAddrCString = "<IP-to-string failed>"_ns;
+ }
+ SOCKET_LOG(
+ ("nsSocketTransport::InitiateSocket skipping "
+ "speculative connection for host [%s:%d] proxy "
+ "[%s:%d] with Local IP address [%s]",
+ mHost.get(), mPort, mProxyHost.get(), mProxyPort,
+ netAddrCString.get()));
+ }
+ mCondition = NS_ERROR_CONNECTION_REFUSED;
+ OnSocketDetached(nullptr);
+ return mCondition;
+ }
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!mSocketTransportService->CanAttachSocket()) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsSocketEvent(this, MSG_RETRY_INIT_SOCKET);
+ if (!event) return NS_ERROR_OUT_OF_MEMORY;
+ return mSocketTransportService->NotifyWhenCanAttachSocket(event);
+ }
+
+ //
+ // if we already have a connected socket, then just attach and return.
+ //
+ {
+ MutexAutoLock lock(mLock);
+ if (mFD.IsInitialized()) {
+ rv = mSocketTransportService->AttachSocket(mFD, this);
+ if (NS_SUCCEEDED(rv)) mAttached = true;
+ return rv;
+ }
+ }
+
+ //
+ // create new socket fd, push io layers, etc.
+ //
+ PRFileDesc* fd;
+ bool proxyTransparent;
+ bool usingSSL;
+
+ rv = BuildSocket(fd, proxyTransparent, usingSSL);
+ if (NS_FAILED(rv)) {
+ SOCKET_LOG(
+ (" BuildSocket failed [rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ // create proxy via IOActivityMonitor
+ IOActivityMonitor::MonitorSocket(fd);
+
+#ifdef FUZZING
+ if (StaticPrefs::fuzzing_necko_enabled()) {
+ rv = AttachFuzzyIOLayer(fd);
+ if (NS_FAILED(rv)) {
+ SOCKET_LOG(("Failed to attach fuzzing IOLayer [rv=%" PRIx32 "].\n",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ SOCKET_LOG(("Successfully attached fuzzing IOLayer.\n"));
+
+ if (usingSSL) {
+ mTLSSocketControl = new FuzzySocketControl();
+ }
+ }
+#endif
+
+ PRStatus status;
+
+ // Make the socket non-blocking...
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ status = PR_SetSocketOption(fd, &opt);
+ NS_ASSERTION(status == PR_SUCCESS, "unable to make socket non-blocking");
+
+ if (mReuseAddrPort) {
+ SOCKET_LOG((" Setting port/addr reuse socket options\n"));
+
+ // Set ReuseAddr for TCP sockets to enable having several
+ // sockets bound to same local IP and port
+ PRSocketOptionData opt_reuseaddr;
+ opt_reuseaddr.option = PR_SockOpt_Reuseaddr;
+ opt_reuseaddr.value.reuse_addr = PR_TRUE;
+ status = PR_SetSocketOption(fd, &opt_reuseaddr);
+ if (status != PR_SUCCESS) {
+ SOCKET_LOG((" Couldn't set reuse addr socket option: %d\n", status));
+ }
+
+ // And also set ReusePort for platforms supporting this socket option
+ PRSocketOptionData opt_reuseport;
+ opt_reuseport.option = PR_SockOpt_Reuseport;
+ opt_reuseport.value.reuse_port = PR_TRUE;
+ status = PR_SetSocketOption(fd, &opt_reuseport);
+ if (status != PR_SUCCESS &&
+ PR_GetError() != PR_OPERATION_NOT_SUPPORTED_ERROR) {
+ SOCKET_LOG((" Couldn't set reuse port socket option: %d\n", status));
+ }
+ }
+
+ // disable the nagle algorithm - if we rely on it to coalesce writes into
+ // full packets the final packet of a multi segment POST/PUT or pipeline
+ // sequence is delayed a full rtt
+ opt.option = PR_SockOpt_NoDelay;
+ opt.value.no_delay = true;
+ PR_SetSocketOption(fd, &opt);
+
+ // if the network.tcp.sendbuffer preference is set, use it to size SO_SNDBUF
+ // The Windows default of 8KB is too small and as of vista sp1, autotuning
+ // only applies to receive window
+ int32_t sndBufferSize;
+ mSocketTransportService->GetSendBufferSize(&sndBufferSize);
+ if (sndBufferSize > 0) {
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = sndBufferSize;
+ PR_SetSocketOption(fd, &opt);
+ }
+
+ if (mQoSBits) {
+ opt.option = PR_SockOpt_IpTypeOfService;
+ opt.value.tos = mQoSBits;
+ PR_SetSocketOption(fd, &opt);
+ }
+
+#if defined(XP_WIN)
+ // The linger is turned off by default. This is not a hard close, but
+ // closesocket should return immediately and operating system tries to send
+ // remaining data for certain, implementation specific, amount of time.
+ // https://msdn.microsoft.com/en-us/library/ms739165.aspx
+ //
+ // Turn the linger option on an set the interval to 0. This will cause hard
+ // close of the socket.
+ opt.option = PR_SockOpt_Linger;
+ opt.value.linger.polarity = 1;
+ opt.value.linger.linger = 0;
+ PR_SetSocketOption(fd, &opt);
+#endif
+
+ // up to here, mFD will only be accessed by us
+
+ // assign mFD so that we can properly handle OnSocketDetached before we've
+ // established a connection.
+ {
+ MutexAutoLock lock(mLock);
+ // inform socket transport about this newly created socket...
+ rv = mSocketTransportService->AttachSocket(fd, this);
+ if (NS_FAILED(rv)) {
+ CloseSocket(
+ fd, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ return rv;
+ }
+ mAttached = true;
+
+ mFD = fd;
+ mFDref = 1;
+ mFDconnected = false;
+ mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
+ }
+
+ SOCKET_LOG((" advancing to STATE_CONNECTING\n"));
+ mState = STATE_CONNECTING;
+ SendStatus(NS_NET_STATUS_CONNECTING_TO);
+
+ if (SOCKET_LOG_ENABLED()) {
+ char buf[kNetAddrMaxCStrBufSize];
+ mNetAddr.ToStringBuffer(buf, sizeof(buf));
+ SOCKET_LOG((" trying address: %s\n", buf));
+ }
+
+ //
+ // Initiate the connect() to the host...
+ //
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ {
+ if (mBindAddr) {
+ MutexAutoLock lock(mLock);
+ NetAddrToPRNetAddr(mBindAddr.get(), &prAddr);
+ status = PR_Bind(fd, &prAddr);
+ if (status != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ mBindAddr = nullptr;
+ }
+ }
+
+ NetAddrToPRNetAddr(&mNetAddr, &prAddr);
+
+#ifdef XP_WIN
+ // Find the real tcp socket and set non-blocking once again!
+ // Bug 1158189.
+ PRFileDesc* bottom = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER);
+ if (bottom) {
+ PROsfd osfd = PR_FileDesc2NativeHandle(bottom);
+ u_long nonblocking = 1;
+ if (ioctlsocket(osfd, FIONBIO, &nonblocking) != 0) {
+ NS_WARNING("Socket could not be set non-blocking!");
+ return NS_ERROR_FAILURE;
+ }
+ }
+#endif
+
+ if (mTLSSocketControl) {
+ if (!mEchConfig.IsEmpty() &&
+ !(mConnectionFlags & (DONT_TRY_ECH | BE_CONSERVATIVE))) {
+ SOCKET_LOG(("nsSocketTransport::InitiateSocket set echconfig."));
+ rv = mTLSSocketControl->SetEchConfig(mEchConfig);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mEchConfigUsed = true;
+ }
+ }
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime connectStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ connectStarted = PR_IntervalNow();
+ }
+
+ if (Telemetry::CanRecordPrereleaseData() ||
+ Telemetry::CanRecordReleaseData()) {
+ if (NS_FAILED(AttachNetworkDataCountLayer(fd))) {
+ SOCKET_LOG(
+ ("nsSocketTransport::InitiateSocket "
+ "AttachNetworkDataCountLayer failed [this=%p]\n",
+ this));
+ }
+ }
+
+ bool connectCalled = true; // This is only needed for telemetry.
+ status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT);
+ PRErrorCode code = PR_GetError();
+ if (status == PR_SUCCESS) {
+ PR_SetFDInheritable(fd, false);
+ }
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted && connectCalled) {
+ SendPRBlockingTelemetry(
+ connectStarted, Telemetry::PRCONNECT_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECT_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECT_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECT_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECT_BLOCKING_TIME_OFFLINE);
+ }
+
+ if (status == PR_SUCCESS) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+ } else {
+#if defined(TEST_CONNECT_ERRORS)
+ code = RandomizeConnectError(code);
+#endif
+ //
+ // If the PR_Connect(...) would block, then poll for a connection.
+ //
+ if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) {
+ mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
+ //
+ // If the socket is already connected, then return success...
+ //
+ } else if (PR_IS_CONNECTED_ERROR == code) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+
+ if (mTLSSocketControl && !mProxyHost.IsEmpty() && proxyTransparent &&
+ usingSSL) {
+ // if the connection phase is finished, and the ssl layer has
+ // been pushed, and we were proxying (transparently; ie. nothing
+ // has to happen in the protocol layer above us), it's time for
+ // the ssl to start doing it's thing.
+ SOCKET_LOG((" calling ProxyStartSSL()\n"));
+ mTLSSocketControl->ProxyStartSSL();
+ // XXX what if we were forced to poll on the socket for a successful
+ // connection... wouldn't we need to call ProxyStartSSL after a call
+ // to PR_ConnectContinue indicates that we are connected?
+ //
+ // XXX this appears to be what the old socket transport did. why
+ // isn't this broken?
+ }
+ }
+ //
+ // A SOCKS request was rejected; get the actual error code from
+ // the OS error
+ //
+ else if (PR_UNKNOWN_ERROR == code && mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ code = PR_GetOSError();
+ rv = ErrorAccordingToNSPR(code);
+ }
+ //
+ // The connection was refused...
+ //
+ else {
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted && connectCalled) {
+ SendPRBlockingTelemetry(
+ connectStarted, Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_OFFLINE);
+ }
+
+ rv = ErrorAccordingToNSPR(code);
+ if ((rv == NS_ERROR_CONNECTION_REFUSED) && !mProxyHost.IsEmpty()) {
+ rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ }
+ }
+ }
+ return rv;
+}
+
+bool nsSocketTransport::RecoverFromError() {
+ NS_ASSERTION(NS_FAILED(mCondition), "there should be something wrong");
+
+ SOCKET_LOG(
+ ("nsSocketTransport::RecoverFromError [this=%p state=%x cond=%" PRIx32
+ "]\n",
+ this, mState, static_cast<uint32_t>(mCondition)));
+
+ if (mDoNotRetryToConnect) {
+ SOCKET_LOG(
+ ("nsSocketTransport::RecoverFromError do not retry because "
+ "mDoNotRetryToConnect is set [this=%p]\n",
+ this));
+ return false;
+ }
+
+#if defined(XP_UNIX)
+ // Unix domain connections don't have multiple addresses to try,
+ // so the recovery techniques here don't apply.
+ if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) return false;
+#endif
+
+ if ((mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) &&
+ mCondition == NS_ERROR_UNKNOWN_HOST &&
+ (mState == MSG_DNS_LOOKUP_COMPLETE || mState == MSG_ENSURE_CONNECT)) {
+ SOCKET_LOG((" try again without USE_IP_HINT_ADDRESS"));
+ mConnectionFlags &= ~nsSocketTransport::USE_IP_HINT_ADDRESS;
+ mState = STATE_CLOSED;
+ return NS_SUCCEEDED(PostEvent(MSG_ENSURE_CONNECT, NS_OK));
+ }
+
+ // can only recover from errors in these states
+ if (mState != STATE_RESOLVING && mState != STATE_CONNECTING) {
+ SOCKET_LOG((" not in a recoverable state"));
+ return false;
+ }
+
+ nsresult rv;
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mLock);
+ NS_ASSERTION(!mFDconnected, "socket should not be connected");
+ }
+#endif
+
+ // all connection failures need to be reported to DNS so that the next
+ // time we will use a different address if available.
+ // NS_BASE_STREAM_CLOSED is not an actual connection failure, so don't report
+ // to DNS.
+ if (mState == STATE_CONNECTING && mDNSRecord &&
+ mCondition != NS_BASE_STREAM_CLOSED) {
+ mDNSRecord->ReportUnusable(SocketPort());
+ }
+
+ if (mCondition != NS_ERROR_CONNECTION_REFUSED &&
+ mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED &&
+ mCondition != NS_ERROR_NET_TIMEOUT &&
+ mCondition != NS_ERROR_UNKNOWN_HOST &&
+ mCondition != NS_ERROR_UNKNOWN_PROXY_HOST) {
+ SOCKET_LOG((" not a recoverable error %" PRIx32,
+ static_cast<uint32_t>(mCondition)));
+ return false;
+ }
+
+ bool tryAgain = false;
+
+ if ((mState == STATE_CONNECTING) && mDNSRecord) {
+ if (mNetAddr.raw.family == AF_INET) {
+ if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
+ }
+ } else if (mNetAddr.raw.family == AF_INET6) {
+ if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
+ }
+ }
+ }
+
+ if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY &&
+ mCondition == NS_ERROR_UNKNOWN_HOST && mState == STATE_RESOLVING &&
+ !mProxyTransparentResolvesHost) {
+ SOCKET_LOG((" trying lookup again with opposite ip family\n"));
+ mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4);
+ mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY;
+ // This will tell the consuming half-open to reset preference on the
+ // connection entry
+ mResetFamilyPreference = true;
+ tryAgain = true;
+ }
+
+ // try next ip address only if past the resolver stage...
+ if (mState == STATE_CONNECTING && mDNSRecord) {
+ nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
+ mDNSRecord->IsTRR(&mResolvedByTRR);
+ mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
+ if (NS_SUCCEEDED(rv)) {
+ SOCKET_LOG((" trying again with next ip address\n"));
+ tryAgain = true;
+ } else if (mExternalDNSResolution) {
+ mRetryDnsIfPossible = true;
+ bool trrEnabled;
+ mDNSRecord->IsTRR(&trrEnabled);
+ // Bug 1648147 - If the server responded with `0.0.0.0` or `::` then we
+ // should intentionally not fallback to regular DNS.
+ if (trrEnabled && !StaticPrefs::network_trr_fallback_on_zero_response() &&
+ ((mNetAddr.raw.family == AF_INET && mNetAddr.inet.ip == 0) ||
+ (mNetAddr.raw.family == AF_INET6 && mNetAddr.inet6.ip.u64[0] == 0 &&
+ mNetAddr.inet6.ip.u64[1] == 0))) {
+ SOCKET_LOG((" TRR returned 0.0.0.0 and there are no other IPs"));
+ mRetryDnsIfPossible = false;
+ }
+ } else if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY) {
+ SOCKET_LOG((" failed to connect, trying with opposite ip family\n"));
+ // Drop state to closed. This will trigger new round of DNS
+ // resolving bellow.
+ mState = STATE_CLOSED;
+ mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4);
+ mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY;
+ // This will tell the consuming half-open to reset preference on the
+ // connection entry
+ mResetFamilyPreference = true;
+ tryAgain = true;
+ } else if (!(mConnectionFlags & DISABLE_TRR)) {
+ bool trrEnabled;
+ mDNSRecord->IsTRR(&trrEnabled);
+
+ // Bug 1648147 - If the server responded with `0.0.0.0` or `::` then we
+ // should intentionally not fallback to regular DNS.
+ if (!StaticPrefs::network_trr_fallback_on_zero_response() &&
+ ((mNetAddr.raw.family == AF_INET && mNetAddr.inet.ip == 0) ||
+ (mNetAddr.raw.family == AF_INET6 && mNetAddr.inet6.ip.u64[0] == 0 &&
+ mNetAddr.inet6.ip.u64[1] == 0))) {
+ SOCKET_LOG((" TRR returned 0.0.0.0 and there are no other IPs"));
+ } else if (trrEnabled) {
+ nsIRequest::TRRMode trrMode = nsIRequest::TRR_DEFAULT_MODE;
+ mDNSRecord->GetEffectiveTRRMode(&trrMode);
+ // If current trr mode is trr only, we should not retry.
+ if (trrMode != nsIRequest::TRR_ONLY_MODE) {
+ // Drop state to closed. This will trigger a new round of
+ // DNS resolving. Bypass the cache this time since the
+ // cached data came from TRR and failed already!
+ SOCKET_LOG((" failed to connect with TRR enabled, try w/o\n"));
+ mState = STATE_CLOSED;
+ mConnectionFlags |= DISABLE_TRR | BYPASS_CACHE | REFRESH_CACHE;
+ tryAgain = true;
+ }
+ }
+ }
+ }
+
+ // prepare to try again.
+ if (tryAgain) {
+ uint32_t msg;
+
+ if (mState == STATE_CONNECTING) {
+ mState = STATE_RESOLVING;
+ msg = MSG_DNS_LOOKUP_COMPLETE;
+ } else {
+ mState = STATE_CLOSED;
+ msg = MSG_ENSURE_CONNECT;
+ }
+
+ rv = PostEvent(msg, NS_OK);
+ if (NS_FAILED(rv)) tryAgain = false;
+ }
+
+ return tryAgain;
+}
+
+// called on the socket thread only
+void nsSocketTransport::OnMsgInputClosed(nsresult reason) {
+ SOCKET_LOG(("nsSocketTransport::OnMsgInputClosed [this=%p reason=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mInputClosed = true;
+ // check if event should affect entire transport
+ if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED)) {
+ mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
+ } else if (mOutputClosed) {
+ mCondition =
+ NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
+ } else {
+ if (mState == STATE_TRANSFERRING) mPollFlags &= ~PR_POLL_READ;
+ mInput->OnSocketReady(reason);
+ }
+}
+
+// called on the socket thread only
+void nsSocketTransport::OnMsgOutputClosed(nsresult reason) {
+ SOCKET_LOG(("nsSocketTransport::OnMsgOutputClosed [this=%p reason=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mOutputClosed = true;
+ // check if event should affect entire transport
+ if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED)) {
+ mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
+ } else if (mInputClosed) {
+ mCondition =
+ NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
+ } else {
+ if (mState == STATE_TRANSFERRING) mPollFlags &= ~PR_POLL_WRITE;
+ mOutput->OnSocketReady(reason);
+ }
+}
+
+void nsSocketTransport::OnSocketConnected() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG((" advancing to STATE_TRANSFERRING\n"));
+
+ mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
+ mState = STATE_TRANSFERRING;
+
+ // Set the m*AddrIsSet flags only when state has reached TRANSFERRING
+ // because we need to make sure its value does not change due to failover
+ mNetAddrIsSet = true;
+
+ // assign mFD (must do this within the transport lock), but take care not
+ // to trample over mFDref if mFD is already set.
+ {
+ MutexAutoLock lock(mLock);
+ NS_ASSERTION(mFD.IsInitialized(), "no socket");
+ NS_ASSERTION(mFDref == 1, "wrong socket ref count");
+ SetSocketName(mFD);
+ mFDconnected = true;
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ }
+
+ // Ensure keepalive is configured correctly if previously enabled.
+ if (mKeepaliveEnabled) {
+ nsresult rv = SetKeepaliveEnabledInternal(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ SendStatus(NS_NET_STATUS_CONNECTED_TO);
+}
+
+void nsSocketTransport::SetSocketName(PRFileDesc* fd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mSelfAddrIsSet) {
+ return;
+ }
+
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ if (PR_GetSockName(fd, &prAddr) == PR_SUCCESS) {
+ PRNetAddrToNetAddr(&prAddr, &mSelfAddr);
+ mSelfAddrIsSet = true;
+ }
+}
+
+PRFileDesc* nsSocketTransport::GetFD_Locked() {
+ mLock.AssertCurrentThreadOwns();
+
+ // mFD is not available to the streams while disconnected.
+ if (!mFDconnected) return nullptr;
+
+ if (mFD.IsInitialized()) mFDref++;
+
+ return mFD;
+}
+
+class ThunkPRClose : public Runnable {
+ public:
+ explicit ThunkPRClose(PRFileDesc* fd)
+ : Runnable("net::ThunkPRClose"), mFD(fd) {}
+
+ NS_IMETHOD Run() override {
+ nsSocketTransport::CloseSocket(
+ mFD, gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ return NS_OK;
+ }
+
+ private:
+ PRFileDesc* mFD;
+};
+
+void STS_PRCloseOnSocketTransport(PRFileDesc* fd, bool lingerPolarity,
+ int16_t lingerTimeout) {
+ if (gSocketTransportService) {
+ // Can't PR_Close() a socket off STS thread. Thunk it to STS to die
+ gSocketTransportService->Dispatch(new ThunkPRClose(fd), NS_DISPATCH_NORMAL);
+ } else {
+ // something horrible has happened
+ NS_ASSERTION(gSocketTransportService, "No STS service");
+ }
+}
+
+void nsSocketTransport::ReleaseFD_Locked(PRFileDesc* fd) {
+ mLock.AssertCurrentThreadOwns();
+
+ NS_ASSERTION(mFD == fd, "wrong fd");
+
+ if (--mFDref == 0) {
+ if (gIOService->IsNetTearingDown() &&
+ ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
+ gSocketTransportService->MaxTimeForPrClosePref())) {
+ // If shutdown last to long, let the socket leak and do not close it.
+ SOCKET_LOG(("Intentional leak"));
+ } else {
+ if (mLingerPolarity || mLingerTimeout) {
+ PRSocketOptionData socket_linger;
+ socket_linger.option = PR_SockOpt_Linger;
+ socket_linger.value.linger.polarity = mLingerPolarity;
+ socket_linger.value.linger.linger = mLingerTimeout;
+ PR_SetSocketOption(mFD, &socket_linger);
+ }
+ if (OnSocketThread()) {
+ SOCKET_LOG(("nsSocketTransport: calling PR_Close [this=%p]\n", this));
+ CloseSocket(
+ mFD, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ } else {
+ // Can't PR_Close() a socket off STS thread. Thunk it to STS to die
+ STS_PRCloseOnSocketTransport(mFD, mLingerPolarity, mLingerTimeout);
+ }
+ }
+ mFD = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// socket event handler impl
+
+void nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status,
+ nsISupports* param,
+ std::function<void()>&& task) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG(
+ ("nsSocketTransport::OnSocketEvent [this=%p type=%u status=%" PRIx32
+ " param=%p]\n",
+ this, type, static_cast<uint32_t>(status), param));
+
+ if (NS_FAILED(mCondition)) {
+ // block event since we're apparently already dead.
+ SOCKET_LOG((" blocking event [condition=%" PRIx32 "]\n",
+ static_cast<uint32_t>(mCondition)));
+ //
+ // notify input/output streams in case either has a pending notify.
+ //
+ mInput->OnSocketReady(mCondition);
+ mOutput->OnSocketReady(mCondition);
+ return;
+ }
+
+ switch (type) {
+ case MSG_ENSURE_CONNECT:
+ SOCKET_LOG((" MSG_ENSURE_CONNECT\n"));
+ if (task) {
+ task();
+ }
+
+ // Apply port remapping here so that we do it on the socket thread and
+ // before we process the resolved DNS name or create the socket the first
+ // time.
+ if (!mPortRemappingApplied) {
+ mPortRemappingApplied = true;
+
+ mSocketTransportService->ApplyPortRemap(&mPort);
+ mSocketTransportService->ApplyPortRemap(&mOriginPort);
+ }
+
+ //
+ // ensure that we have created a socket, attached it, and have a
+ // connection.
+ //
+ if (mState == STATE_CLOSED) {
+ // Unix domain sockets are ready to connect; mNetAddr is all we
+ // need. Internet address families require a DNS lookup (or possibly
+ // several) before we can connect.
+#if defined(XP_UNIX)
+ if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) {
+ mCondition = InitiateSocket();
+ } else {
+#else
+ {
+#endif
+ mCondition = ResolveHost();
+ }
+
+ } else {
+ SOCKET_LOG((" ignoring redundant event\n"));
+ }
+ break;
+
+ case MSG_DNS_LOOKUP_COMPLETE:
+ if (mDNSRequest) { // only send this if we actually resolved anything
+ SendStatus(NS_NET_STATUS_RESOLVED_HOST);
+ }
+
+ SOCKET_LOG((" MSG_DNS_LOOKUP_COMPLETE\n"));
+ mDNSRequest = nullptr;
+
+ if (mDNSRecord) {
+ mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
+ mDNSRecord->IsTRR(&mResolvedByTRR);
+ mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
+ }
+ // status contains DNS lookup status
+ if (NS_FAILED(status)) {
+ // When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
+ // proxy host is not found, so we fixup the error code.
+ // For SOCKS proxies (mProxyTransparent == true), the socket
+ // transport resolves the real host here, so there's no fixup
+ // (see bug 226943).
+ if ((status == NS_ERROR_UNKNOWN_HOST) && !mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ mCondition = NS_ERROR_UNKNOWN_PROXY_HOST;
+ } else {
+ mCondition = status;
+ }
+ } else if (mState == STATE_RESOLVING) {
+ mCondition = InitiateSocket();
+ }
+ break;
+
+ case MSG_RETRY_INIT_SOCKET:
+ mCondition = InitiateSocket();
+ break;
+
+ case MSG_INPUT_CLOSED:
+ SOCKET_LOG((" MSG_INPUT_CLOSED\n"));
+ OnMsgInputClosed(status);
+ break;
+
+ case MSG_INPUT_PENDING:
+ SOCKET_LOG((" MSG_INPUT_PENDING\n"));
+ OnMsgInputPending();
+ break;
+
+ case MSG_OUTPUT_CLOSED:
+ SOCKET_LOG((" MSG_OUTPUT_CLOSED\n"));
+ OnMsgOutputClosed(status);
+ break;
+
+ case MSG_OUTPUT_PENDING:
+ SOCKET_LOG((" MSG_OUTPUT_PENDING\n"));
+ OnMsgOutputPending();
+ break;
+ case MSG_TIMEOUT_CHANGED:
+ SOCKET_LOG((" MSG_TIMEOUT_CHANGED\n"));
+ {
+ MutexAutoLock lock(mLock);
+ mPollTimeout =
+ mTimeouts[(mState == STATE_TRANSFERRING) ? TIMEOUT_READ_WRITE
+ : TIMEOUT_CONNECT];
+ }
+ break;
+ default:
+ SOCKET_LOG((" unhandled event!\n"));
+ }
+
+ if (NS_FAILED(mCondition)) {
+ SOCKET_LOG((" after event [this=%p cond=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(mCondition)));
+ if (!mAttached) { // need to process this error ourselves...
+ OnSocketDetached(nullptr);
+ }
+ } else if (mPollFlags == PR_POLL_EXCEPT) {
+ mPollFlags = 0; // make idle
+ }
+}
+
+uint64_t nsSocketTransport::ByteCountReceived() { return mInput->ByteCount(); }
+
+uint64_t nsSocketTransport::ByteCountSent() { return mOutput->ByteCount(); }
+
+//-----------------------------------------------------------------------------
+// socket handler impl
+
+void nsSocketTransport::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ SOCKET_LOG1(("nsSocketTransport::OnSocketReady [this=%p outFlags=%hd]\n",
+ this, outFlags));
+
+ if (outFlags == -1) {
+ SOCKET_LOG(("socket timeout expired\n"));
+ mCondition = NS_ERROR_NET_TIMEOUT;
+ return;
+ }
+
+ if (mState == STATE_TRANSFERRING) {
+ // if waiting to write and socket is writable or hit an exception.
+ if ((mPollFlags & PR_POLL_WRITE) && (outFlags & ~PR_POLL_READ)) {
+ // assume that we won't need to poll any longer (the stream will
+ // request that we poll again if it is still pending).
+ mPollFlags &= ~PR_POLL_WRITE;
+ mOutput->OnSocketReady(NS_OK);
+ }
+ // if waiting to read and socket is readable or hit an exception.
+ if ((mPollFlags & PR_POLL_READ) && (outFlags & ~PR_POLL_WRITE)) {
+ // assume that we won't need to poll any longer (the stream will
+ // request that we poll again if it is still pending).
+ mPollFlags &= ~PR_POLL_READ;
+ mInput->OnSocketReady(NS_OK);
+ }
+ // Update poll timeout in case it was changed
+ {
+ MutexAutoLock lock(mLock);
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ }
+ } else if ((mState == STATE_CONNECTING) && !gIOService->IsNetTearingDown()) {
+ // We do not need to do PR_ConnectContinue when we are already
+ // shutting down.
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime connectStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ connectStarted = PR_IntervalNow();
+ }
+
+ PRStatus status = PR_ConnectContinue(fd, outFlags);
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted) {
+ SendPRBlockingTelemetry(
+ connectStarted, Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_OFFLINE);
+ }
+
+ if (status == PR_SUCCESS) {
+ //
+ // we are connected!
+ //
+ OnSocketConnected();
+
+ if (mNetAddr.raw.family == AF_INET) {
+ if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
+ }
+ } else if (mNetAddr.raw.family == AF_INET6) {
+ if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
+ SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
+ }
+ }
+ } else {
+ PRErrorCode code = PR_GetError();
+#if defined(TEST_CONNECT_ERRORS)
+ code = RandomizeConnectError(code);
+#endif
+ //
+ // If the connect is still not ready, then continue polling...
+ //
+ if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) {
+ // Set up the select flags for connect...
+ mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
+ // Update poll timeout in case it was changed
+ {
+ MutexAutoLock lock(mLock);
+ mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
+ }
+ }
+ //
+ // The SOCKS proxy rejected our request. Find out why.
+ //
+ else if (PR_UNKNOWN_ERROR == code && mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ code = PR_GetOSError();
+ mCondition = ErrorAccordingToNSPR(code);
+ } else {
+ //
+ // else, the connection failed...
+ //
+ mCondition = ErrorAccordingToNSPR(code);
+ if ((mCondition == NS_ERROR_CONNECTION_REFUSED) &&
+ !mProxyHost.IsEmpty()) {
+ mCondition = NS_ERROR_PROXY_CONNECTION_REFUSED;
+ }
+ SOCKET_LOG((" connection failed! [reason=%" PRIx32 "]\n",
+ static_cast<uint32_t>(mCondition)));
+ }
+ }
+ } else if ((mState == STATE_CONNECTING) && gIOService->IsNetTearingDown()) {
+ // We do not need to do PR_ConnectContinue when we are already
+ // shutting down.
+ SOCKET_LOG(
+ ("We are in shutdown so skip PR_ConnectContinue and set "
+ "and error.\n"));
+ mCondition = NS_ERROR_ABORT;
+ } else {
+ NS_ERROR("unexpected socket state");
+ mCondition = NS_ERROR_UNEXPECTED;
+ }
+
+ if (mPollFlags == PR_POLL_EXCEPT) mPollFlags = 0; // make idle
+}
+
+// called on the socket thread only
+void nsSocketTransport::OnSocketDetached(PRFileDesc* fd) {
+ SOCKET_LOG(("nsSocketTransport::OnSocketDetached [this=%p cond=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(mCondition)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mAttached = false;
+
+ // if we didn't initiate this detach, then be sure to pass an error
+ // condition up to our consumers. (e.g., STS is shutting down.)
+ if (NS_SUCCEEDED(mCondition)) {
+ if (gIOService->IsOffline()) {
+ mCondition = NS_ERROR_OFFLINE;
+ } else {
+ mCondition = NS_ERROR_ABORT;
+ }
+ }
+
+ // If we are not shutting down try again.
+ if (!gIOService->IsNetTearingDown() && RecoverFromError()) {
+ mCondition = NS_OK;
+ } else {
+ mState = STATE_CLOSED;
+
+ // make sure there isn't any pending DNS request
+ if (mDNSRequest) {
+ mDNSRequest->Cancel(NS_ERROR_ABORT);
+ mDNSRequest = nullptr;
+ }
+
+ //
+ // notify input/output streams
+ //
+ mInput->OnSocketReady(mCondition);
+ mOutput->OnSocketReady(mCondition);
+ if (gIOService->IsNetTearingDown()) {
+ if (mInputCopyContext) {
+ NS_CancelAsyncCopy(mInputCopyContext, mCondition);
+ }
+ if (mOutputCopyContext) {
+ NS_CancelAsyncCopy(mOutputCopyContext, mCondition);
+ }
+ }
+ }
+
+ if (mCondition == NS_ERROR_NET_RESET && mDNSRecord &&
+ mOutput->ByteCount() == 0) {
+ // If we are here, it's likely that we are retrying a transaction. Blocking
+ // the already used address could increase the successful rate of the retry.
+ mDNSRecord->ReportUnusable(SocketPort());
+ }
+
+ // finally, release our reference to the socket (must do this within
+ // the transport lock) possibly closing the socket. Also release our
+ // listeners to break potential refcount cycles.
+
+ // We should be careful not to release mEventSink and mCallbacks while
+ // we're locked, because releasing it might require acquiring the lock
+ // again, so we just null out mEventSink and mCallbacks while we're
+ // holding the lock, and let the stack based objects' destuctors take
+ // care of destroying it if needed.
+ nsCOMPtr<nsIInterfaceRequestor> ourCallbacks;
+ nsCOMPtr<nsITransportEventSink> ourEventSink;
+ {
+ MutexAutoLock lock(mLock);
+ if (mFD.IsInitialized()) {
+ ReleaseFD_Locked(mFD);
+ // flag mFD as unusable; this prevents other consumers from
+ // acquiring a reference to mFD.
+ mFDconnected = false;
+ }
+
+ // We must release mCallbacks and mEventSink to avoid memory leak
+ // but only when RecoverFromError() above failed. Otherwise we lose
+ // link with UI and security callbacks on next connection attempt
+ // round. That would lead e.g. to a broken certificate exception page.
+ if (NS_FAILED(mCondition)) {
+ mCallbacks.swap(ourCallbacks);
+ mEventSink.swap(ourEventSink);
+ }
+ }
+}
+
+void nsSocketTransport::IsLocal(bool* aIsLocal) {
+ {
+ MutexAutoLock lock(mLock);
+
+#if defined(XP_UNIX)
+ // Unix-domain sockets are always local.
+ if (mNetAddr.raw.family == PR_AF_LOCAL) {
+ *aIsLocal = true;
+ return;
+ }
+#endif
+
+ *aIsLocal = mNetAddr.IsLoopbackAddr();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// xpcom api
+
+NS_IMPL_ISUPPORTS(nsSocketTransport, nsISocketTransport, nsITransport,
+ nsIDNSListener, nsIClassInfo, nsIInterfaceRequestor)
+NS_IMPL_CI_INTERFACE_GETTER(nsSocketTransport, nsISocketTransport, nsITransport,
+ nsIDNSListener, nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+nsSocketTransport::OpenInputStream(uint32_t flags, uint32_t segsize,
+ uint32_t segcount,
+ nsIInputStream** aResult) {
+ SOCKET_LOG(
+ ("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n", this, flags));
+
+ NS_ENSURE_TRUE(!mInput->IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIInputStream> result;
+ nsCOMPtr<nsISupports> inputCopyContext;
+
+ if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
+ // XXX if the caller wants blocking, then the caller also gets buffered!
+ // bool openBuffered = !(flags & OPEN_UNBUFFERED);
+ bool openBlocking = (flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ // create a pipe
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), !openBlocking,
+ true, segsize, segcount);
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(mInput.get(), pipeOut, mSocketTransportService,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize, nullptr, nullptr,
+ true, true, getter_AddRefs(inputCopyContext));
+ if (NS_FAILED(rv)) return rv;
+
+ result = pipeIn;
+ } else {
+ result = mInput.get();
+ }
+
+ // flag input stream as open
+ mInputClosed = false;
+ // mInputCopyContext can be only touched on socket thread
+ auto task = [self = RefPtr{this}, inputCopyContext(inputCopyContext)]() {
+ MOZ_ASSERT(OnSocketThread());
+ self->mInputCopyContext = inputCopyContext;
+ };
+ rv = PostEvent(MSG_ENSURE_CONNECT, NS_OK, nullptr, std::move(task));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OpenOutputStream(uint32_t flags, uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream** aResult) {
+ SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n", this,
+ flags));
+
+ NS_ENSURE_TRUE(!mOutput->IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ nsCOMPtr<nsIOutputStream> result;
+ nsCOMPtr<nsISupports> outputCopyContext;
+ if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
+ // XXX if the caller wants blocking, then the caller also gets buffered!
+ // bool openBuffered = !(flags & OPEN_UNBUFFERED);
+ bool openBlocking = (flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ // create a pipe
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true,
+ !openBlocking, segsize, segcount);
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(pipeIn, mOutput.get(), mSocketTransportService,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, segsize, nullptr, nullptr,
+ true, true, getter_AddRefs(outputCopyContext));
+ if (NS_FAILED(rv)) return rv;
+
+ result = pipeOut;
+ } else {
+ result = mOutput.get();
+ }
+
+ // flag output stream as open
+ mOutputClosed = false;
+
+ // mOutputCopyContext can be only touched on socket thread
+ auto task = [self = RefPtr{this}, outputCopyContext(outputCopyContext)]() {
+ MOZ_ASSERT(OnSocketThread());
+ self->mOutputCopyContext = outputCopyContext;
+ };
+ rv = PostEvent(MSG_ENSURE_CONNECT, NS_OK, nullptr, std::move(task));
+ if (NS_FAILED(rv)) return rv;
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::Close(nsresult reason) {
+ SOCKET_LOG(("nsSocketTransport::Close %p reason=%" PRIx32, this,
+ static_cast<uint32_t>(reason)));
+
+ if (NS_SUCCEEDED(reason)) reason = NS_BASE_STREAM_CLOSED;
+
+ mDoNotRetryToConnect = true;
+
+ mInput->CloseWithStatus(reason);
+ mOutput->CloseWithStatus(reason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetTlsSocketControl(nsITLSSocketControl** tlsSocketControl) {
+ MutexAutoLock lock(mLock);
+ *tlsSocketControl = do_AddRef(mTLSSocketControl).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor** callbacks) {
+ MutexAutoLock lock(mLock);
+ *callbacks = do_AddRef(mCallbacks).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor* callbacks) {
+ nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks;
+ NS_NewNotificationCallbacksAggregation(callbacks, nullptr,
+ GetCurrentSerialEventTarget(),
+ getter_AddRefs(threadsafeCallbacks));
+ MutexAutoLock lock(mLock);
+ mCallbacks = threadsafeCallbacks;
+ SOCKET_LOG(("Reset callbacks for tlsSocketInfo=%p callbacks=%p\n",
+ mTLSSocketControl.get(), mCallbacks.get()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetEventSink(nsITransportEventSink* sink,
+ nsIEventTarget* target) {
+ nsCOMPtr<nsITransportEventSink> temp;
+ if (target) {
+ nsresult rv =
+ net_NewTransportEventSinkProxy(getter_AddRefs(temp), sink, target);
+ if (NS_FAILED(rv)) return rv;
+ sink = temp.get();
+ }
+
+ MutexAutoLock lock(mLock);
+ mEventSink = sink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::IsAlive(bool* result) {
+ *result = false;
+
+ nsresult conditionWhileLocked = NS_OK;
+ PRFileDescAutoLock fd(this, &conditionWhileLocked);
+ if (NS_FAILED(conditionWhileLocked) || !fd.IsInitialized()) {
+ return NS_OK;
+ }
+
+ // XXX do some idle-time based checks??
+
+ char c;
+ int32_t rval = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
+
+ if ((rval > 0) || (rval < 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR)) {
+ *result = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetHost(nsACString& host) {
+ host = SocketHost();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetPort(int32_t* port) {
+ *port = (int32_t)SocketPort();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ MutexAutoLock lock(mLock);
+ NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
+
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mOriginAttributes = attrs;
+ return NS_OK;
+}
+
+nsresult nsSocketTransport::GetOriginAttributes(
+ OriginAttributes* aOriginAttributes) {
+ NS_ENSURE_ARG(aOriginAttributes);
+ *aOriginAttributes = mOriginAttributes;
+ return NS_OK;
+}
+
+nsresult nsSocketTransport::SetOriginAttributes(
+ const OriginAttributes& aOriginAttributes) {
+ MutexAutoLock lock(mLock);
+ NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
+
+ mOriginAttributes = aOriginAttributes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetPeerAddr(NetAddr* addr) {
+ // once we are in the connected state, mNetAddr will not change.
+ // so if we can verify that we are in the connected state, then
+ // we can freely access mNetAddr from any thread without being
+ // inside a critical section.
+
+ if (!mNetAddrIsSet) {
+ SOCKET_LOG(
+ ("nsSocketTransport::GetPeerAddr [this=%p state=%d] "
+ "NOT_AVAILABLE because not yet connected.",
+ this, mState));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ memcpy(addr, &mNetAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSelfAddr(NetAddr* addr) {
+ // once we are in the connected state, mSelfAddr will not change.
+ // so if we can verify that we are in the connected state, then
+ // we can freely access mSelfAddr from any thread without being
+ // inside a critical section.
+
+ if (!mSelfAddrIsSet) {
+ SOCKET_LOG(
+ ("nsSocketTransport::GetSelfAddr [this=%p state=%d] "
+ "NOT_AVAILABLE because not yet connected.",
+ this, mState));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ memcpy(addr, &mSelfAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::Bind(NetAddr* aLocalAddr) {
+ NS_ENSURE_ARG(aLocalAddr);
+
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mAttached) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mBindAddr = MakeUnique<NetAddr>();
+ memcpy(mBindAddr.get(), aLocalAddr, sizeof(NetAddr));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptablePeerAddr(nsINetAddr** addr) {
+ NetAddr rawAddr;
+
+ nsresult rv;
+ rv = GetPeerAddr(&rawAddr);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr);
+ netaddr.forget(addr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableSelfAddr(nsINetAddr** addr) {
+ NetAddr rawAddr;
+
+ nsresult rv;
+ rv = GetSelfAddr(&rawAddr);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr);
+ netaddr.forget(addr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetTimeout(uint32_t type, uint32_t* value) {
+ NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
+ MutexAutoLock lock(mLock);
+ *value = (uint32_t)mTimeouts[type];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetTimeout(uint32_t type, uint32_t value) {
+ NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
+
+ SOCKET_LOG(("nsSocketTransport::SetTimeout %p type=%u, value=%u", this, type,
+ value));
+
+ // truncate overly large timeout values.
+ {
+ MutexAutoLock lock(mLock);
+ mTimeouts[type] = (uint16_t)std::min<uint32_t>(value, UINT16_MAX);
+ }
+ PostEvent(MSG_TIMEOUT_CHANGED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetReuseAddrPort(bool reuseAddrPort) {
+ mReuseAddrPort = reuseAddrPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetLinger(bool aPolarity, int16_t aTimeout) {
+ MutexAutoLock lock(mLock);
+
+ mLingerPolarity = aPolarity;
+ mLingerTimeout = aTimeout;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetQoSBits(uint8_t aQoSBits) {
+ // Don't do any checking here of bits. Why? Because as of RFC-4594
+ // several different Class Selector and Assured Forwarding values
+ // have been defined, but that isn't to say more won't be added later.
+ // In that case, any checking would be an impediment to interoperating
+ // with newer QoS definitions.
+
+ mQoSBits = aQoSBits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetQoSBits(uint8_t* aQoSBits) {
+ *aQoSBits = mQoSBits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetRecvBufferSize(uint32_t* aSize) {
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_RecvBufferSize;
+ if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) {
+ *aSize = opt.value.recv_buffer_size;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSendBufferSize(uint32_t* aSize) {
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_SendBufferSize;
+ if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) {
+ *aSize = opt.value.send_buffer_size;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetRecvBufferSize(uint32_t aSize) {
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_RecvBufferSize;
+ opt.value.recv_buffer_size = aSize;
+ if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetSendBufferSize(uint32_t aSize) {
+ PRFileDescAutoLock fd(this);
+ if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
+
+ nsresult rv = NS_OK;
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = aSize;
+ if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
+ nsresult status) {
+ SOCKET_LOG(("nsSocketTransport::OnLookupComplete: this=%p status %" PRIx32
+ ".",
+ this, static_cast<uint32_t>(status)));
+
+ if (NS_SUCCEEDED(status)) {
+ mDNSRecord = do_QueryInterface(rec);
+ MOZ_ASSERT(mDNSRecord);
+ }
+
+ if (nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface(rec)) {
+ addrRecord->IsTRR(&mResolvedByTRR);
+ addrRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ addrRecord->GetTrrSkipReason(&mTRRSkipReason);
+ }
+
+ // flag host lookup complete for the benefit of the ResolveHost method.
+ mResolving = false;
+ nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, nullptr);
+
+ // if posting a message fails, then we should assume that the socket
+ // transport has been shutdown. this should never happen! if it does
+ // it means that the socket transport service was shutdown before the
+ // DNS service.
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to post DNS lookup complete message");
+ }
+
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+NS_IMETHODIMP
+nsSocketTransport::GetInterface(const nsIID& iid, void** result) {
+ if (iid.Equals(NS_GET_IID(nsIDNSRecord)) ||
+ iid.Equals(NS_GET_IID(nsIDNSAddrRecord))) {
+ return mDNSRecord ? mDNSRecord->QueryInterface(iid, result)
+ : NS_ERROR_NO_INTERFACE;
+ }
+ return this->QueryInterface(iid, result);
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetInterfaces(nsTArray<nsIID>& array) {
+ return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(array);
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetScriptableHelper(nsIXPCScriptable** _retval) {
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetContractID(nsACString& aContractID) {
+ aContractID.SetIsVoid(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassDescription(nsACString& aClassDescription) {
+ aClassDescription.SetIsVoid(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassID(nsCID** aClassID) {
+ *aClassID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetFlags(uint32_t* aFlags) {
+ *aFlags = nsIClassInfo::THREADSAFE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetConnectionFlags(uint32_t* value) {
+ *value = mConnectionFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetConnectionFlags(uint32_t value) {
+ SOCKET_LOG(
+ ("nsSocketTransport::SetConnectionFlags %p flags=%u", this, value));
+
+ mConnectionFlags = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetIsPrivate(bool aIsPrivate) {
+ mIsPrivate = aIsPrivate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetTlsFlags(uint32_t* value) {
+ *value = mTlsFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetTlsFlags(uint32_t value) {
+ mTlsFlags = value;
+ return NS_OK;
+}
+
+void nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // The global pref toggles keepalive as a system feature; it only affects
+ // an individual socket if keepalive has been specifically enabled for it.
+ // So, ensure keepalive is configured correctly if previously enabled.
+ if (mKeepaliveEnabled) {
+ nsresult rv = SetKeepaliveEnabledInternal(aEnabled);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal [%s] failed rv[0x%" PRIx32 "]",
+ aEnabled ? "enable" : "disable", static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+nsresult nsSocketTransport::SetKeepaliveEnabledInternal(bool aEnable) {
+ MOZ_ASSERT(mKeepaliveIdleTimeS > 0 && mKeepaliveIdleTimeS <= kMaxTCPKeepIdle);
+ MOZ_ASSERT(mKeepaliveRetryIntervalS > 0 &&
+ mKeepaliveRetryIntervalS <= kMaxTCPKeepIntvl);
+ MOZ_ASSERT(mKeepaliveProbeCount > 0 &&
+ mKeepaliveProbeCount <= kMaxTCPKeepCount);
+
+ PRFileDescAutoLock fd(this);
+ if (NS_WARN_IF(!fd.IsInitialized())) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Only enable if keepalives are globally enabled, but ensure other
+ // options are set correctly on the fd.
+ bool enable = aEnable && mSocketTransportService->IsKeepaliveEnabled();
+ nsresult rv =
+ fd.SetKeepaliveVals(enable, mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS,
+ mKeepaliveProbeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveVals failed rv[0x%" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ rv = fd.SetKeepaliveEnabled(enable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabled failed rv[0x%" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetKeepaliveEnabled(bool* aResult) {
+ MOZ_ASSERT(aResult);
+
+ *aResult = mKeepaliveEnabled;
+ return NS_OK;
+}
+
+nsresult nsSocketTransport::EnsureKeepaliveValsAreInitialized() {
+ nsresult rv = NS_OK;
+ int32_t val = -1;
+ if (mKeepaliveIdleTimeS == -1) {
+ rv = mSocketTransportService->GetKeepaliveIdleTime(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveIdleTimeS = val;
+ }
+ if (mKeepaliveRetryIntervalS == -1) {
+ rv = mSocketTransportService->GetKeepaliveRetryInterval(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveRetryIntervalS = val;
+ }
+ if (mKeepaliveProbeCount == -1) {
+ rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveProbeCount = val;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetKeepaliveEnabled(bool aEnable) {
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (aEnable == mKeepaliveEnabled) {
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] already %s.", this,
+ aEnable ? "enabled" : "disabled"));
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ if (aEnable) {
+ rv = EnsureKeepaliveValsAreInitialized();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG(
+ (" SetKeepaliveEnabled [%p] "
+ "error [0x%" PRIx32 "] initializing keepalive vals",
+ this, static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ }
+ SOCKET_LOG(
+ ("nsSocketTransport::SetKeepaliveEnabled [%p] "
+ "%s, idle time[%ds] retry interval[%ds] packet count[%d]: "
+ "globally %s.",
+ this, aEnable ? "enabled" : "disabled", mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS, mKeepaliveProbeCount,
+ mSocketTransportService->IsKeepaliveEnabled() ? "enabled" : "disabled"));
+
+ // Set mKeepaliveEnabled here so that state is maintained; it is possible
+ // that we're in between fds, e.g. the 1st IP address failed, so we're about
+ // to retry on a 2nd from the DNS record.
+ mKeepaliveEnabled = aEnable;
+
+ rv = SetKeepaliveEnabledInternal(aEnable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%" PRIx32 "]",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ return NS_OK;
+#else /* !(defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)) */
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled unsupported platform"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetKeepaliveVals(int32_t aIdleTime, int32_t aRetryInterval) {
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aIdleTime == mKeepaliveIdleTimeS &&
+ aRetryInterval == mKeepaliveRetryIntervalS) {
+ SOCKET_LOG(
+ ("nsSocketTransport::SetKeepaliveVals [%p] idle time "
+ "already %ds and retry interval already %ds.",
+ this, mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS));
+ return NS_OK;
+ }
+ mKeepaliveIdleTimeS = aIdleTime;
+ mKeepaliveRetryIntervalS = aRetryInterval;
+
+ nsresult rv = NS_OK;
+ if (mKeepaliveProbeCount == -1) {
+ int32_t val = -1;
+ nsresult rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mKeepaliveProbeCount = val;
+ }
+
+ SOCKET_LOG(
+ ("nsSocketTransport::SetKeepaliveVals [%p] "
+ "keepalive %s, idle time[%ds] retry interval[%ds] "
+ "packet count[%d]",
+ this, mKeepaliveEnabled ? "enabled" : "disabled", mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS, mKeepaliveProbeCount));
+
+ PRFileDescAutoLock fd(this);
+ if (NS_WARN_IF(!fd.IsInitialized())) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ rv = fd.SetKeepaliveVals(mKeepaliveEnabled, mKeepaliveIdleTimeS,
+ mKeepaliveRetryIntervalS, mKeepaliveProbeCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+#else
+ SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals unsupported platform"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+#ifdef ENABLE_SOCKET_TRACING
+
+# include <stdio.h>
+# include <ctype.h>
+# include "prenv.h"
+
+static void DumpBytesToFile(const char* path, const char* header,
+ const char* buf, int32_t n) {
+ FILE* fp = fopen(path, "a");
+
+ fprintf(fp, "\n%s [%d bytes]\n", header, n);
+
+ const unsigned char* p;
+ while (n) {
+ p = (const unsigned char*)buf;
+
+ int32_t i, row_max = std::min(16, n);
+
+ for (i = 0; i < row_max; ++i) fprintf(fp, "%02x ", *p++);
+ for (i = row_max; i < 16; ++i) fprintf(fp, " ");
+
+ p = (const unsigned char*)buf;
+ for (i = 0; i < row_max; ++i, ++p) {
+ if (isprint(*p))
+ fprintf(fp, "%c", *p);
+ else
+ fprintf(fp, ".");
+ }
+
+ fprintf(fp, "\n");
+ buf += row_max;
+ n -= row_max;
+ }
+
+ fprintf(fp, "\n");
+ fclose(fp);
+}
+
+void nsSocketTransport::TraceInBuf(const char* buf, int32_t n) {
+ char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
+ if (!val || !*val) return;
+
+ nsAutoCString header;
+ header.AssignLiteral("Reading from: ");
+ header.Append(mHost);
+ header.Append(':');
+ header.AppendInt(mPort);
+
+ DumpBytesToFile(val, header.get(), buf, n);
+}
+
+void nsSocketTransport::TraceOutBuf(const char* buf, int32_t n) {
+ char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
+ if (!val || !*val) return;
+
+ nsAutoCString header;
+ header.AssignLiteral("Writing to: ");
+ header.Append(mHost);
+ header.Append(':');
+ header.AppendInt(mPort);
+
+ DumpBytesToFile(val, header.get(), buf, n);
+}
+
+#endif
+
+static void LogNSPRError(const char* aPrefix, const void* aObjPtr) {
+#if defined(DEBUG)
+ PRErrorCode errCode = PR_GetError();
+ int errLen = PR_GetErrorTextLength();
+ nsAutoCString errStr;
+ if (errLen > 0) {
+ errStr.SetLength(errLen);
+ PR_GetErrorText(errStr.BeginWriting());
+ }
+ NS_WARNING(
+ nsPrintfCString("%s [%p] NSPR error[0x%x] %s.",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode,
+ errLen > 0 ? errStr.BeginReading() : "<no error text>")
+ .get());
+#endif
+}
+
+nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled(
+ bool aEnable) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!(aEnable && !gSocketTransportService->IsKeepaliveEnabled()),
+ "Cannot enable keepalive if global pref is disabled!");
+ if (aEnable && !gSocketTransportService->IsKeepaliveEnabled()) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_Keepalive;
+ opt.value.keep_alive = aEnable;
+ PRStatus status = PR_SetSocketOption(mFd, &opt);
+ if (NS_WARN_IF(status != PR_SUCCESS)) {
+ LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled",
+ mSocketTransport);
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+ return NS_OK;
+}
+
+static void LogOSError(const char* aPrefix, const void* aObjPtr) {
+#if defined(DEBUG)
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+# ifdef XP_WIN
+ DWORD errCode = WSAGetLastError();
+ char* errMessage;
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, errCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPSTR)&errMessage, 0, NULL);
+ NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%lx] %s",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
+ errCode,
+ errMessage ? errMessage : "<no error text>")
+ .get());
+ LocalFree(errMessage);
+# else
+ int errCode = errno;
+ char* errMessage = strerror(errno);
+ NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%x] %s",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
+ errCode,
+ errMessage ? errMessage : "<no error text>")
+ .get());
+# endif
+#endif
+}
+
+/* XXX PR_SetSockOpt does not support setting keepalive values, so native
+ * handles and platform specific apis (setsockopt, WSAIOCtl) are used in this
+ * file. Requires inclusion of NSPR private/pprio.h, and platform headers.
+ */
+
+nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(
+ bool aEnabled, int aIdleTime, int aRetryInterval, int aProbeCount) {
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aProbeCount <= 0 || kMaxTCPKeepCount < aProbeCount)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PROsfd sock = PR_FileDesc2NativeHandle(mFd);
+ if (NS_WARN_IF(sock == -1)) {
+ LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals",
+ mSocketTransport);
+ return ErrorAccordingToNSPR(PR_GetError());
+ }
+#endif
+
+#if defined(XP_WIN)
+ // Windows allows idle time and retry interval to be set; NOT ping count.
+ struct tcp_keepalive keepalive_vals = {(u_long)aEnabled,
+ // Windows uses msec.
+ (u_long)(aIdleTime * 1000UL),
+ (u_long)(aRetryInterval * 1000UL)};
+ DWORD bytes_returned;
+ int err =
+ WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive_vals,
+ sizeof(keepalive_vals), NULL, 0, &bytes_returned, NULL, NULL);
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport WSAIoctl failed", mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+
+#elif defined(XP_DARWIN)
+ // Darwin uses sec; only supports idle time being set.
+ int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &aIdleTime,
+ sizeof(aIdleTime));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+
+#elif defined(XP_UNIX)
+ // Not all *nix OSes support the following setsockopt() options
+ // ... but we assume they are supported in the Android kernel;
+ // build errors will tell us if they are not.
+# if defined(ANDROID) || defined(TCP_KEEPIDLE)
+ // Idle time until first keepalive probe; interval between ack'd probes;
+ // seconds.
+ int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &aIdleTime,
+ sizeof(aIdleTime));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPIDLE",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+# endif
+# if defined(ANDROID) || defined(TCP_KEEPINTVL)
+ // Interval between unack'd keepalive probes; seconds.
+ err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &aRetryInterval,
+ sizeof(aRetryInterval));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPINTVL",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+# endif
+# if defined(ANDROID) || defined(TCP_KEEPCNT)
+ // Number of unack'd keepalive probes before connection times out.
+ err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &aProbeCount,
+ sizeof(aProbeCount));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPCNT",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+# endif
+ return NS_OK;
+#else
+ MOZ_ASSERT(false,
+ "nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals "
+ "called on unsupported platform!");
+ return NS_ERROR_UNEXPECTED;
+#endif
+}
+
+void nsSocketTransport::CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled) {
+#if defined(XP_WIN)
+ AttachShutdownLayer(aFd);
+#endif
+
+ // We use PRIntervalTime here because we need
+ // nsIOService::LastOfflineStateChange time and
+ // nsIOService::LastConectivityChange time to be atomic.
+ PRIntervalTime closeStarted;
+ if (aTelemetryEnabled) {
+ closeStarted = PR_IntervalNow();
+ }
+
+ PR_Close(aFd);
+
+ if (aTelemetryEnabled) {
+ SendPRBlockingTelemetry(
+ closeStarted, Telemetry::PRCLOSE_TCP_BLOCKING_TIME_NORMAL,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_SHUTDOWN,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_LINK_CHANGE,
+ Telemetry::PRCLOSE_TCP_BLOCKING_TIME_OFFLINE);
+ }
+}
+
+void nsSocketTransport::SendPRBlockingTelemetry(
+ PRIntervalTime aStart, Telemetry::HistogramID aIDNormal,
+ Telemetry::HistogramID aIDShutdown,
+ Telemetry::HistogramID aIDConnectivityChange,
+ Telemetry::HistogramID aIDLinkChange, Telemetry::HistogramID aIDOffline) {
+ PRIntervalTime now = PR_IntervalNow();
+ if (gIOService->IsNetTearingDown()) {
+ Telemetry::Accumulate(aIDShutdown, PR_IntervalToMilliseconds(now - aStart));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange()) <
+ 60) {
+ Telemetry::Accumulate(aIDConnectivityChange,
+ PR_IntervalToMilliseconds(now - aStart));
+ } else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange()) <
+ 60) {
+ Telemetry::Accumulate(aIDLinkChange,
+ PR_IntervalToMilliseconds(now - aStart));
+
+ } else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange()) <
+ 60) {
+ Telemetry::Accumulate(aIDOffline, PR_IntervalToMilliseconds(now - aStart));
+ } else {
+ Telemetry::Accumulate(aIDNormal, PR_IntervalToMilliseconds(now - aStart));
+ }
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetResetIPFamilyPreference(bool* aReset) {
+ *aReset = mResetFamilyPreference;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetEchConfigUsed(bool* aEchConfigUsed) {
+ *aEchConfigUsed = mEchConfigUsed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetEchConfig(const nsACString& aEchConfig) {
+ mEchConfig = aEchConfig;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::ResolvedByTRR(bool* aResolvedByTRR) {
+ *aResolvedByTRR = mResolvedByTRR;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSocketTransport::GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) {
+ *aEffectiveTRRMode = mEffectiveTRRMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSocketTransport::GetTrrSkipReason(
+ nsITRRSkipReason::value* aSkipReason) {
+ *aSkipReason = mTRRSkipReason;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetRetryDnsIfPossible(bool* aRetryDns) {
+ *aRetryDns = mRetryDnsIfPossible;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetStatus(nsresult* aStatus) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ *aStatus = mCondition;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSocketTransport2.h b/netwerk/base/nsSocketTransport2.h
new file mode 100644
index 0000000000..7dd94abac7
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.h
@@ -0,0 +1,510 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSocketTransport2_h__
+#define nsSocketTransport2_h__
+
+#ifdef DEBUG_darinf
+# define ENABLE_SOCKET_TRACING
+#endif
+
+#include <functional>
+
+#include "mozilla/Mutex.h"
+#include "nsSocketTransportService2.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIClassInfo.h"
+#include "mozilla/net/DNS.h"
+#include "nsASocketHandler.h"
+#include "mozilla/Telemetry.h"
+
+#include "prerror.h"
+#include "ssl.h"
+
+class nsICancelable;
+class nsIDNSRecord;
+class nsIInterfaceRequestor;
+
+//-----------------------------------------------------------------------------
+
+// after this short interval, we will return to PR_Poll
+#define NS_SOCKET_CONNECT_TIMEOUT PR_MillisecondsToInterval(20)
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+nsresult ErrorAccordingToNSPR(PRErrorCode errorCode);
+
+class nsSocketInputStream;
+class nsSocketOutputStream;
+
+//-----------------------------------------------------------------------------
+
+class nsSocketTransport final : public nsASocketHandler,
+ public nsISocketTransport,
+ public nsIDNSListener,
+ public nsIClassInfo,
+ public nsIInterfaceRequestor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ nsSocketTransport();
+
+ // this method instructs the socket transport to open a socket of the
+ // given type(s) to the given host or proxy.
+ nsresult Init(const nsTArray<nsCString>& socketTypes, const nsACString& host,
+ uint16_t port, const nsACString& hostRoute, uint16_t portRoute,
+ nsIProxyInfo* proxyInfo, nsIDNSRecord* dnsRecord);
+
+ // this method instructs the socket transport to use an already connected
+ // socket with the given address.
+ nsresult InitWithConnectedSocket(PRFileDesc* socketFD, const NetAddr* addr);
+
+ // this method instructs the socket transport to use an already connected
+ // socket with the given address, and additionally supplies the security
+ // callbacks interface requestor.
+ nsresult InitWithConnectedSocket(PRFileDesc* aFD, const NetAddr* aAddr,
+ nsIInterfaceRequestor* aCallbacks);
+
+#ifdef XP_UNIX
+ // This method instructs the socket transport to open a socket
+ // connected to the given Unix domain address. We can only create
+ // unlayered, simple, stream sockets.
+ nsresult InitWithFilename(const char* filename);
+
+ // This method instructs the socket transport to open a socket
+ // connected to the given Unix domain address that includes abstract
+ // socket address. If using abstract socket address, first character of
+ // name parameter has to be \0.
+ // We can only create unlayered, simple, stream sockets.
+ nsresult InitWithName(const char* name, size_t len);
+#endif
+
+ // nsASocketHandler methods:
+ void OnSocketReady(PRFileDesc*, int16_t outFlags) override;
+ void OnSocketDetached(PRFileDesc*) override;
+ void IsLocal(bool* aIsLocal) override;
+ void OnKeepaliveEnabledPrefChange(bool aEnabled) final;
+
+ // called when a socket event is handled
+ void OnSocketEvent(uint32_t type, nsresult status, nsISupports* param,
+ std::function<void()>&& task);
+
+ uint64_t ByteCountReceived() override;
+ uint64_t ByteCountSent() override;
+ static void CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled);
+ static void SendPRBlockingTelemetry(
+ PRIntervalTime aStart, Telemetry::HistogramID aIDNormal,
+ Telemetry::HistogramID aIDShutdown,
+ Telemetry::HistogramID aIDConnectivityChange,
+ Telemetry::HistogramID aIDLinkChange, Telemetry::HistogramID aIDOffline);
+
+ protected:
+ virtual ~nsSocketTransport();
+
+ private:
+ // event types
+ enum {
+ MSG_ENSURE_CONNECT,
+ MSG_DNS_LOOKUP_COMPLETE,
+ MSG_RETRY_INIT_SOCKET,
+ MSG_TIMEOUT_CHANGED,
+ MSG_INPUT_CLOSED,
+ MSG_INPUT_PENDING,
+ MSG_OUTPUT_CLOSED,
+ MSG_OUTPUT_PENDING
+ };
+ nsresult PostEvent(uint32_t type, nsresult status = NS_OK,
+ nsISupports* param = nullptr,
+ std::function<void()>&& task = nullptr);
+
+ enum {
+ STATE_CLOSED,
+ STATE_IDLE,
+ STATE_RESOLVING,
+ STATE_CONNECTING,
+ STATE_TRANSFERRING
+ };
+
+ // Safer way to get and automatically release PRFileDesc objects.
+ class MOZ_STACK_CLASS PRFileDescAutoLock {
+ public:
+ explicit PRFileDescAutoLock(nsSocketTransport* aSocketTransport,
+ nsresult* aConditionWhileLocked = nullptr)
+ : mSocketTransport(aSocketTransport), mFd(nullptr) {
+ MOZ_ASSERT(aSocketTransport);
+ MutexAutoLock lock(mSocketTransport->mLock);
+ if (aConditionWhileLocked) {
+ *aConditionWhileLocked = mSocketTransport->mCondition;
+ if (NS_FAILED(mSocketTransport->mCondition)) {
+ return;
+ }
+ }
+ mFd = mSocketTransport->GetFD_Locked();
+ }
+ ~PRFileDescAutoLock() {
+ MutexAutoLock lock(mSocketTransport->mLock);
+ if (mFd) {
+ mSocketTransport->ReleaseFD_Locked(mFd);
+ }
+ }
+ bool IsInitialized() { return mFd; }
+ operator PRFileDesc*() { return mFd; }
+ nsresult SetKeepaliveEnabled(bool aEnable);
+ nsresult SetKeepaliveVals(bool aEnabled, int aIdleTime, int aRetryInterval,
+ int aProbeCount);
+
+ private:
+ operator PRFileDescAutoLock*() { return nullptr; }
+
+ // Weak ptr to nsSocketTransport since this is a stack class only.
+ nsSocketTransport* mSocketTransport;
+ PRFileDesc* mFd;
+ };
+ friend class PRFileDescAutoLock;
+
+ class LockedPRFileDesc {
+ public:
+ explicit LockedPRFileDesc(nsSocketTransport* aSocketTransport)
+ : mSocketTransport(aSocketTransport), mFd(nullptr) {
+ MOZ_ASSERT(aSocketTransport);
+ }
+ ~LockedPRFileDesc() = default;
+ bool IsInitialized() { return mFd; }
+ LockedPRFileDesc& operator=(PRFileDesc* aFd) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ mFd = aFd;
+ return *this;
+ }
+ operator PRFileDesc*() {
+ if (mSocketTransport->mAttached) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ }
+ return mFd;
+ }
+ bool operator==(PRFileDesc* aFd) {
+ mSocketTransport->mLock.AssertCurrentThreadOwns();
+ return mFd == aFd;
+ }
+
+ private:
+ operator LockedPRFileDesc*() { return nullptr; }
+ // Weak ptr to nsSocketTransport since it owns this class.
+ nsSocketTransport* mSocketTransport;
+ PRFileDesc* mFd;
+ };
+ friend class LockedPRFileDesc;
+
+ //-------------------------------------------------------------------------
+ // these members are "set" at initialization time and are never modified
+ // afterwards. this allows them to be safely accessed from any thread.
+ //-------------------------------------------------------------------------
+
+ // socket type info:
+ nsTArray<nsCString> mTypes;
+ nsCString mHost;
+ nsCString mProxyHost;
+ nsCString mOriginHost;
+ uint16_t mPort{0};
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ uint16_t mProxyPort{0};
+ uint16_t mOriginPort{0};
+ bool mProxyTransparent{false};
+ bool mProxyTransparentResolvesHost{false};
+ bool mHttpsProxy{false};
+ uint32_t mConnectionFlags{0};
+ // When we fail to connect using a prefered IP family, we tell the consumer to
+ // reset the IP family preference on the connection entry.
+ bool mResetFamilyPreference{false};
+ uint32_t mTlsFlags{0};
+ bool mReuseAddrPort{false};
+
+ // The origin attributes are used to create sockets. The first party domain
+ // will eventually be used to isolate OCSP cache and is only non-empty when
+ // "privacy.firstparty.isolate" is enabled. Setting this is the only way to
+ // carry origin attributes down to NSPR layers which are final consumers.
+ // It must be set before the socket transport is built.
+ OriginAttributes mOriginAttributes;
+
+ uint16_t SocketPort() {
+ return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyPort : mPort;
+ }
+ const nsCString& SocketHost() {
+ return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyHost : mHost;
+ }
+
+ Atomic<bool> mInputClosed{true};
+ Atomic<bool> mOutputClosed{true};
+
+ //-------------------------------------------------------------------------
+ // members accessible only on the socket transport thread:
+ // (the exception being initialization/shutdown time)
+ //-------------------------------------------------------------------------
+
+ // socket state vars:
+ uint32_t mState{STATE_CLOSED}; // STATE_??? flags
+ bool mAttached{false};
+
+ // this flag is used to determine if the results of a host lookup arrive
+ // recursively or not. this flag is not protected by any lock.
+ bool mResolving{false};
+
+ nsCOMPtr<nsICancelable> mDNSRequest;
+ nsCOMPtr<nsIDNSAddrRecord> mDNSRecord;
+
+ nsCString mEchConfig;
+ bool mEchConfigUsed = false;
+ bool mResolvedByTRR{false};
+ nsIRequest::TRRMode mEffectiveTRRMode{nsIRequest::TRR_DEFAULT_MODE};
+ nsITRRSkipReason::value mTRRSkipReason{nsITRRSkipReason::TRR_UNSET};
+
+ nsCOMPtr<nsISupports> mInputCopyContext;
+ nsCOMPtr<nsISupports> mOutputCopyContext;
+
+ // mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have
+ // reached STATE_TRANSFERRING. It must not change after that.
+ void SetSocketName(PRFileDesc* fd);
+ NetAddr mNetAddr;
+ NetAddr mSelfAddr; // getsockname()
+ Atomic<bool, Relaxed> mNetAddrIsSet{false};
+ Atomic<bool, Relaxed> mSelfAddrIsSet{false};
+
+ UniquePtr<NetAddr> mBindAddr;
+
+ // socket methods (these can only be called on the socket thread):
+
+ void SendStatus(nsresult status);
+ nsresult ResolveHost();
+ nsresult BuildSocket(PRFileDesc*&, bool&, bool&);
+ nsresult InitiateSocket();
+ bool RecoverFromError();
+
+ void OnMsgInputPending() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mState == STATE_TRANSFERRING) {
+ mPollFlags |= (PR_POLL_READ | PR_POLL_EXCEPT);
+ }
+ }
+ void OnMsgOutputPending() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (mState == STATE_TRANSFERRING) {
+ mPollFlags |= (PR_POLL_WRITE | PR_POLL_EXCEPT);
+ }
+ }
+ void OnMsgInputClosed(nsresult reason);
+ void OnMsgOutputClosed(nsresult reason);
+
+ // called when the socket is connected
+ void OnSocketConnected();
+
+ //-------------------------------------------------------------------------
+ // socket input/output objects. these may be accessed on any thread with
+ // the exception of some specific methods (XXX).
+
+ // protects members in this section.
+ Mutex mLock{"nsSocketTransport.mLock"};
+ LockedPRFileDesc mFD MOZ_GUARDED_BY(mLock);
+ // mFD is closed when mFDref goes to zero.
+ nsrefcnt mFDref MOZ_GUARDED_BY(mLock){0};
+ // mFD is available to consumer when TRUE.
+ bool mFDconnected MOZ_GUARDED_BY(mLock){false};
+
+ // A delete protector reference to gSocketTransportService held for lifetime
+ // of 'this'. Sometimes used interchangably with gSocketTransportService due
+ // to scoping.
+ RefPtr<nsSocketTransportService> mSocketTransportService;
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsITLSSocketControl> mTLSSocketControl;
+
+ UniquePtr<nsSocketInputStream> mInput;
+ UniquePtr<nsSocketOutputStream> mOutput;
+
+ friend class nsSocketInputStream;
+ friend class nsSocketOutputStream;
+
+ // socket timeouts are protected by mLock.
+ uint16_t mTimeouts[2]{0};
+
+ // linger options to use when closing
+ bool mLingerPolarity{false};
+ int16_t mLingerTimeout{0};
+
+ // QoS setting for socket
+ uint8_t mQoSBits{0x00};
+
+ //
+ // mFD access methods: called with mLock held.
+ //
+ PRFileDesc* GetFD_Locked() MOZ_REQUIRES(mLock);
+ void ReleaseFD_Locked(PRFileDesc* fd) MOZ_REQUIRES(mLock);
+
+ //
+ // stream state changes (called outside mLock):
+ //
+ void OnInputClosed(nsresult reason) {
+ // no need to post an event if called on the socket thread
+ if (OnSocketThread()) {
+ OnMsgInputClosed(reason);
+ } else {
+ PostEvent(MSG_INPUT_CLOSED, reason);
+ }
+ }
+ void OnInputPending() {
+ // no need to post an event if called on the socket thread
+ if (OnSocketThread()) {
+ OnMsgInputPending();
+ } else {
+ PostEvent(MSG_INPUT_PENDING);
+ }
+ }
+ void OnOutputClosed(nsresult reason) {
+ // no need to post an event if called on the socket thread
+ if (OnSocketThread()) {
+ OnMsgOutputClosed(reason); // XXX need to not be inside lock!
+ } else {
+ PostEvent(MSG_OUTPUT_CLOSED, reason);
+ }
+ }
+ void OnOutputPending() {
+ // no need to post an event if called on the socket thread
+ if (OnSocketThread()) {
+ OnMsgOutputPending();
+ } else {
+ PostEvent(MSG_OUTPUT_PENDING);
+ }
+ }
+
+#ifdef ENABLE_SOCKET_TRACING
+ void TraceInBuf(const char* buf, int32_t n);
+ void TraceOutBuf(const char* buf, int32_t n);
+#endif
+
+ // Reads prefs to get default keepalive config.
+ nsresult EnsureKeepaliveValsAreInitialized();
+
+ // Groups calls to fd.SetKeepaliveEnabled and fd.SetKeepaliveVals.
+ nsresult SetKeepaliveEnabledInternal(bool aEnable);
+
+ // True if keepalive has been enabled by the socket owner. Note: Keepalive
+ // must also be enabled globally for it to be enabled in TCP.
+ bool mKeepaliveEnabled{false};
+
+ // Keepalive config (support varies by platform).
+ int32_t mKeepaliveIdleTimeS{-1};
+ int32_t mKeepaliveRetryIntervalS{-1};
+ int32_t mKeepaliveProbeCount{-1};
+
+ Atomic<bool> mDoNotRetryToConnect{false};
+
+ // Whether the port remapping has already been applied. We definitely want to
+ // prevent duplicate calls in case of chaining remapping.
+ bool mPortRemappingApplied = false;
+
+ bool mExternalDNSResolution = false;
+ bool mRetryDnsIfPossible = false;
+};
+
+class nsSocketInputStream : public nsIAsyncInputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit nsSocketInputStream(nsSocketTransport*);
+ virtual ~nsSocketInputStream() = default;
+
+ // nsSocketTransport holds a ref to us
+ bool IsReferenced() { return mReaderRefCnt > 0; }
+ nsresult Condition() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mCondition;
+ }
+ uint64_t ByteCount() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mByteCount;
+ }
+ uint64_t ByteCount(MutexAutoLock&) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ return mByteCount;
+ }
+
+ // called by the socket transport on the socket thread...
+ void OnSocketReady(nsresult condition);
+
+ private:
+ nsSocketTransport* mTransport;
+ ThreadSafeAutoRefCnt mReaderRefCnt{0};
+
+ // access to these should be protected by mTransport->mLock but we
+ // can't order things to allow using MOZ_GUARDED_BY().
+ nsresult mCondition MOZ_GUARDED_BY(mTransport->mLock){NS_OK};
+ nsCOMPtr<nsIInputStreamCallback> mCallback MOZ_GUARDED_BY(mTransport->mLock);
+ uint32_t mCallbackFlags MOZ_GUARDED_BY(mTransport->mLock){0};
+ uint64_t mByteCount MOZ_GUARDED_BY(mTransport->mLock){0};
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketOutputStream : public nsIAsyncOutputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit nsSocketOutputStream(nsSocketTransport*);
+ virtual ~nsSocketOutputStream() = default;
+
+ // nsSocketTransport holds a ref to us
+ bool IsReferenced() { return mWriterRefCnt > 0; }
+ nsresult Condition() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mCondition;
+ }
+ uint64_t ByteCount() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mByteCount;
+ }
+ uint64_t ByteCount(MutexAutoLock&) MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ return mByteCount;
+ }
+
+ // called by the socket transport on the socket thread...
+ void OnSocketReady(nsresult condition);
+
+ private:
+ static nsresult WriteFromSegments(nsIInputStream*, void*, const char*,
+ uint32_t offset, uint32_t count,
+ uint32_t* countRead);
+
+ nsSocketTransport* mTransport;
+ ThreadSafeAutoRefCnt mWriterRefCnt{0};
+
+ // access to these should be protected by mTransport->mLock but we
+ // can't order things to allow using MOZ_GUARDED_BY().
+ nsresult mCondition MOZ_GUARDED_BY(mTransport->mLock){NS_OK};
+ nsCOMPtr<nsIOutputStreamCallback> mCallback MOZ_GUARDED_BY(mTransport->mLock);
+ uint32_t mCallbackFlags MOZ_GUARDED_BY(mTransport->mLock){0};
+ uint64_t mByteCount MOZ_GUARDED_BY(mTransport->mLock){0};
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsSocketTransport_h__
diff --git a/netwerk/base/nsSocketTransportService2.cpp b/netwerk/base/nsSocketTransportService2.cpp
new file mode 100644
index 0000000000..a6aa249465
--- /dev/null
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -0,0 +1,1899 @@
+// vim:set sw=2 sts=2 et cin:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransportService2.h"
+
+#include "IOActivityMonitor.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/ProfilerThreadSleep.h"
+#include "mozilla/PublicSSL.h"
+#include "mozilla/ReverseIterator.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/Telemetry.h"
+#include "nsASocketHandler.h"
+#include "nsError.h"
+#include "nsIFile.h"
+#include "nsINetworkLinkService.h"
+#include "nsIOService.h"
+#include "nsIObserverService.h"
+#include "nsIWidget.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransport2.h"
+#include "nsThreadUtils.h"
+#include "prerror.h"
+#include "prnetdb.h"
+
+namespace mozilla {
+namespace net {
+
+#define SOCKET_THREAD_LONGTASK_MS 3
+
+LazyLogModule gSocketTransportLog("nsSocketTransport");
+LazyLogModule gUDPSocketLog("UDPSocket");
+LazyLogModule gTCPSocketLog("TCPSocket");
+
+nsSocketTransportService* gSocketTransportService = nullptr;
+static Atomic<PRThread*, Relaxed> gSocketThread(nullptr);
+
+#define SEND_BUFFER_PREF "network.tcp.sendbuffer"
+#define KEEPALIVE_ENABLED_PREF "network.tcp.keepalive.enabled"
+#define KEEPALIVE_IDLE_TIME_PREF "network.tcp.keepalive.idle_time"
+#define KEEPALIVE_RETRY_INTERVAL_PREF "network.tcp.keepalive.retry_interval"
+#define KEEPALIVE_PROBE_COUNT_PREF "network.tcp.keepalive.probe_count"
+#define SOCKET_LIMIT_TARGET 1000U
+#define MAX_TIME_BETWEEN_TWO_POLLS \
+ "network.sts.max_time_for_events_between_two_polls"
+#define POLL_BUSY_WAIT_PERIOD "network.sts.poll_busy_wait_period"
+#define POLL_BUSY_WAIT_PERIOD_TIMEOUT \
+ "network.sts.poll_busy_wait_period_timeout"
+#define MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN \
+ "network.sts.max_time_for_pr_close_during_shutdown"
+#define POLLABLE_EVENT_TIMEOUT "network.sts.pollable_event_timeout"
+
+#define REPAIR_POLLABLE_EVENT_TIME 10
+
+uint32_t nsSocketTransportService::gMaxCount;
+PRCallOnceType nsSocketTransportService::gMaxCountInitOnce;
+
+// Utility functions
+bool OnSocketThread() { return PR_GetCurrentThread() == gSocketThread; }
+
+//-----------------------------------------------------------------------------
+
+bool nsSocketTransportService::SocketContext::IsTimedOut(
+ PRIntervalTime now) const {
+ return TimeoutIn(now) == 0;
+}
+
+void nsSocketTransportService::SocketContext::EnsureTimeout(
+ PRIntervalTime now) {
+ SOCKET_LOG(("SocketContext::EnsureTimeout socket=%p", mHandler.get()));
+ if (!mPollStartEpoch) {
+ SOCKET_LOG((" engaging"));
+ mPollStartEpoch = now;
+ }
+}
+
+void nsSocketTransportService::SocketContext::DisengageTimeout() {
+ SOCKET_LOG(("SocketContext::DisengageTimeout socket=%p", mHandler.get()));
+ mPollStartEpoch = 0;
+}
+
+PRIntervalTime nsSocketTransportService::SocketContext::TimeoutIn(
+ PRIntervalTime now) const {
+ SOCKET_LOG(("SocketContext::TimeoutIn socket=%p, timeout=%us", mHandler.get(),
+ mHandler->mPollTimeout));
+
+ if (mHandler->mPollTimeout == UINT16_MAX || !mPollStartEpoch) {
+ SOCKET_LOG((" not engaged"));
+ return NS_SOCKET_POLL_TIMEOUT;
+ }
+
+ PRIntervalTime elapsed = (now - mPollStartEpoch);
+ PRIntervalTime timeout = PR_SecondsToInterval(mHandler->mPollTimeout);
+
+ if (elapsed >= timeout) {
+ SOCKET_LOG((" timed out!"));
+ return 0;
+ }
+ SOCKET_LOG((" remains %us", PR_IntervalToSeconds(timeout - elapsed)));
+ return timeout - elapsed;
+}
+
+void nsSocketTransportService::SocketContext::MaybeResetEpoch() {
+ if (mPollStartEpoch && mHandler->mPollTimeout == UINT16_MAX) {
+ mPollStartEpoch = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// ctor/dtor (called on the main/UI thread by the service manager)
+
+nsSocketTransportService::nsSocketTransportService()
+ : mPollableEventTimeout(TimeDuration::FromSeconds(6)),
+ mMaxTimeForPrClosePref(PR_SecondsToInterval(5)),
+ mNetworkLinkChangeBusyWaitPeriod(PR_SecondsToInterval(50)),
+ mNetworkLinkChangeBusyWaitTimeout(PR_SecondsToInterval(7)) {
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+
+ PR_CallOnce(&gMaxCountInitOnce, DiscoverMaxCount);
+
+ NS_ASSERTION(!gSocketTransportService, "must not instantiate twice");
+ gSocketTransportService = this;
+
+ // The Poll list always has an entry at [0]. The rest of the
+ // list is a duplicate of the Active list's PRFileDesc file descriptors.
+ PRPollDesc entry = {nullptr, PR_POLL_READ | PR_POLL_EXCEPT, 0};
+ mPollList.InsertElementAt(0, entry);
+}
+
+void nsSocketTransportService::ApplyPortRemap(uint16_t* aPort) {
+ MOZ_ASSERT(IsOnCurrentThreadInfallible());
+
+ if (!mPortRemapping) {
+ return;
+ }
+
+ // Reverse the array to make later rules override earlier rules.
+ for (auto const& portMapping : Reversed(*mPortRemapping)) {
+ if (*aPort < std::get<0>(portMapping)) {
+ continue;
+ }
+ if (*aPort > std::get<1>(portMapping)) {
+ continue;
+ }
+
+ *aPort = std::get<2>(portMapping);
+ return;
+ }
+}
+
+bool nsSocketTransportService::UpdatePortRemapPreference(
+ nsACString const& aPortMappingPref) {
+ TPortRemapping portRemapping;
+
+ auto consumePreference = [&]() -> bool {
+ Tokenizer tokenizer(aPortMappingPref);
+
+ tokenizer.SkipWhites();
+ if (tokenizer.CheckEOF()) {
+ return true;
+ }
+
+ nsTArray<std::tuple<uint16_t, uint16_t>> ranges(2);
+ while (true) {
+ uint16_t loPort;
+ tokenizer.SkipWhites();
+ if (!tokenizer.ReadInteger(&loPort)) {
+ break;
+ }
+
+ uint16_t hiPort;
+ tokenizer.SkipWhites();
+ if (tokenizer.CheckChar('-')) {
+ tokenizer.SkipWhites();
+ if (!tokenizer.ReadInteger(&hiPort)) {
+ break;
+ }
+ } else {
+ hiPort = loPort;
+ }
+
+ ranges.AppendElement(std::make_tuple(loPort, hiPort));
+
+ tokenizer.SkipWhites();
+ if (tokenizer.CheckChar(',')) {
+ continue; // another port or port range is expected
+ }
+
+ if (tokenizer.CheckChar('=')) {
+ uint16_t targetPort;
+ tokenizer.SkipWhites();
+ if (!tokenizer.ReadInteger(&targetPort)) {
+ break;
+ }
+
+ // Storing reversed, because the most common cases (like 443) will very
+ // likely be listed as first, less common cases will be added to the end
+ // of the list mapping to the same port. As we iterate the whole
+ // remapping array from the end, this may have a small perf win by
+ // hitting the most common cases earlier.
+ for (auto const& range : Reversed(ranges)) {
+ portRemapping.AppendElement(std::make_tuple(
+ std::get<0>(range), std::get<1>(range), targetPort));
+ }
+ ranges.Clear();
+
+ tokenizer.SkipWhites();
+ if (tokenizer.CheckChar(';')) {
+ continue; // more mappings (or EOF) expected
+ }
+ if (tokenizer.CheckEOF()) {
+ return true;
+ }
+ }
+
+ // Anything else is unexpected.
+ break;
+ }
+
+ // 'break' from the parsing loop means ill-formed preference
+ portRemapping.Clear();
+ return false;
+ };
+
+ bool rv = consumePreference();
+
+ if (!IsOnCurrentThread()) {
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ if (!thread) {
+ // Init hasn't been called yet. Could probably just assert.
+ // If shutdown, the dispatch below will just silently fail.
+ NS_ASSERTION(false, "ApplyPortRemapPreference before STS::Init");
+ return false;
+ }
+ thread->Dispatch(NewRunnableMethod<TPortRemapping>(
+ "net::ApplyPortRemapping", this,
+ &nsSocketTransportService::ApplyPortRemapPreference, portRemapping));
+ } else {
+ ApplyPortRemapPreference(portRemapping);
+ }
+
+ return rv;
+}
+
+nsSocketTransportService::~nsSocketTransportService() {
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+ NS_ASSERTION(!mInitialized, "not shutdown properly");
+
+ gSocketTransportService = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// event queue (any thread)
+
+already_AddRefed<nsIThread> nsSocketTransportService::GetThreadSafely() {
+ MutexAutoLock lock(mLock);
+ nsCOMPtr<nsIThread> result = mThread;
+ return result.forget();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DispatchFromScript(nsIRunnable* event,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event_ref(event);
+ return Dispatch(event_ref.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Dispatch(already_AddRefed<nsIRunnable> event,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event_ref(event);
+ SOCKET_LOG(("STS dispatch [%p]\n", event_ref.get()));
+
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ nsresult rv;
+ rv = thread ? thread->Dispatch(event_ref.forget(), flags)
+ : NS_ERROR_NOT_INITIALIZED;
+ if (rv == NS_ERROR_UNEXPECTED) {
+ // Thread is no longer accepting events. We must have just shut it
+ // down on the main thread. Pretend we never saw it.
+ rv = NS_ERROR_NOT_INITIALIZED;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DelayedDispatch(already_AddRefed<nsIRunnable>,
+ uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::RegisterShutdownTask(nsITargetShutdownTask* task) {
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ return thread ? thread->RegisterShutdownTask(task) : NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::UnregisterShutdownTask(nsITargetShutdownTask* task) {
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ return thread ? thread->UnregisterShutdownTask(task) : NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::IsOnCurrentThread(bool* result) {
+ *result = OnSocketThread();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsSocketTransportService::IsOnCurrentThreadInfallible() {
+ return OnSocketThread();
+}
+
+//-----------------------------------------------------------------------------
+// nsIDirectTaskDispatcher
+
+already_AddRefed<nsIDirectTaskDispatcher>
+nsSocketTransportService::GetDirectTaskDispatcherSafely() {
+ MutexAutoLock lock(mLock);
+ nsCOMPtr<nsIDirectTaskDispatcher> result = mDirectTaskDispatcher;
+ return result.forget();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DispatchDirectTask(
+ already_AddRefed<nsIRunnable> aEvent) {
+ nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
+ GetDirectTaskDispatcherSafely();
+ NS_ENSURE_TRUE(dispatcher, NS_ERROR_NOT_INITIALIZED);
+ return dispatcher->DispatchDirectTask(std::move(aEvent));
+}
+
+NS_IMETHODIMP nsSocketTransportService::DrainDirectTasks() {
+ nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
+ GetDirectTaskDispatcherSafely();
+ if (!dispatcher) {
+ // nothing to drain.
+ return NS_OK;
+ }
+ return dispatcher->DrainDirectTasks();
+}
+
+NS_IMETHODIMP nsSocketTransportService::HaveDirectTasks(bool* aValue) {
+ nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
+ GetDirectTaskDispatcherSafely();
+ if (!dispatcher) {
+ *aValue = false;
+ return NS_OK;
+ }
+ return dispatcher->HaveDirectTasks(aValue);
+}
+
+//-----------------------------------------------------------------------------
+// socket api (socket thread only)
+
+NS_IMETHODIMP
+nsSocketTransportService::NotifyWhenCanAttachSocket(nsIRunnable* event) {
+ SOCKET_LOG(("nsSocketTransportService::NotifyWhenCanAttachSocket\n"));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (CanAttachSocket()) {
+ return Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ auto* runnable = new LinkedRunnableEvent(event);
+ mPendingSocketQueue.insertBack(runnable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::AttachSocket(PRFileDesc* fd,
+ nsASocketHandler* handler) {
+ SOCKET_LOG(
+ ("nsSocketTransportService::AttachSocket [handler=%p]\n", handler));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!CanAttachSocket()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SocketContext sock{fd, handler, 0};
+
+ AddToIdleList(&sock);
+ return NS_OK;
+}
+
+// the number of sockets that can be attached at any given time is
+// limited. this is done because some operating systems (e.g., Win9x)
+// limit the number of sockets that can be created by an application.
+// AttachSocket will fail if the limit is exceeded. consumers should
+// call CanAttachSocket and check the result before creating a socket.
+
+bool nsSocketTransportService::CanAttachSocket() {
+ MOZ_ASSERT(!mShuttingDown);
+ uint32_t total = mActiveList.Length() + mIdleList.Length();
+ bool rv = total < gMaxCount;
+
+ MOZ_ASSERT(mInitialized);
+ return rv;
+}
+
+nsresult nsSocketTransportService::DetachSocket(SocketContextList& listHead,
+ SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::DetachSocket [handler=%p]\n",
+ sock->mHandler.get()));
+ MOZ_ASSERT((&listHead == &mActiveList) || (&listHead == &mIdleList),
+ "DetachSocket invalid head");
+
+ {
+ // inform the handler that this socket is going away
+ sock->mHandler->OnSocketDetached(sock->mFD);
+ }
+ mSentBytesCount += sock->mHandler->ByteCountSent();
+ mReceivedBytesCount += sock->mHandler->ByteCountReceived();
+
+ // cleanup
+ sock->mFD = nullptr;
+
+ if (&listHead == &mActiveList) {
+ RemoveFromPollList(sock);
+ } else {
+ RemoveFromIdleList(sock);
+ }
+
+ // NOTE: sock is now an invalid pointer
+
+ //
+ // notify the first element on the pending socket queue...
+ //
+ nsCOMPtr<nsIRunnable> event;
+ LinkedRunnableEvent* runnable = mPendingSocketQueue.getFirst();
+ if (runnable) {
+ event = runnable->TakeEvent();
+ runnable->remove();
+ delete runnable;
+ }
+ if (event) {
+ // move event from pending queue to dispatch queue
+ return Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+// Returns the index of a SocketContext within a list, or -1 if it's
+// not a pointer to a list element
+// NOTE: this could be supplied by nsTArray<>
+int64_t nsSocketTransportService::SockIndex(SocketContextList& aList,
+ SocketContext* aSock) {
+ ptrdiff_t index = -1;
+ if (!aList.IsEmpty()) {
+ index = aSock - &aList[0];
+ if (index < 0 || (size_t)index + 1 > aList.Length()) {
+ index = -1;
+ }
+ }
+ return (int64_t)index;
+}
+
+void nsSocketTransportService::AddToPollList(SocketContext* sock) {
+ MOZ_ASSERT(SockIndex(mActiveList, sock) == -1,
+ "AddToPollList Socket Already Active");
+
+ SOCKET_LOG(("nsSocketTransportService::AddToPollList %p [handler=%p]\n", sock,
+ sock->mHandler.get()));
+
+ sock->EnsureTimeout(PR_IntervalNow());
+ PRPollDesc poll;
+ poll.fd = sock->mFD;
+ poll.in_flags = sock->mHandler->mPollFlags;
+ poll.out_flags = 0;
+ if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) {
+ auto newSocketIndex = mActiveList.Length();
+ newSocketIndex = ChaosMode::randomUint32LessThan(newSocketIndex + 1);
+ mActiveList.InsertElementAt(newSocketIndex, *sock);
+ // mPollList is offset by 1
+ mPollList.InsertElementAt(newSocketIndex + 1, poll);
+ } else {
+ // Avoid refcount bump/decrease
+ mActiveList.EmplaceBack(sock->mFD, sock->mHandler.forget(),
+ sock->mPollStartEpoch);
+ mPollList.AppendElement(poll);
+ }
+
+ SOCKET_LOG(
+ (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
+}
+
+void nsSocketTransportService::RemoveFromPollList(SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::RemoveFromPollList %p [handler=%p]\n",
+ sock, sock->mHandler.get()));
+
+ auto index = SockIndex(mActiveList, sock);
+ MOZ_RELEASE_ASSERT(index != -1, "invalid index");
+
+ SOCKET_LOG((" index=%" PRId64 " mActiveList.Length()=%zu\n", index,
+ mActiveList.Length()));
+ mActiveList.UnorderedRemoveElementAt(index);
+ // mPollList is offset by 1
+ mPollList.UnorderedRemoveElementAt(index + 1);
+
+ SOCKET_LOG(
+ (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
+}
+
+void nsSocketTransportService::AddToIdleList(SocketContext* sock) {
+ MOZ_ASSERT(SockIndex(mIdleList, sock) == -1,
+ "AddToIdleList Socket Already Idle");
+
+ SOCKET_LOG(("nsSocketTransportService::AddToIdleList %p [handler=%p]\n", sock,
+ sock->mHandler.get()));
+
+ // Avoid refcount bump/decrease
+ mIdleList.EmplaceBack(sock->mFD, sock->mHandler.forget(),
+ sock->mPollStartEpoch);
+
+ SOCKET_LOG(
+ (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
+}
+
+void nsSocketTransportService::RemoveFromIdleList(SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%p]\n",
+ sock->mHandler.get()));
+ auto index = SockIndex(mIdleList, sock);
+ MOZ_RELEASE_ASSERT(index != -1);
+ mIdleList.UnorderedRemoveElementAt(index);
+
+ SOCKET_LOG(
+ (" active=%zu idle=%zu\n", mActiveList.Length(), mIdleList.Length()));
+}
+
+void nsSocketTransportService::MoveToIdleList(SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::MoveToIdleList %p [handler=%p]\n",
+ sock, sock->mHandler.get()));
+ MOZ_ASSERT(SockIndex(mIdleList, sock) == -1);
+ MOZ_ASSERT(SockIndex(mActiveList, sock) != -1);
+ AddToIdleList(sock);
+ RemoveFromPollList(sock);
+}
+
+void nsSocketTransportService::MoveToPollList(SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::MoveToPollList %p [handler=%p]\n",
+ sock, sock->mHandler.get()));
+ MOZ_ASSERT(SockIndex(mIdleList, sock) != -1);
+ MOZ_ASSERT(SockIndex(mActiveList, sock) == -1);
+ AddToPollList(sock);
+ RemoveFromIdleList(sock);
+}
+
+void nsSocketTransportService::ApplyPortRemapPreference(
+ TPortRemapping const& portRemapping) {
+ MOZ_ASSERT(IsOnCurrentThreadInfallible());
+
+ mPortRemapping.reset();
+ if (!portRemapping.IsEmpty()) {
+ mPortRemapping.emplace(portRemapping);
+ }
+}
+
+PRIntervalTime nsSocketTransportService::PollTimeout(PRIntervalTime now) {
+ if (mActiveList.IsEmpty()) {
+ return NS_SOCKET_POLL_TIMEOUT;
+ }
+
+ // compute minimum time before any socket timeout expires.
+ PRIntervalTime minR = NS_SOCKET_POLL_TIMEOUT;
+ for (uint32_t i = 0; i < mActiveList.Length(); ++i) {
+ const SocketContext& s = mActiveList[i];
+ PRIntervalTime r = s.TimeoutIn(now);
+ if (r < minR) {
+ minR = r;
+ }
+ }
+ if (minR == NS_SOCKET_POLL_TIMEOUT) {
+ SOCKET_LOG(("poll timeout: none\n"));
+ return NS_SOCKET_POLL_TIMEOUT;
+ }
+ SOCKET_LOG(("poll timeout: %" PRIu32 "\n", PR_IntervalToSeconds(minR)));
+ return minR;
+}
+
+int32_t nsSocketTransportService::Poll(TimeDuration* pollDuration,
+ PRIntervalTime ts) {
+ MOZ_ASSERT(IsOnCurrentThread());
+ PRPollDesc* firstPollEntry;
+ uint32_t pollCount;
+ PRIntervalTime pollTimeout;
+ *pollDuration = nullptr;
+
+ // If there are pending events for this thread then
+ // DoPollIteration() should service the network without blocking.
+ bool pendingEvents = false;
+ mRawThread->HasPendingEvents(&pendingEvents);
+
+ if (mPollList[0].fd) {
+ mPollList[0].out_flags = 0;
+ firstPollEntry = &mPollList[0];
+ pollCount = mPollList.Length();
+ pollTimeout = pendingEvents ? PR_INTERVAL_NO_WAIT : PollTimeout(ts);
+ } else {
+ // no pollable event, so busy wait...
+ pollCount = mActiveList.Length();
+ if (pollCount) {
+ firstPollEntry = &mPollList[1];
+ } else {
+ firstPollEntry = nullptr;
+ }
+ pollTimeout =
+ pendingEvents ? PR_INTERVAL_NO_WAIT : PR_MillisecondsToInterval(25);
+ }
+
+ if ((ts - mLastNetworkLinkChangeTime) < mNetworkLinkChangeBusyWaitPeriod) {
+ // Being here means we are few seconds after a network change has
+ // been detected.
+ PRIntervalTime to = mNetworkLinkChangeBusyWaitTimeout;
+ if (to) {
+ pollTimeout = std::min(to, pollTimeout);
+ SOCKET_LOG((" timeout shorthened after network change event"));
+ }
+ }
+
+ TimeStamp pollStart;
+ if (Telemetry::CanRecordPrereleaseData()) {
+ pollStart = TimeStamp::NowLoRes();
+ }
+
+ SOCKET_LOG((" timeout = %i milliseconds\n",
+ PR_IntervalToMilliseconds(pollTimeout)));
+
+ int32_t n;
+ {
+#ifdef MOZ_GECKO_PROFILER
+ TimeStamp startTime = TimeStamp::Now();
+ if (pollTimeout != PR_INTERVAL_NO_WAIT) {
+ // There will be an actual non-zero wait, let the profiler know about it
+ // by marking thread as sleeping around the polling call.
+ profiler_thread_sleep();
+ }
+#endif
+
+ n = PR_Poll(firstPollEntry, pollCount, pollTimeout);
+
+#ifdef MOZ_GECKO_PROFILER
+ if (pollTimeout != PR_INTERVAL_NO_WAIT) {
+ profiler_thread_wake();
+ }
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ PROFILER_MARKER_TEXT(
+ "SocketTransportService::Poll", NETWORK,
+ MarkerTiming::IntervalUntilNowFrom(startTime),
+ pollTimeout == PR_INTERVAL_NO_TIMEOUT
+ ? nsPrintfCString("Poll count: %u, Poll timeout: NO_TIMEOUT",
+ pollCount)
+ : pollTimeout == PR_INTERVAL_NO_WAIT
+ ? nsPrintfCString("Poll count: %u, Poll timeout: NO_WAIT",
+ pollCount)
+ : nsPrintfCString("Poll count: %u, Poll timeout: %ums", pollCount,
+ PR_IntervalToMilliseconds(pollTimeout)));
+ }
+#endif
+ }
+
+ if (Telemetry::CanRecordPrereleaseData() && !pollStart.IsNull()) {
+ *pollDuration = TimeStamp::NowLoRes() - pollStart;
+ }
+
+ SOCKET_LOG((" ...returned after %i milliseconds\n",
+ PR_IntervalToMilliseconds(PR_IntervalNow() - ts)));
+
+ return n;
+}
+
+//-----------------------------------------------------------------------------
+// xpcom api
+
+NS_IMPL_ISUPPORTS(nsSocketTransportService, nsISocketTransportService,
+ nsIRoutedSocketTransportService, nsIEventTarget,
+ nsISerialEventTarget, nsIThreadObserver, nsIRunnable,
+ nsPISocketTransportService, nsIObserver, nsINamed,
+ nsIDirectTaskDispatcher)
+
+static const char* gCallbackPrefs[] = {
+ SEND_BUFFER_PREF,
+ KEEPALIVE_ENABLED_PREF,
+ KEEPALIVE_IDLE_TIME_PREF,
+ KEEPALIVE_RETRY_INTERVAL_PREF,
+ KEEPALIVE_PROBE_COUNT_PREF,
+ MAX_TIME_BETWEEN_TWO_POLLS,
+ MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN,
+ POLLABLE_EVENT_TIMEOUT,
+ "network.socket.forcePort",
+ nullptr,
+};
+
+/* static */
+void nsSocketTransportService::UpdatePrefs(const char* aPref, void* aSelf) {
+ static_cast<nsSocketTransportService*>(aSelf)->UpdatePrefs();
+}
+
+static uint32_t GetThreadStackSize() {
+#ifdef XP_WIN
+ if (!StaticPrefs::network_allow_large_stack_size_for_socket_thread()) {
+ return nsIThreadManager::DEFAULT_STACK_SIZE;
+ }
+
+ const uint32_t kWindowsThreadStackSize = 512 * 1024;
+ // We can remove this custom stack size when DEFAULT_STACK_SIZE is increased.
+ static_assert(kWindowsThreadStackSize > nsIThreadManager::DEFAULT_STACK_SIZE);
+ return kWindowsThreadStackSize;
+#else
+ return nsIThreadManager::DEFAULT_STACK_SIZE;
+#endif
+}
+
+// called from main thread only
+NS_IMETHODIMP
+nsSocketTransportService::Init() {
+ if (!NS_IsMainThread()) {
+ NS_ERROR("wrong thread");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mInitialized) {
+ return NS_OK;
+ }
+
+ if (mShuttingDown) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+
+ if (!XRE_IsContentProcess() ||
+ StaticPrefs::network_allow_raw_sockets_in_content_processes_AtStartup()) {
+ // Since we Poll, we can't use normal LongTask support in Main Process
+ nsresult rv = NS_NewNamedThread(
+ "Socket Thread", getter_AddRefs(thread), this,
+ {GetThreadStackSize(), false, false, Some(SOCKET_THREAD_LONGTASK_MS)});
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // In the child process, we just want a regular nsThread with no socket
+ // polling. So we don't want to run the nsSocketTransportService runnable on
+ // it.
+ nsresult rv =
+ NS_NewNamedThread("Socket Thread", getter_AddRefs(thread), nullptr,
+ {nsIThreadManager::DEFAULT_STACK_SIZE, false, false,
+ Some(SOCKET_THREAD_LONGTASK_MS)});
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set up some of the state that nsSocketTransportService::Run would set.
+ PRThread* prthread = nullptr;
+ thread->GetPRThread(&prthread);
+ gSocketThread = prthread;
+ mRawThread = thread;
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ // Install our mThread, protecting against concurrent readers
+ thread.swap(mThread);
+ mDirectTaskDispatcher = do_QueryInterface(mThread);
+ MOZ_DIAGNOSTIC_ASSERT(
+ mDirectTaskDispatcher,
+ "Underlying thread must support direct task dispatching");
+ }
+
+ Preferences::RegisterCallbacks(UpdatePrefs, gCallbackPrefs, this);
+ UpdatePrefs();
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ // Note that the observr notifications are forwarded from parent process to
+ // socket process. We have to make sure the topics registered below are also
+ // registered in nsIObserver::Init().
+ if (obsSvc) {
+ obsSvc->AddObserver(this, "profile-initial-state", false);
+ obsSvc->AddObserver(this, "last-pb-context-exited", false);
+ obsSvc->AddObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC, true);
+ obsSvc->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
+ obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
+ obsSvc->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ }
+
+ // We can now dispatch tasks to the socket thread.
+ mInitialized = true;
+ return NS_OK;
+}
+
+// called from main thread only
+NS_IMETHODIMP
+nsSocketTransportService::Shutdown(bool aXpcomShutdown) {
+ SOCKET_LOG(("nsSocketTransportService::Shutdown\n"));
+
+ NS_ENSURE_STATE(NS_IsMainThread());
+
+ if (!mInitialized || mShuttingDown) {
+ // We never inited, or shutdown has already started
+ return NS_OK;
+ }
+
+ {
+ auto observersCopy = mShutdownObservers;
+ for (auto& observer : observersCopy) {
+ observer->Observe();
+ }
+ }
+
+ mShuttingDown = true;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+ }
+
+ // If we're shutting down due to going offline (rather than due to XPCOM
+ // shutdown), also tear down the thread. The thread will be shutdown during
+ // xpcom-shutdown-threads if during xpcom-shutdown proper.
+ if (!aXpcomShutdown) {
+ ShutdownThread();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSocketTransportService::ShutdownThread() {
+ SOCKET_LOG(("nsSocketTransportService::ShutdownThread\n"));
+
+ NS_ENSURE_STATE(NS_IsMainThread());
+
+ if (!mInitialized) {
+ return NS_OK;
+ }
+
+ // join with thread
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ thread->Shutdown();
+ {
+ MutexAutoLock lock(mLock);
+ // Drop our reference to mThread and make sure that any concurrent readers
+ // are excluded
+ mThread = nullptr;
+ mDirectTaskDispatcher = nullptr;
+ }
+
+ Preferences::UnregisterCallbacks(UpdatePrefs, gCallbackPrefs, this);
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(this, "profile-initial-state");
+ obsSvc->RemoveObserver(this, "last-pb-context-exited");
+ obsSvc->RemoveObserver(this, NS_WIDGET_SLEEP_OBSERVER_TOPIC);
+ obsSvc->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
+ obsSvc->RemoveObserver(this, "xpcom-shutdown-threads");
+ obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ }
+
+ if (mAfterWakeUpTimer) {
+ mAfterWakeUpTimer->Cancel();
+ mAfterWakeUpTimer = nullptr;
+ }
+
+ IOActivityMonitor::Shutdown();
+
+ mInitialized = false;
+ mShuttingDown = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetOffline(bool* offline) {
+ MutexAutoLock lock(mLock);
+ *offline = mOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::SetOffline(bool offline) {
+ MutexAutoLock lock(mLock);
+ if (!mOffline && offline) {
+ // signal the socket thread to go offline, so it will detach sockets
+ mGoingOffline = true;
+ mOffline = true;
+ } else if (mOffline && !offline) {
+ mOffline = false;
+ }
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveIdleTime(int32_t* aKeepaliveIdleTimeS) {
+ MOZ_ASSERT(aKeepaliveIdleTimeS);
+ if (NS_WARN_IF(!aKeepaliveIdleTimeS)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveIdleTimeS = mKeepaliveIdleTimeS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveRetryInterval(
+ int32_t* aKeepaliveRetryIntervalS) {
+ MOZ_ASSERT(aKeepaliveRetryIntervalS);
+ if (NS_WARN_IF(!aKeepaliveRetryIntervalS)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveRetryIntervalS = mKeepaliveRetryIntervalS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetKeepaliveProbeCount(
+ int32_t* aKeepaliveProbeCount) {
+ MOZ_ASSERT(aKeepaliveProbeCount);
+ if (NS_WARN_IF(!aKeepaliveProbeCount)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aKeepaliveProbeCount = mKeepaliveProbeCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateTransport(const nsTArray<nsCString>& types,
+ const nsACString& host, int32_t port,
+ nsIProxyInfo* proxyInfo,
+ nsIDNSRecord* dnsRecord,
+ nsISocketTransport** result) {
+ return CreateRoutedTransport(types, host, port, ""_ns, 0, proxyInfo,
+ dnsRecord, result);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateRoutedTransport(
+ const nsTArray<nsCString>& types, const nsACString& host, int32_t port,
+ const nsACString& hostRoute, int32_t portRoute, nsIProxyInfo* proxyInfo,
+ nsIDNSRecord* dnsRecord, nsISocketTransport** result) {
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(port >= 0 && port <= 0xFFFF, NS_ERROR_ILLEGAL_VALUE);
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport();
+ nsresult rv = trans->Init(types, host, port, hostRoute, portRoute, proxyInfo,
+ dnsRecord);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ trans.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateUnixDomainTransport(
+ nsIFile* aPath, nsISocketTransport** result) {
+#ifdef XP_UNIX
+ nsresult rv;
+
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ nsAutoCString path;
+ rv = aPath->GetNativePath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport();
+
+ rv = trans->InitWithFilename(path.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ trans.forget(result);
+ return NS_OK;
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateUnixDomainAbstractAddressTransport(
+ const nsACString& aName, nsISocketTransport** result) {
+ // Abstract socket address is supported on Linux only
+#ifdef XP_LINUX
+ RefPtr<nsSocketTransport> trans = new nsSocketTransport();
+ // First character of Abstract socket address is null
+ UniquePtr<char[]> name(new char[aName.Length() + 1]);
+ *(name.get()) = 0;
+ memcpy(name.get() + 1, aName.BeginReading(), aName.Length());
+ nsresult rv = trans->InitWithName(name.get(), aName.Length() + 1);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ trans.forget(result);
+ return NS_OK;
+#else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+#endif
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::OnDispatchedEvent() {
+#ifndef XP_WIN
+ // On windows poll can hang and this became worse when we introduced the
+ // patch for bug 698882 (see also bug 1292181), therefore we reverted the
+ // behavior on windows to be as before bug 698882, e.g. write to the socket
+ // also if an event dispatch is on the socket thread and writing to the
+ // socket for each event.
+ if (OnSocketThread()) {
+ // this check is redundant to one done inside ::Signal(), but
+ // we can do it here and skip obtaining the lock - given that
+ // this is a relatively common occurance its worth the
+ // redundant code
+ SOCKET_LOG(("OnDispatchedEvent Same Thread Skip Signal\n"));
+ return NS_OK;
+ }
+#else
+ if (gIOService->IsNetTearingDown()) {
+ // Poll can hang sometimes. If we are in shutdown, we are going to
+ // start a watchdog. If we do not exit poll within
+ // REPAIR_POLLABLE_EVENT_TIME signal a pollable event again.
+ StartPollWatchdog();
+ }
+#endif
+
+ MutexAutoLock lock(mLock);
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::OnProcessNextEvent(nsIThreadInternal* thread,
+ bool mayWait) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed) {
+ return NS_OK;
+}
+
+void nsSocketTransportService::MarkTheLastElementOfPendingQueue() {
+ mServingPendingQueue = false;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Run() {
+ SOCKET_LOG(("STS thread init %d sockets\n", gMaxCount));
+
+#if defined(XP_WIN)
+ // see bug 1361495, gethostname() triggers winsock initialization.
+ // so do it here (on parent and child) to protect against it being done first
+ // accidentally on the main thread.. especially via PR_GetSystemInfo(). This
+ // will also improve latency of first real winsock operation
+ // ..
+ // If STS-thread is no longer needed this should still be run before exiting
+
+ char ignoredStackBuffer[255];
+ Unused << gethostname(ignoredStackBuffer, 255);
+#endif
+
+ psm::InitializeSSLServerCertVerificationThreads();
+
+ gSocketThread = PR_GetCurrentThread();
+
+ {
+ // See bug 1843384:
+ // Avoid blocking the main thread by allocating the PollableEvent outside
+ // the mutex. Still has the potential to hang the socket thread, but the
+ // main thread remains responsive.
+ PollableEvent* pollable = new PollableEvent();
+ MutexAutoLock lock(mLock);
+ mPollableEvent.reset(pollable);
+
+ //
+ // NOTE: per bug 190000, this failure could be caused by Zone-Alarm
+ // or similar software.
+ //
+ // NOTE: per bug 191739, this failure could also be caused by lack
+ // of a loopback device on Windows and OS/2 platforms (it creates
+ // a loopback socket pair on these platforms to implement a pollable
+ // event object). if we can't create a pollable event, then we'll
+ // have to "busy wait" to implement the socket event queue :-(
+ //
+ if (!mPollableEvent->Valid()) {
+ mPollableEvent = nullptr;
+ NS_WARNING("running socket transport thread without a pollable event");
+ SOCKET_LOG(("running socket transport thread without a pollable event"));
+ }
+
+ PRPollDesc entry = {mPollableEvent ? mPollableEvent->PollableFD() : nullptr,
+ PR_POLL_READ | PR_POLL_EXCEPT, 0};
+ SOCKET_LOG(("Setting entry 0"));
+ mPollList[0] = entry;
+ }
+
+ mRawThread = NS_GetCurrentThread();
+
+ // Ensure a call to GetCurrentSerialEventTarget() returns this event target.
+ SerialEventTargetGuard guard(this);
+
+ // hook ourselves up to observe event processing for this thread
+ nsCOMPtr<nsIThreadInternal> threadInt = do_QueryInterface(mRawThread);
+ threadInt->SetObserver(this);
+
+ // make sure the pseudo random number generator is seeded on this thread
+ srand(static_cast<unsigned>(PR_Now()));
+
+ // For the calculation of the duration of the last cycle (i.e. the last
+ // for-loop iteration before shutdown).
+ TimeStamp startOfCycleForLastCycleCalc;
+
+ // For measuring of the poll iteration duration without time spent blocked
+ // in poll().
+ TimeStamp pollCycleStart;
+ // Time blocked in poll().
+ TimeDuration singlePollDuration;
+
+ // For calculating the time needed for a new element to run.
+ TimeStamp startOfIteration;
+ TimeStamp startOfNextIteration;
+
+ // If there is too many pending events queued, we will run some poll()
+ // between them and the following variable is cumulative time spent
+ // blocking in poll().
+ TimeDuration pollDuration;
+
+ for (;;) {
+ bool pendingEvents = false;
+ if (Telemetry::CanRecordPrereleaseData()) {
+ startOfCycleForLastCycleCalc = TimeStamp::NowLoRes();
+ startOfNextIteration = TimeStamp::NowLoRes();
+ }
+ pollDuration = nullptr;
+ // We pop out to this loop when there are no pending events.
+ // If we don't reset these, we may not re-enter ProcessNextEvent()
+ // until we have events to process, and it may seem like we have
+ // an event running for a very long time.
+ mRawThread->SetRunningEventDelay(TimeDuration(), TimeStamp());
+
+ do {
+ if (Telemetry::CanRecordPrereleaseData()) {
+ pollCycleStart = TimeStamp::NowLoRes();
+ }
+
+ DoPollIteration(&singlePollDuration);
+
+ if (Telemetry::CanRecordPrereleaseData() && !pollCycleStart.IsNull()) {
+ Telemetry::Accumulate(Telemetry::STS_POLL_BLOCK_TIME,
+ singlePollDuration.ToMilliseconds());
+ Telemetry::AccumulateTimeDelta(Telemetry::STS_POLL_CYCLE,
+ pollCycleStart + singlePollDuration,
+ TimeStamp::NowLoRes());
+ pollDuration += singlePollDuration;
+ }
+
+ mRawThread->HasPendingEvents(&pendingEvents);
+ if (pendingEvents) {
+ if (!mServingPendingQueue) {
+ nsresult rv = Dispatch(
+ NewRunnableMethod(
+ "net::nsSocketTransportService::"
+ "MarkTheLastElementOfPendingQueue",
+ this,
+ &nsSocketTransportService::MarkTheLastElementOfPendingQueue),
+ nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "Could not dispatch a new event on the "
+ "socket thread.");
+ } else {
+ mServingPendingQueue = true;
+ }
+
+ if (Telemetry::CanRecordPrereleaseData()) {
+ startOfIteration = startOfNextIteration;
+ // Everything that comes after this point will
+ // be served in the next iteration. If no even
+ // arrives, startOfNextIteration will be reset at the
+ // beginning of each for-loop.
+ startOfNextIteration = TimeStamp::NowLoRes();
+ }
+ }
+ TimeStamp eventQueueStart = TimeStamp::NowLoRes();
+ do {
+ NS_ProcessNextEvent(mRawThread);
+ pendingEvents = false;
+ mRawThread->HasPendingEvents(&pendingEvents);
+ } while (pendingEvents && mServingPendingQueue &&
+ ((TimeStamp::NowLoRes() - eventQueueStart).ToMilliseconds() <
+ mMaxTimePerPollIter));
+
+ if (Telemetry::CanRecordPrereleaseData() && !mServingPendingQueue &&
+ !startOfIteration.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::STS_POLL_AND_EVENTS_CYCLE,
+ startOfIteration + pollDuration,
+ TimeStamp::NowLoRes());
+ pollDuration = nullptr;
+ }
+ }
+ } while (pendingEvents);
+
+ bool goingOffline = false;
+ // now that our event queue is empty, check to see if we should exit
+ if (mShuttingDown) {
+ if (Telemetry::CanRecordPrereleaseData() &&
+ !startOfCycleForLastCycleCalc.IsNull()) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::STS_POLL_AND_EVENT_THE_LAST_CYCLE,
+ startOfCycleForLastCycleCalc, TimeStamp::NowLoRes());
+ }
+ break;
+ }
+ {
+ MutexAutoLock lock(mLock);
+ if (mGoingOffline) {
+ mGoingOffline = false;
+ goingOffline = true;
+ }
+ }
+ // Avoid potential deadlock
+ if (goingOffline) {
+ Reset(true);
+ }
+ }
+
+ SOCKET_LOG(("STS shutting down thread\n"));
+
+ // detach all sockets, including locals
+ Reset(false);
+
+ // We don't clear gSocketThread so that OnSocketThread() won't be a false
+ // alarm for events generated by stopping the SSL threads during shutdown.
+ psm::StopSSLServerCertVerificationThreads();
+
+ // Final pass over the event queue. This makes sure that events posted by
+ // socket detach handlers get processed.
+ NS_ProcessPendingEvents(mRawThread);
+
+ SOCKET_LOG(("STS thread exit\n"));
+ MOZ_ASSERT(mPollList.Length() == 1);
+ MOZ_ASSERT(mActiveList.IsEmpty());
+ MOZ_ASSERT(mIdleList.IsEmpty());
+
+ return NS_OK;
+}
+
+void nsSocketTransportService::DetachSocketWithGuard(
+ bool aGuardLocals, SocketContextList& socketList, int32_t index) {
+ bool isGuarded = false;
+ if (aGuardLocals) {
+ socketList[index].mHandler->IsLocal(&isGuarded);
+ if (!isGuarded) {
+ socketList[index].mHandler->KeepWhenOffline(&isGuarded);
+ }
+ }
+ if (!isGuarded) {
+ DetachSocket(socketList, &socketList[index]);
+ }
+}
+
+void nsSocketTransportService::Reset(bool aGuardLocals) {
+ // detach any sockets
+ int32_t i;
+ for (i = mActiveList.Length() - 1; i >= 0; --i) {
+ DetachSocketWithGuard(aGuardLocals, mActiveList, i);
+ }
+ for (i = mIdleList.Length() - 1; i >= 0; --i) {
+ DetachSocketWithGuard(aGuardLocals, mIdleList, i);
+ }
+}
+
+nsresult nsSocketTransportService::DoPollIteration(TimeDuration* pollDuration) {
+ SOCKET_LOG(("STS poll iter\n"));
+
+ PRIntervalTime now = PR_IntervalNow();
+
+ // We can't have more than int32_max sockets in use
+ int32_t i, count;
+ //
+ // poll loop
+ //
+ // walk active list backwards to see if any sockets should actually be
+ // idle, then walk the idle list backwards to see if any idle sockets
+ // should become active. take care to check only idle sockets that
+ // were idle to begin with ;-)
+ //
+ count = mIdleList.Length();
+ for (i = mActiveList.Length() - 1; i >= 0; --i) {
+ //---
+ SOCKET_LOG((" active [%u] { handler=%p condition=%" PRIx32
+ " pollflags=%hu }\n",
+ i, mActiveList[i].mHandler.get(),
+ static_cast<uint32_t>(mActiveList[i].mHandler->mCondition),
+ mActiveList[i].mHandler->mPollFlags));
+ //---
+ if (NS_FAILED(mActiveList[i].mHandler->mCondition)) {
+ DetachSocket(mActiveList, &mActiveList[i]);
+ } else {
+ uint16_t in_flags = mActiveList[i].mHandler->mPollFlags;
+ if (in_flags == 0) {
+ MoveToIdleList(&mActiveList[i]);
+ } else {
+ // update poll flags
+ mPollList[i + 1].in_flags = in_flags;
+ mPollList[i + 1].out_flags = 0;
+ mActiveList[i].EnsureTimeout(now);
+ }
+ }
+ }
+ for (i = count - 1; i >= 0; --i) {
+ //---
+ SOCKET_LOG((" idle [%u] { handler=%p condition=%" PRIx32
+ " pollflags=%hu }\n",
+ i, mIdleList[i].mHandler.get(),
+ static_cast<uint32_t>(mIdleList[i].mHandler->mCondition),
+ mIdleList[i].mHandler->mPollFlags));
+ //---
+ if (NS_FAILED(mIdleList[i].mHandler->mCondition)) {
+ DetachSocket(mIdleList, &mIdleList[i]);
+ } else if (mIdleList[i].mHandler->mPollFlags != 0) {
+ MoveToPollList(&mIdleList[i]);
+ }
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ if (mPollableEvent) {
+ // we want to make sure the timeout is measured from the time
+ // we enter poll(). This method resets the timestamp to 'now',
+ // if we were first signalled between leaving poll() and here.
+ // If we didn't do this and processing events took longer than
+ // the allowed signal timeout, we would detect it as a
+ // false-positive. AdjustFirstSignalTimestamp is then a no-op
+ // until mPollableEvent->Clear() is called.
+ mPollableEvent->AdjustFirstSignalTimestamp();
+ }
+ }
+
+ SOCKET_LOG((" calling PR_Poll [active=%zu idle=%zu]\n", mActiveList.Length(),
+ mIdleList.Length()));
+
+ // Measures seconds spent while blocked on PR_Poll
+ int32_t n = 0;
+ *pollDuration = nullptr;
+
+ if (!gIOService->IsNetTearingDown()) {
+ // Let's not do polling during shutdown.
+#if defined(XP_WIN)
+ StartPolling();
+#endif
+ n = Poll(pollDuration, now);
+#if defined(XP_WIN)
+ EndPolling();
+#endif
+ }
+
+ now = PR_IntervalNow();
+#ifdef MOZ_GECKO_PROFILER
+ TimeStamp startTime;
+ bool profiling = profiler_thread_is_being_profiled_for_markers();
+ if (profiling) {
+ startTime = TimeStamp::Now();
+ }
+#endif
+
+ if (n < 0) {
+ SOCKET_LOG((" PR_Poll error [%d] os error [%d]\n", PR_GetError(),
+ PR_GetOSError()));
+ } else {
+ //
+ // service "active" sockets...
+ //
+ for (i = 0; i < int32_t(mActiveList.Length()); ++i) {
+ PRPollDesc& desc = mPollList[i + 1];
+ SocketContext& s = mActiveList[i];
+ if (n > 0 && desc.out_flags != 0) {
+ s.DisengageTimeout();
+ s.mHandler->OnSocketReady(desc.fd, desc.out_flags);
+ } else if (s.IsTimedOut(now)) {
+ SOCKET_LOG(("socket %p timed out", s.mHandler.get()));
+ s.DisengageTimeout();
+ s.mHandler->OnSocketReady(desc.fd, -1);
+ } else {
+ s.MaybeResetEpoch();
+ }
+ }
+ //
+ // check for "dead" sockets and remove them (need to do this in
+ // reverse order obviously).
+ //
+ for (i = mActiveList.Length() - 1; i >= 0; --i) {
+ if (NS_FAILED(mActiveList[i].mHandler->mCondition)) {
+ DetachSocket(mActiveList, &mActiveList[i]);
+ }
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ // acknowledge pollable event (should not block)
+ if (n != 0 &&
+ (mPollList[0].out_flags & (PR_POLL_READ | PR_POLL_EXCEPT)) &&
+ mPollableEvent &&
+ ((mPollList[0].out_flags & PR_POLL_EXCEPT) ||
+ !mPollableEvent->Clear())) {
+ // On Windows, the TCP loopback connection in the
+ // pollable event may become broken when a laptop
+ // switches between wired and wireless networks or
+ // wakes up from hibernation. We try to create a
+ // new pollable event. If that fails, we fall back
+ // on "busy wait".
+ TryRepairPollableEvent();
+ }
+
+ if (mPollableEvent &&
+ !mPollableEvent->IsSignallingAlive(mPollableEventTimeout)) {
+ SOCKET_LOG(("Pollable event signalling failed/timed out"));
+ TryRepairPollableEvent();
+ }
+ }
+ }
+#ifdef MOZ_GECKO_PROFILER
+ if (profiling) {
+ TimeStamp endTime = TimeStamp::Now();
+ if ((endTime - startTime).ToMilliseconds() >= SOCKET_THREAD_LONGTASK_MS) {
+ struct LongTaskMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("SocketThreadLongTask");
+ }
+ static void StreamJSONMarkerData(
+ baseprofiler::SpliceableJSONWriter& aWriter) {
+ aWriter.StringProperty("category", "LongTask");
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
+ schema.AddKeyLabelFormatSearchable("category", "Type",
+ MS::Format::String,
+ MS::Searchable::Searchable);
+ return schema;
+ }
+ };
+
+ profiler_add_marker(ProfilerString8View("LongTaskSocketProcessing"),
+ geckoprofiler::category::OTHER,
+ MarkerTiming::Interval(startTime, endTime),
+ LongTaskMarker{});
+ }
+ }
+
+#endif
+
+ return NS_OK;
+}
+
+void nsSocketTransportService::UpdateSendBufferPref() {
+ int32_t bufferSize;
+
+ // If the pref is set, honor it. 0 means use OS defaults.
+ nsresult rv = Preferences::GetInt(SEND_BUFFER_PREF, &bufferSize);
+ if (NS_SUCCEEDED(rv)) {
+ mSendBufferSize = bufferSize;
+ return;
+ }
+
+#if defined(XP_WIN)
+ mSendBufferSize = 131072 * 4;
+#endif
+}
+
+nsresult nsSocketTransportService::UpdatePrefs() {
+ mSendBufferSize = 0;
+
+ UpdateSendBufferPref();
+
+ // Default TCP Keepalive Values.
+ int32_t keepaliveIdleTimeS;
+ nsresult rv =
+ Preferences::GetInt(KEEPALIVE_IDLE_TIME_PREF, &keepaliveIdleTimeS);
+ if (NS_SUCCEEDED(rv)) {
+ mKeepaliveIdleTimeS = clamped(keepaliveIdleTimeS, 1, kMaxTCPKeepIdle);
+ }
+
+ int32_t keepaliveRetryIntervalS;
+ rv = Preferences::GetInt(KEEPALIVE_RETRY_INTERVAL_PREF,
+ &keepaliveRetryIntervalS);
+ if (NS_SUCCEEDED(rv)) {
+ mKeepaliveRetryIntervalS =
+ clamped(keepaliveRetryIntervalS, 1, kMaxTCPKeepIntvl);
+ }
+
+ int32_t keepaliveProbeCount;
+ rv = Preferences::GetInt(KEEPALIVE_PROBE_COUNT_PREF, &keepaliveProbeCount);
+ if (NS_SUCCEEDED(rv)) {
+ mKeepaliveProbeCount = clamped(keepaliveProbeCount, 1, kMaxTCPKeepCount);
+ }
+ bool keepaliveEnabled = false;
+ rv = Preferences::GetBool(KEEPALIVE_ENABLED_PREF, &keepaliveEnabled);
+ if (NS_SUCCEEDED(rv) && keepaliveEnabled != mKeepaliveEnabledPref) {
+ mKeepaliveEnabledPref = keepaliveEnabled;
+ OnKeepaliveEnabledPrefChange();
+ }
+
+ int32_t maxTimePref;
+ rv = Preferences::GetInt(MAX_TIME_BETWEEN_TWO_POLLS, &maxTimePref);
+ if (NS_SUCCEEDED(rv) && maxTimePref >= 0) {
+ mMaxTimePerPollIter = maxTimePref;
+ }
+
+ int32_t pollBusyWaitPeriod;
+ rv = Preferences::GetInt(POLL_BUSY_WAIT_PERIOD, &pollBusyWaitPeriod);
+ if (NS_SUCCEEDED(rv) && pollBusyWaitPeriod > 0) {
+ mNetworkLinkChangeBusyWaitPeriod = PR_SecondsToInterval(pollBusyWaitPeriod);
+ }
+
+ int32_t pollBusyWaitPeriodTimeout;
+ rv = Preferences::GetInt(POLL_BUSY_WAIT_PERIOD_TIMEOUT,
+ &pollBusyWaitPeriodTimeout);
+ if (NS_SUCCEEDED(rv) && pollBusyWaitPeriodTimeout > 0) {
+ mNetworkLinkChangeBusyWaitTimeout =
+ PR_SecondsToInterval(pollBusyWaitPeriodTimeout);
+ }
+
+ int32_t maxTimeForPrClosePref;
+ rv = Preferences::GetInt(MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN,
+ &maxTimeForPrClosePref);
+ if (NS_SUCCEEDED(rv) && maxTimeForPrClosePref >= 0) {
+ mMaxTimeForPrClosePref = PR_MillisecondsToInterval(maxTimeForPrClosePref);
+ }
+
+ int32_t pollableEventTimeout;
+ rv = Preferences::GetInt(POLLABLE_EVENT_TIMEOUT, &pollableEventTimeout);
+ if (NS_SUCCEEDED(rv) && pollableEventTimeout >= 0) {
+ MutexAutoLock lock(mLock);
+ mPollableEventTimeout = TimeDuration::FromSeconds(pollableEventTimeout);
+ }
+
+ nsAutoCString portMappingPref;
+ rv = Preferences::GetCString("network.socket.forcePort", portMappingPref);
+ if (NS_SUCCEEDED(rv)) {
+ bool rv = UpdatePortRemapPreference(portMappingPref);
+ if (!rv) {
+ NS_ERROR(
+ "network.socket.forcePort preference is ill-formed, this will likely "
+ "make everything unexpectedly fail!");
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsSocketTransportService::OnKeepaliveEnabledPrefChange() {
+ // Dispatch to socket thread if we're not executing there.
+ if (!OnSocketThread()) {
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod(
+ "net::nsSocketTransportService::OnKeepaliveEnabledPrefChange", this,
+ &nsSocketTransportService::OnKeepaliveEnabledPrefChange),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SOCKET_LOG(("nsSocketTransportService::OnKeepaliveEnabledPrefChange %s",
+ mKeepaliveEnabledPref ? "enabled" : "disabled"));
+
+ // Notify each socket that keepalive has been en/disabled globally.
+ for (int32_t i = mActiveList.Length() - 1; i >= 0; --i) {
+ NotifyKeepaliveEnabledPrefChange(&mActiveList[i]);
+ }
+ for (int32_t i = mIdleList.Length() - 1; i >= 0; --i) {
+ NotifyKeepaliveEnabledPrefChange(&mIdleList[i]);
+ }
+}
+
+void nsSocketTransportService::NotifyKeepaliveEnabledPrefChange(
+ SocketContext* sock) {
+ MOZ_ASSERT(sock, "SocketContext cannot be null!");
+ MOZ_ASSERT(sock->mHandler, "SocketContext does not have a handler!");
+
+ if (!sock || !sock->mHandler) {
+ return;
+ }
+
+ sock->mHandler->OnKeepaliveEnabledPrefChange(mKeepaliveEnabledPref);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsSocketTransportService");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ SOCKET_LOG(("nsSocketTransportService::Observe topic=%s", topic));
+
+ if (!strcmp(topic, "profile-initial-state")) {
+ if (!Preferences::GetBool(IO_ACTIVITY_ENABLED_PREF, false)) {
+ return NS_OK;
+ }
+ return net::IOActivityMonitor::Init();
+ }
+
+ if (!strcmp(topic, "last-pb-context-exited")) {
+ nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(
+ "net::nsSocketTransportService::ClosePrivateConnections", this,
+ &nsSocketTransportService::ClosePrivateConnections);
+ nsresult rv = Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!strcmp(topic, NS_TIMER_CALLBACK_TOPIC)) {
+ nsCOMPtr<nsITimer> timer = do_QueryInterface(subject);
+ if (timer == mAfterWakeUpTimer) {
+ mAfterWakeUpTimer = nullptr;
+ mSleepPhase = false;
+ }
+
+#if defined(XP_WIN)
+ if (timer == mPollRepairTimer) {
+ DoPollRepair();
+ }
+#endif
+
+ } else if (!strcmp(topic, NS_WIDGET_SLEEP_OBSERVER_TOPIC)) {
+ mSleepPhase = true;
+ if (mAfterWakeUpTimer) {
+ mAfterWakeUpTimer->Cancel();
+ mAfterWakeUpTimer = nullptr;
+ }
+ } else if (!strcmp(topic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
+ if (mSleepPhase && !mAfterWakeUpTimer) {
+ NS_NewTimerWithObserver(getter_AddRefs(mAfterWakeUpTimer), this, 2000,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ } else if (!strcmp(topic, "xpcom-shutdown-threads")) {
+ ShutdownThread();
+ } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ mLastNetworkLinkChangeTime = PR_IntervalNow();
+ }
+
+ return NS_OK;
+}
+
+void nsSocketTransportService::ClosePrivateConnections() {
+ MOZ_ASSERT(IsOnCurrentThread(), "Must be called on the socket thread");
+
+ for (int32_t i = mActiveList.Length() - 1; i >= 0; --i) {
+ if (mActiveList[i].mHandler->mIsPrivate) {
+ DetachSocket(mActiveList, &mActiveList[i]);
+ }
+ }
+ for (int32_t i = mIdleList.Length() - 1; i >= 0; --i) {
+ if (mIdleList[i].mHandler->mIsPrivate) {
+ DetachSocket(mIdleList, &mIdleList[i]);
+ }
+ }
+
+ ClearPrivateSSLState();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetSendBufferSize(int32_t* value) {
+ *value = mSendBufferSize;
+ return NS_OK;
+}
+
+/// ugly OS specific includes are placed at the bottom of the src for clarity
+
+#if defined(XP_WIN)
+# include <windows.h>
+#elif defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+# include <sys/resource.h>
+#endif
+
+PRStatus nsSocketTransportService::DiscoverMaxCount() {
+ gMaxCount = SOCKET_LIMIT_MIN;
+
+#if defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+ // On unix and os x network sockets and file
+ // descriptors are the same. OS X comes defaulted at 256,
+ // most linux at 1000. We can reliably use [sg]rlimit to
+ // query that and raise it if needed.
+
+ struct rlimit rlimitData {};
+ if (getrlimit(RLIMIT_NOFILE, &rlimitData) == -1) { // rlimit broken - use min
+ return PR_SUCCESS;
+ }
+
+ if (rlimitData.rlim_cur >= SOCKET_LIMIT_TARGET) { // larger than target!
+ gMaxCount = SOCKET_LIMIT_TARGET;
+ return PR_SUCCESS;
+ }
+
+ int32_t maxallowed = rlimitData.rlim_max;
+ if ((uint32_t)maxallowed <= SOCKET_LIMIT_MIN) {
+ return PR_SUCCESS; // so small treat as if rlimit is broken
+ }
+
+ if ((maxallowed == -1) || // no hard cap - ok to set target
+ ((uint32_t)maxallowed >= SOCKET_LIMIT_TARGET)) {
+ maxallowed = SOCKET_LIMIT_TARGET;
+ }
+
+ rlimitData.rlim_cur = maxallowed;
+ setrlimit(RLIMIT_NOFILE, &rlimitData);
+ if ((getrlimit(RLIMIT_NOFILE, &rlimitData) != -1) &&
+ (rlimitData.rlim_cur > SOCKET_LIMIT_MIN)) {
+ gMaxCount = rlimitData.rlim_cur;
+ }
+
+#elif defined(XP_WIN) && !defined(WIN_CE)
+ // >= XP is confirmed to have at least 1000
+ static_assert(SOCKET_LIMIT_TARGET <= 1000,
+ "SOCKET_LIMIT_TARGET max value is 1000");
+ gMaxCount = SOCKET_LIMIT_TARGET;
+#else
+ // other platforms are harder to test - so leave at safe legacy value
+#endif
+
+ return PR_SUCCESS;
+}
+
+// Used to return connection info to Dashboard.cpp
+void nsSocketTransportService::AnalyzeConnection(nsTArray<SocketInfo>* data,
+ SocketContext* context,
+ bool aActive) {
+ if (context->mHandler->mIsPrivate) {
+ return;
+ }
+ PRFileDesc* aFD = context->mFD;
+
+ PRFileDesc* idLayer = PR_GetIdentitiesLayer(aFD, PR_NSPR_IO_LAYER);
+
+ NS_ENSURE_TRUE_VOID(idLayer);
+
+ PRDescType type = PR_GetDescType(idLayer);
+ char host[64] = {0};
+ uint16_t port;
+ const char* type_desc;
+
+ if (type == PR_DESC_SOCKET_TCP) {
+ type_desc = "TCP";
+ PRNetAddr peer_addr;
+ PodZero(&peer_addr);
+
+ PRStatus rv = PR_GetPeerName(aFD, &peer_addr);
+ if (rv != PR_SUCCESS) {
+ return;
+ }
+
+ rv = PR_NetAddrToString(&peer_addr, host, sizeof(host));
+ if (rv != PR_SUCCESS) {
+ return;
+ }
+
+ if (peer_addr.raw.family == PR_AF_INET) {
+ port = peer_addr.inet.port;
+ } else {
+ port = peer_addr.ipv6.port;
+ }
+ port = PR_ntohs(port);
+ } else {
+ if (type == PR_DESC_SOCKET_UDP) {
+ type_desc = "UDP";
+ } else {
+ type_desc = "other";
+ }
+ NetAddr addr;
+ if (context->mHandler->GetRemoteAddr(&addr) != NS_OK) {
+ return;
+ }
+ if (!addr.ToStringBuffer(host, sizeof(host))) {
+ return;
+ }
+ if (addr.GetPort(&port) != NS_OK) {
+ return;
+ }
+ }
+
+ uint64_t sent = context->mHandler->ByteCountSent();
+ uint64_t received = context->mHandler->ByteCountReceived();
+ SocketInfo info = {nsCString(host), sent, received, port, aActive,
+ nsCString(type_desc)};
+
+ data->AppendElement(info);
+}
+
+void nsSocketTransportService::GetSocketConnections(
+ nsTArray<SocketInfo>* data) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ for (uint32_t i = 0; i < mActiveList.Length(); i++) {
+ AnalyzeConnection(data, &mActiveList[i], true);
+ }
+ for (uint32_t i = 0; i < mIdleList.Length(); i++) {
+ AnalyzeConnection(data, &mIdleList[i], false);
+ }
+}
+
+bool nsSocketTransportService::IsTelemetryEnabledAndNotSleepPhase() {
+ return Telemetry::CanRecordPrereleaseData() && !mSleepPhase;
+}
+
+#if defined(XP_WIN)
+void nsSocketTransportService::StartPollWatchdog() {
+ // Start off the timer from a runnable off of the main thread in order to
+ // avoid a deadlock, see bug 1370448.
+ RefPtr<nsSocketTransportService> self(this);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsSocketTransportService::StartPollWatchdog", [self] {
+ MutexAutoLock lock(self->mLock);
+
+ // Poll can hang sometimes. If we are in shutdown, we are going to start
+ // a watchdog. If we do not exit poll within REPAIR_POLLABLE_EVENT_TIME
+ // signal a pollable event again.
+ if (gIOService->IsNetTearingDown() && self->mPolling &&
+ !self->mPollRepairTimer) {
+ NS_NewTimerWithObserver(getter_AddRefs(self->mPollRepairTimer), self,
+ REPAIR_POLLABLE_EVENT_TIME,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+ }));
+}
+
+void nsSocketTransportService::DoPollRepair() {
+ MutexAutoLock lock(mLock);
+ if (mPolling && mPollableEvent) {
+ mPollableEvent->Signal();
+ } else if (mPollRepairTimer) {
+ mPollRepairTimer->Cancel();
+ }
+}
+
+void nsSocketTransportService::StartPolling() {
+ MutexAutoLock lock(mLock);
+ mPolling = true;
+}
+
+void nsSocketTransportService::EndPolling() {
+ MutexAutoLock lock(mLock);
+ mPolling = false;
+ if (mPollRepairTimer) {
+ mPollRepairTimer->Cancel();
+ }
+}
+
+#endif
+
+void nsSocketTransportService::TryRepairPollableEvent() {
+ mLock.AssertCurrentThreadOwns();
+
+ NS_WARNING("Trying to repair mPollableEvent");
+ mPollableEvent.reset(new PollableEvent());
+ if (!mPollableEvent->Valid()) {
+ mPollableEvent = nullptr;
+ }
+ SOCKET_LOG(
+ ("running socket transport thread without "
+ "a pollable event now valid=%d",
+ !!mPollableEvent));
+ mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr;
+ mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
+ mPollList[0].out_flags = 0;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::AddShutdownObserver(
+ nsISTSShutdownObserver* aObserver) {
+ mShutdownObservers.AppendElement(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::RemoveShutdownObserver(
+ nsISTSShutdownObserver* aObserver) {
+ mShutdownObservers.RemoveElement(aObserver);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSocketTransportService2.h b/netwerk/base/nsSocketTransportService2.h
new file mode 100644
index 0000000000..4c4848044b
--- /dev/null
+++ b/netwerk/base/nsSocketTransportService2.h
@@ -0,0 +1,355 @@
+/* vim:set ts=4 sw=2 sts=2 ci et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSocketTransportService2_h__
+#define nsSocketTransportService2_h__
+
+#include "PollableEvent.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "nsCOMPtr.h"
+#include "nsASocketHandler.h"
+#include "nsIDirectTaskDispatcher.h"
+#include "nsIObserver.h"
+#include "nsIRunnable.h"
+#include "nsIThreadInternal.h"
+#include "nsITimer.h"
+#include "nsPISocketTransportService.h"
+#include "prinit.h"
+#include "prinrval.h"
+
+struct PRPollDesc;
+class nsIPrefBranch;
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+//
+// set MOZ_LOG=nsSocketTransport:5
+//
+extern LazyLogModule gSocketTransportLog;
+#define SOCKET_LOG(args) MOZ_LOG(gSocketTransportLog, LogLevel::Debug, args)
+#define SOCKET_LOG1(args) MOZ_LOG(gSocketTransportLog, LogLevel::Error, args)
+#define SOCKET_LOG_ENABLED() MOZ_LOG_TEST(gSocketTransportLog, LogLevel::Debug)
+
+//
+// set MOZ_LOG=UDPSocket:5
+//
+extern LazyLogModule gUDPSocketLog;
+#define UDPSOCKET_LOG(args) MOZ_LOG(gUDPSocketLog, LogLevel::Debug, args)
+#define UDPSOCKET_LOG_ENABLED() MOZ_LOG_TEST(gUDPSocketLog, LogLevel::Debug)
+
+//-----------------------------------------------------------------------------
+
+#define NS_SOCKET_POLL_TIMEOUT PR_INTERVAL_NO_TIMEOUT
+
+//-----------------------------------------------------------------------------
+
+// These maximums are borrowed from the linux kernel.
+static const int32_t kMaxTCPKeepIdle = 32767; // ~9 hours.
+static const int32_t kMaxTCPKeepIntvl = 32767;
+static const int32_t kMaxTCPKeepCount = 127;
+static const int32_t kDefaultTCPKeepCount =
+#if defined(XP_WIN)
+ 10; // Hardcoded in Windows.
+#elif defined(XP_MACOSX)
+ 8; // Hardcoded in OSX.
+#else
+ 4; // Specifiable in Linux.
+#endif
+
+class LinkedRunnableEvent final
+ : public LinkedListElement<LinkedRunnableEvent> {
+ public:
+ explicit LinkedRunnableEvent(nsIRunnable* event) : mEvent(event) {}
+ ~LinkedRunnableEvent() = default;
+
+ already_AddRefed<nsIRunnable> TakeEvent() { return mEvent.forget(); }
+
+ private:
+ nsCOMPtr<nsIRunnable> mEvent;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketTransportService final : public nsPISocketTransportService,
+ public nsISerialEventTarget,
+ public nsIThreadObserver,
+ public nsIRunnable,
+ public nsIObserver,
+ public nsINamed,
+ public nsIDirectTaskDispatcher {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSPISOCKETTRANSPORTSERVICE
+ NS_DECL_NSISOCKETTRANSPORTSERVICE
+ NS_DECL_NSIROUTEDSOCKETTRANSPORTSERVICE
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSITHREADOBSERVER
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+ NS_DECL_NSIDIRECTTASKDISPATCHER
+
+ static const uint32_t SOCKET_LIMIT_MIN = 50U;
+
+ nsSocketTransportService();
+
+ // Max Socket count may need to get initialized/used by nsHttpHandler
+ // before this class is initialized.
+ static uint32_t gMaxCount;
+ static PRCallOnceType gMaxCountInitOnce;
+ static PRStatus DiscoverMaxCount();
+
+ bool CanAttachSocket();
+
+ // Called by the networking dashboard on the socket thread only
+ // Fills the passed array with socket information
+ void GetSocketConnections(nsTArray<SocketInfo>*);
+ uint64_t GetSentBytes() { return mSentBytesCount; }
+ uint64_t GetReceivedBytes() { return mReceivedBytesCount; }
+
+ // Returns true if keepalives are enabled in prefs.
+ bool IsKeepaliveEnabled() { return mKeepaliveEnabledPref; }
+
+ bool IsTelemetryEnabledAndNotSleepPhase();
+ PRIntervalTime MaxTimeForPrClosePref() { return mMaxTimeForPrClosePref; }
+
+ // According the preference value of `network.socket.forcePort` this method
+ // possibly remaps the port number passed as the arg.
+ void ApplyPortRemap(uint16_t* aPort);
+
+ // Reads the preference string and updates (rewrites) the mPortRemapping
+ // array on the socket thread. Returns true if the whole pref string was
+ // correctly formed.
+ bool UpdatePortRemapPreference(nsACString const& aPortMappingPref);
+
+ protected:
+ virtual ~nsSocketTransportService();
+
+ private:
+ //-------------------------------------------------------------------------
+ // misc (any thread)
+ //-------------------------------------------------------------------------
+
+ // The value is guaranteed to be valid and not dangling while on the socket
+ // thread as mThread is only ever reset after it's been shutdown.
+ // This member should only ever be read on the socket thread.
+ nsIThread* mRawThread{nullptr};
+
+ // Returns mThread in a thread-safe manner.
+ already_AddRefed<nsIThread> GetThreadSafely();
+ // Same as above, but return mThread as a nsIDirectTaskDispatcher
+ already_AddRefed<nsIDirectTaskDispatcher> GetDirectTaskDispatcherSafely();
+
+ //-------------------------------------------------------------------------
+ // initialization and shutdown (any thread)
+ //-------------------------------------------------------------------------
+
+ Atomic<bool> mInitialized{false};
+ // indicates whether we are currently in the process of shutting down
+ Atomic<bool> mShuttingDown{false};
+
+ Mutex mLock{"nsSocketTransportService::mLock"};
+ // Variables in the next section protected by mLock
+
+ // mThread and mDirectTaskDispatcher are only ever modified on the main
+ // thread. Will be set on Init and set to null after shutdown. You must access
+ // mThread and mDirectTaskDispatcher outside the main thread via respectively
+ // GetThreadSafely and GetDirectTaskDispatchedSafely().
+ nsCOMPtr<nsIThread> mThread MOZ_GUARDED_BY(mLock);
+ // We store a pointer to mThread as a direct task dispatcher to avoid having
+ // to do do_QueryInterface whenever we need to access the interface.
+ nsCOMPtr<nsIDirectTaskDispatcher> mDirectTaskDispatcher MOZ_GUARDED_BY(mLock);
+ UniquePtr<PollableEvent> mPollableEvent MOZ_GUARDED_BY(mLock);
+ bool mOffline MOZ_GUARDED_BY(mLock) = false;
+ bool mGoingOffline MOZ_GUARDED_BY(mLock) = false;
+
+ // Detaches all sockets.
+ void Reset(bool aGuardLocals);
+
+ nsresult ShutdownThread();
+
+ //-------------------------------------------------------------------------
+ // socket lists (socket thread only)
+ //
+ // only "active" sockets are on the poll list. the active list is kept
+ // in sync with the poll list such that:
+ //
+ // mActiveList[k].mFD == mPollList[k+1].fd
+ //
+ // where k=0,1,2,...
+ //-------------------------------------------------------------------------
+
+ class SocketContext {
+ public:
+ SocketContext(PRFileDesc* aFD,
+ already_AddRefed<nsASocketHandler>&& aHandler,
+ PRIntervalTime aPollStartEpoch)
+ : mFD(aFD), mHandler(aHandler), mPollStartEpoch(aPollStartEpoch) {}
+ SocketContext(PRFileDesc* aFD, nsASocketHandler* aHandler,
+ PRIntervalTime aPollStartEpoch)
+ : mFD(aFD), mHandler(aHandler), mPollStartEpoch(aPollStartEpoch) {}
+ ~SocketContext() = default;
+
+ // Returns true iff the socket has not been signalled longer than
+ // the desired timeout (mHandler->mPollTimeout).
+ bool IsTimedOut(PRIntervalTime now) const;
+ // Engages the timeout by marking the epoch we start polling this socket.
+ // If epoch is already marked this does nothing, hence, this method can be
+ // called everytime we put this socket to poll() list with in-flags set.
+ void EnsureTimeout(PRIntervalTime now);
+ // Called after an event on a socket has been signalled to turn of the
+ // timeout calculation.
+ void DisengageTimeout();
+ // Returns the number of intervals this socket is about to timeout in,
+ // or 0 (zero) when it has already timed out. Returns
+ // NS_SOCKET_POLL_TIMEOUT when there is no timeout set on the socket.
+ PRIntervalTime TimeoutIn(PRIntervalTime now) const;
+ // When a socket timeout is reset and later set again, it may happen
+ // that mPollStartEpoch is not reset in between. We have to manually
+ // call this on every iteration over sockets to ensure the epoch reset.
+ void MaybeResetEpoch();
+
+ PRFileDesc* mFD;
+ RefPtr<nsASocketHandler> mHandler;
+ PRIntervalTime mPollStartEpoch; // time we started to poll this socket
+ };
+
+ using SocketContextList = AutoTArray<SocketContext, SOCKET_LIMIT_MIN>;
+ int64_t SockIndex(SocketContextList& aList, SocketContext* aSock);
+
+ SocketContextList mActiveList;
+ SocketContextList mIdleList;
+
+ nsresult DetachSocket(SocketContextList& listHead, SocketContext*);
+ void AddToIdleList(SocketContext* sock);
+ void AddToPollList(SocketContext* sock);
+ void RemoveFromIdleList(SocketContext* sock);
+ void RemoveFromPollList(SocketContext* sock);
+ void MoveToIdleList(SocketContext* sock);
+ void MoveToPollList(SocketContext* sock);
+
+ void InitMaxCount();
+
+ // Total bytes number transfered through all the sockets except active ones
+ uint64_t mSentBytesCount{0};
+ uint64_t mReceivedBytesCount{0};
+ //-------------------------------------------------------------------------
+ // poll list (socket thread only)
+ //
+ // first element of the poll list is mPollableEvent (or null if the pollable
+ // event cannot be created).
+ //-------------------------------------------------------------------------
+
+ nsTArray<PRPollDesc> mPollList;
+
+ PRIntervalTime PollTimeout(
+ PRIntervalTime now); // computes ideal poll timeout
+ nsresult DoPollIteration(TimeDuration* pollDuration);
+ // perfoms a single poll iteration
+ int32_t Poll(TimeDuration* pollDuration, PRIntervalTime ts);
+ // calls PR_Poll. the out param
+ // interval indicates the poll
+ // duration in seconds.
+ // pollDuration is used only for
+ // telemetry
+
+ //-------------------------------------------------------------------------
+ // pending socket queue - see NotifyWhenCanAttachSocket
+ //-------------------------------------------------------------------------
+ AutoCleanLinkedList<LinkedRunnableEvent> mPendingSocketQueue;
+
+ // Preference Monitor for SendBufferSize and Keepalive prefs.
+ nsresult UpdatePrefs();
+ static void UpdatePrefs(const char* aPref, void* aSelf);
+ void UpdateSendBufferPref();
+ int32_t mSendBufferSize{0};
+ // Number of seconds of connection is idle before first keepalive ping.
+ int32_t mKeepaliveIdleTimeS{600};
+ // Number of seconds between retries should keepalive pings fail.
+ int32_t mKeepaliveRetryIntervalS{1};
+ // Number of keepalive probes to send.
+ int32_t mKeepaliveProbeCount{kDefaultTCPKeepCount};
+ // True if TCP keepalive is enabled globally.
+ bool mKeepaliveEnabledPref{false};
+ // Timeout of pollable event signalling.
+ TimeDuration mPollableEventTimeout MOZ_GUARDED_BY(mLock);
+
+ Atomic<bool> mServingPendingQueue{false};
+ Atomic<int32_t, Relaxed> mMaxTimePerPollIter{100};
+ Atomic<PRIntervalTime, Relaxed> mMaxTimeForPrClosePref;
+ // Timestamp of the last network link change event, tracked
+ // also on child processes.
+ Atomic<PRIntervalTime, Relaxed> mLastNetworkLinkChangeTime{0};
+ // Preference for how long we do busy wait after network link
+ // change has been detected.
+ Atomic<PRIntervalTime, Relaxed> mNetworkLinkChangeBusyWaitPeriod;
+ // Preference for the value of timeout for poll() we use during
+ // the network link change event period.
+ Atomic<PRIntervalTime, Relaxed> mNetworkLinkChangeBusyWaitTimeout;
+
+ // Between a computer going to sleep and waking up the PR_*** telemetry
+ // will be corrupted - so do not record it.
+ Atomic<bool, Relaxed> mSleepPhase{false};
+ nsCOMPtr<nsITimer> mAfterWakeUpTimer;
+
+ // Lazily created array of forced port remappings. The tuple members meaning
+ // is exactly:
+ // <0> the greater-or-equal port number of the range to remap
+ // <1> the less-or-equal port number of the range to remap
+ // <2> the port number to remap to, when the given port number falls to the
+ // range
+ using TPortRemapping =
+ CopyableTArray<std::tuple<uint16_t, uint16_t, uint16_t>>;
+ Maybe<TPortRemapping> mPortRemapping;
+
+ // Called on the socket thread to apply the mapping build on the main thread
+ // from the preference.
+ void ApplyPortRemapPreference(TPortRemapping const& portRemapping);
+
+ void OnKeepaliveEnabledPrefChange();
+ void NotifyKeepaliveEnabledPrefChange(SocketContext* sock);
+
+ // Report socket status to about:networking
+ void AnalyzeConnection(nsTArray<SocketInfo>* data, SocketContext* context,
+ bool aActive);
+
+ void ClosePrivateConnections();
+ void DetachSocketWithGuard(bool aGuardLocals, SocketContextList& socketList,
+ int32_t index);
+
+ void MarkTheLastElementOfPendingQueue();
+
+#if defined(XP_WIN)
+ Atomic<bool> mPolling{false};
+ nsCOMPtr<nsITimer> mPollRepairTimer;
+ void StartPollWatchdog();
+ void DoPollRepair();
+ void StartPolling();
+ void EndPolling();
+#endif
+
+ void TryRepairPollableEvent();
+
+ CopyableTArray<nsCOMPtr<nsISTSShutdownObserver>> mShutdownObservers;
+};
+
+extern nsSocketTransportService* gSocketTransportService;
+bool OnSocketThread();
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsSocketTransportService_h__
diff --git a/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp
new file mode 100644
index 0000000000..de3578c439
--- /dev/null
+++ b/netwerk/base/nsStandardURL.cpp
@@ -0,0 +1,4034 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "nsASCIIMask.h"
+#include "nsStandardURL.h"
+#include "nsCRT.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIIDNService.h"
+#include "mozilla/Logging.h"
+#include "nsIURLParser.h"
+#include "nsPrintfCString.h"
+#include "nsNetCID.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/TextUtils.h"
+#include <algorithm>
+#include "nsContentUtils.h"
+#include "prprf.h"
+#include "nsReadableUtils.h"
+#include "mozilla/net/MozURL_ffi.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
+#include "nsIClassInfoImpl.h"
+#include <string.h>
+
+//
+// setenv MOZ_LOG nsStandardURL:5
+//
+static mozilla::LazyLogModule gStandardURLLog("nsStandardURL");
+
+// The Chromium code defines its own LOG macro which we don't want
+#undef LOG
+#define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID);
+
+// This will always be initialized and destroyed on the main thread, but
+// can be safely used on other threads.
+StaticRefPtr<nsIIDNService> nsStandardURL::gIDN;
+
+// This value will only be updated on the main thread once.
+static Atomic<bool, Relaxed> gInitialized{false};
+
+const char nsStandardURL::gHostLimitDigits[] = {'/', '\\', '?', '#', 0};
+
+// Invalid host characters
+// Note that the array below will be initialized at compile time,
+// so we do not need to "optimize" TestForInvalidHostCharacters.
+//
+constexpr bool TestForInvalidHostCharacters(char c) {
+ // Testing for these:
+ // CONTROL_CHARACTERS " #/:?@[\\]*<>|\"";
+ return (c > 0 && c < 32) || // The control characters are [1, 31]
+ c == 0x7F || // // DEL (delete)
+ c == ' ' || c == '#' || c == '/' || c == ':' || c == '?' || c == '@' ||
+ c == '[' || c == '\\' || c == ']' || c == '*' || c == '<' ||
+ c == '^' ||
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ // Mailnews %-escapes file paths into URLs.
+ c == '>' || c == '|' || c == '"';
+#else
+ c == '>' || c == '|' || c == '"' || c == '%';
+#endif
+}
+constexpr ASCIIMaskArray sInvalidHostChars =
+ CreateASCIIMask(TestForInvalidHostCharacters);
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsSegmentEncoder
+//----------------------------------------------------------------------------
+
+nsStandardURL::nsSegmentEncoder::nsSegmentEncoder(const Encoding* encoding)
+ : mEncoding(encoding) {
+ if (mEncoding == UTF_8_ENCODING) {
+ mEncoding = nullptr;
+ }
+}
+
+int32_t nsStandardURL::nsSegmentEncoder::EncodeSegmentCount(
+ const char* aStr, const URLSegment& aSeg, int16_t aMask, nsCString& aOut,
+ bool& aAppended, uint32_t aExtraLen) {
+ // aExtraLen is characters outside the segment that will be
+ // added when the segment is not empty (like the @ following
+ // a username).
+ if (!aStr || aSeg.mLen <= 0) {
+ // Empty segment, so aExtraLen not added per above.
+ aAppended = false;
+ return 0;
+ }
+
+ uint32_t origLen = aOut.Length();
+
+ Span<const char> span = Span(aStr + aSeg.mPos, aSeg.mLen);
+
+ // first honor the origin charset if appropriate. as an optimization,
+ // only do this if the segment is non-ASCII. Further, if mEncoding is
+ // null, then the origin charset is UTF-8 and there is nothing to do.
+ if (mEncoding) {
+ size_t upTo;
+ if (MOZ_UNLIKELY(mEncoding == ISO_2022_JP_ENCODING)) {
+ upTo = Encoding::ISO2022JPASCIIValidUpTo(AsBytes(span));
+ } else {
+ upTo = Encoding::ASCIIValidUpTo(AsBytes(span));
+ }
+ if (upTo != span.Length()) {
+ // we have to encode this segment
+ char bufferArr[512];
+ Span<char> buffer = Span(bufferArr);
+
+ auto encoder = mEncoding->NewEncoder();
+
+ nsAutoCString valid; // has to be declared in this scope
+ if (MOZ_UNLIKELY(!IsUtf8(span.From(upTo)))) {
+ MOZ_ASSERT_UNREACHABLE("Invalid UTF-8 passed to nsStandardURL.");
+ // It's UB to pass invalid UTF-8 to
+ // EncodeFromUTF8WithoutReplacement(), so let's make our input valid
+ // UTF-8 by replacing invalid sequences with the REPLACEMENT
+ // CHARACTER.
+ UTF_8_ENCODING->Decode(
+ nsDependentCSubstring(span.Elements(), span.Length()), valid);
+ // This assigment is OK. `span` can't be used after `valid` has
+ // been destroyed because the only way out of the scope that `valid`
+ // was declared in is via return inside the loop below. Specifically,
+ // the return is the only way out of the loop.
+ span = valid;
+ }
+
+ size_t totalRead = 0;
+ for (;;) {
+ auto [encoderResult, read, written] =
+ encoder->EncodeFromUTF8WithoutReplacement(
+ AsBytes(span.From(totalRead)), AsWritableBytes(buffer), true);
+ totalRead += read;
+ auto bufferWritten = buffer.To(written);
+ if (!NS_EscapeURLSpan(bufferWritten, aMask, aOut)) {
+ aOut.Append(bufferWritten);
+ }
+ if (encoderResult == kInputEmpty) {
+ aAppended = true;
+ // Difference between original and current output
+ // string lengths plus extra length
+ return aOut.Length() - origLen + aExtraLen;
+ }
+ if (encoderResult == kOutputFull) {
+ continue;
+ }
+ aOut.AppendLiteral("%26%23");
+ aOut.AppendInt(encoderResult);
+ aOut.AppendLiteral("%3B");
+ }
+ MOZ_RELEASE_ASSERT(
+ false,
+ "There's supposed to be no way out of the above loop except return.");
+ }
+ }
+
+ if (NS_EscapeURLSpan(span, aMask, aOut)) {
+ aAppended = true;
+ // Difference between original and current output
+ // string lengths plus extra length
+ return aOut.Length() - origLen + aExtraLen;
+ }
+ aAppended = false;
+ // Original segment length plus extra length
+ return span.Length() + aExtraLen;
+}
+
+const nsACString& nsStandardURL::nsSegmentEncoder::EncodeSegment(
+ const nsACString& str, int16_t mask, nsCString& result) {
+ const char* text;
+ bool encoded;
+ EncodeSegmentCount(str.BeginReading(text), URLSegment(0, str.Length()), mask,
+ result, encoded);
+ if (encoded) {
+ return result;
+ }
+ return str;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL <public>
+//----------------------------------------------------------------------------
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+static StaticMutex gAllURLsMutex MOZ_UNANNOTATED;
+static LinkedList<nsStandardURL> gAllURLs;
+#endif
+
+nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL)
+ : mURLType(URLTYPE_STANDARD),
+ mSupportsFileURL(aSupportsFileURL),
+ mCheckedIfHostA(false) {
+ LOG(("Creating nsStandardURL @%p\n", this));
+
+ // gInitialized changes value only once (false->true) on the main thread.
+ // It's OK to race here because in the worst case we'll just
+ // dispatch a noop runnable to the main thread.
+ MOZ_ASSERT(gInitialized);
+
+ // default parser in case nsIStandardURL::Init is never called
+ mParser = net_GetStdURLParser();
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ if (aTrackURL) {
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ gAllURLs.insertBack(this);
+ }
+#endif
+}
+
+bool nsStandardURL::IsValid() {
+ auto checkSegment = [&](const nsStandardURL::URLSegment& aSeg) {
+#ifdef EARLY_BETA_OR_EARLIER
+ // If the parity is not the same, we assume that this is caused by a memory
+ // error. In this case, we think this URLSegment is valid.
+ if ((aSeg.mPos.Parity() != aSeg.mPos.CalculateParity()) ||
+ (aSeg.mLen.Parity() != aSeg.mLen.CalculateParity())) {
+ MOZ_ASSERT(false);
+ return true;
+ }
+#endif
+ // Bad value
+ if (NS_WARN_IF(aSeg.mLen < -1)) {
+ return false;
+ }
+ if (aSeg.mLen == -1) {
+ return true;
+ }
+
+ // Points out of string
+ if (NS_WARN_IF(aSeg.mPos + aSeg.mLen > mSpec.Length())) {
+ return false;
+ }
+
+ // Overflow
+ if (NS_WARN_IF(aSeg.mPos + aSeg.mLen < aSeg.mPos)) {
+ return false;
+ }
+
+ return true;
+ };
+
+ bool allSegmentsValid = checkSegment(mScheme) && checkSegment(mAuthority) &&
+ checkSegment(mUsername) && checkSegment(mPassword) &&
+ checkSegment(mHost) && checkSegment(mPath) &&
+ checkSegment(mFilepath) && checkSegment(mDirectory) &&
+ checkSegment(mBasename) && checkSegment(mExtension) &&
+ checkSegment(mQuery) && checkSegment(mRef);
+ if (!allSegmentsValid) {
+ return false;
+ }
+
+ if (mScheme.mPos != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+void nsStandardURL::SanityCheck() {
+ if (!IsValid()) {
+ nsPrintfCString msg(
+ "mLen:%zX, mScheme (%X,%X), mAuthority (%X,%X), mUsername (%X,%X), "
+ "mPassword (%X,%X), mHost (%X,%X), mPath (%X,%X), mFilepath (%X,%X), "
+ "mDirectory (%X,%X), mBasename (%X,%X), mExtension (%X,%X), mQuery "
+ "(%X,%X), mRef (%X,%X)",
+ mSpec.Length(), (uint32_t)mScheme.mPos, (int32_t)mScheme.mLen,
+ (uint32_t)mAuthority.mPos, (int32_t)mAuthority.mLen,
+ (uint32_t)mUsername.mPos, (int32_t)mUsername.mLen,
+ (uint32_t)mPassword.mPos, (int32_t)mPassword.mLen, (uint32_t)mHost.mPos,
+ (int32_t)mHost.mLen, (uint32_t)mPath.mPos, (int32_t)mPath.mLen,
+ (uint32_t)mFilepath.mPos, (int32_t)mFilepath.mLen,
+ (uint32_t)mDirectory.mPos, (int32_t)mDirectory.mLen,
+ (uint32_t)mBasename.mPos, (int32_t)mBasename.mLen,
+ (uint32_t)mExtension.mPos, (int32_t)mExtension.mLen,
+ (uint32_t)mQuery.mPos, (int32_t)mQuery.mLen, (uint32_t)mRef.mPos,
+ (int32_t)mRef.mLen);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URLSegments,
+ msg);
+
+ MOZ_CRASH("nsStandardURL::SanityCheck failed");
+ }
+}
+
+nsStandardURL::~nsStandardURL() {
+ LOG(("Destroying nsStandardURL @%p\n", this));
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ {
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ if (isInList()) {
+ remove();
+ }
+ }
+#endif
+}
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+struct DumpLeakedURLs {
+ DumpLeakedURLs() = default;
+ ~DumpLeakedURLs();
+};
+
+DumpLeakedURLs::~DumpLeakedURLs() {
+ MOZ_ASSERT(NS_IsMainThread());
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ if (!gAllURLs.isEmpty()) {
+ printf("Leaked URLs:\n");
+ for (auto* url : gAllURLs) {
+ url->PrintSpec();
+ }
+ gAllURLs.clear();
+ }
+}
+#endif
+
+void nsStandardURL::InitGlobalObjects() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ if (gInitialized) {
+ return;
+ }
+
+ gInitialized = true;
+
+ nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID));
+ if (serv) {
+ gIDN = serv;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(gIDN);
+
+ // Make sure nsURLHelper::InitGlobals() gets called on the main thread
+ nsCOMPtr<nsIURLParser> parser = net_GetStdURLParser();
+ MOZ_DIAGNOSTIC_ASSERT(parser);
+ Unused << parser;
+}
+
+void nsStandardURL::ShutdownGlobalObjects() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ gIDN = nullptr;
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ if (gInitialized) {
+ // This instanciates a dummy class, and will trigger the class
+ // destructor when libxul is unloaded. This is equivalent to atexit(),
+ // but gracefully handles dlclose().
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ static DumpLeakedURLs d;
+ }
+#endif
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL <private>
+//----------------------------------------------------------------------------
+
+void nsStandardURL::Clear() {
+ mSpec.Truncate();
+
+ mPort = -1;
+
+ mScheme.Reset();
+ mAuthority.Reset();
+ mUsername.Reset();
+ mPassword.Reset();
+ mHost.Reset();
+
+ mPath.Reset();
+ mFilepath.Reset();
+ mDirectory.Reset();
+ mBasename.Reset();
+
+ mExtension.Reset();
+ mQuery.Reset();
+ mRef.Reset();
+
+ InvalidateCache();
+}
+
+void nsStandardURL::InvalidateCache(bool invalidateCachedFile) {
+ if (invalidateCachedFile) {
+ mFile = nullptr;
+ }
+}
+
+// Return the number of "dots" in the string, or -1 if invalid. Note that the
+// number of relevant entries in the bases/starts/ends arrays is number of
+// dots + 1.
+//
+// length is assumed to be <= host.Length(); the caller is responsible for that
+//
+// Note that the value returned is guaranteed to be in [-1, 3] range.
+inline int32_t ValidateIPv4Number(const nsACString& host, int32_t bases[4],
+ int32_t dotIndex[3], bool& onlyBase10,
+ int32_t length, bool trailingDot) {
+ MOZ_ASSERT(length <= (int32_t)host.Length());
+ if (length <= 0) {
+ return -1;
+ }
+
+ bool lastWasNumber = false; // We count on this being false for i == 0
+ int32_t dotCount = 0;
+ onlyBase10 = true;
+
+ for (int32_t i = 0; i < length; i++) {
+ char current = host[i];
+ if (current == '.') {
+ // A dot should not follow a dot, or be first - it can follow an x though.
+ if (!(lastWasNumber ||
+ (i >= 2 && (host[i - 1] == 'X' || host[i - 1] == 'x') &&
+ host[i - 2] == '0')) ||
+ (i == (length - 1) && trailingDot)) {
+ return -1;
+ }
+
+ if (dotCount > 2) {
+ return -1;
+ }
+ lastWasNumber = false;
+ dotIndex[dotCount] = i;
+ dotCount++;
+ } else if (current == 'X' || current == 'x') {
+ if (!lastWasNumber || // An X should not follow an X or a dot or be first
+ i == (length - 1) || // No trailing Xs allowed
+ (dotCount == 0 &&
+ i != 1) || // If we had no dots, an X should be second
+ host[i - 1] != '0' || // X should always follow a 0. Guaranteed i >
+ // 0 as lastWasNumber is true
+ (dotCount > 0 &&
+ host[i - 2] != '.')) { // And that zero follows a dot if it exists
+ return -1;
+ }
+ lastWasNumber = false;
+ bases[dotCount] = 16;
+ onlyBase10 = false;
+
+ } else if (current == '0') {
+ if (i < length - 1 && // Trailing zero doesn't signal octal
+ host[i + 1] != '.' && // Lone zero is not octal
+ (i == 0 || host[i - 1] == '.')) { // Zero at start or following a dot
+ // is a candidate for octal
+ bases[dotCount] = 8; // This will turn to 16 above if X shows up
+ onlyBase10 = false;
+ }
+ lastWasNumber = true;
+
+ } else if (current >= '1' && current <= '7') {
+ lastWasNumber = true;
+
+ } else if (current >= '8' && current <= '9') {
+ if (bases[dotCount] == 8) {
+ return -1;
+ }
+ lastWasNumber = true;
+
+ } else if ((current >= 'a' && current <= 'f') ||
+ (current >= 'A' && current <= 'F')) {
+ if (bases[dotCount] != 16) {
+ return -1;
+ }
+ lastWasNumber = true;
+
+ } else {
+ return -1;
+ }
+ }
+
+ return dotCount;
+}
+
+inline nsresult ParseIPv4Number10(const nsACString& input, uint32_t& number,
+ uint32_t maxNumber) {
+ uint64_t value = 0;
+ const char* current = input.BeginReading();
+ const char* end = input.EndReading();
+ for (; current < end; ++current) {
+ char c = *current;
+ MOZ_ASSERT(c >= '0' && c <= '9');
+ value *= 10;
+ value += c - '0';
+ }
+ if (value <= maxNumber) {
+ number = value;
+ return NS_OK;
+ }
+
+ // The error case
+ number = 0;
+ return NS_ERROR_FAILURE;
+}
+
+inline nsresult ParseIPv4Number(const nsACString& input, int32_t base,
+ uint32_t& number, uint32_t maxNumber) {
+ // Accumulate in the 64-bit value
+ uint64_t value = 0;
+ const char* current = input.BeginReading();
+ const char* end = input.EndReading();
+ switch (base) {
+ case 16:
+ ++current;
+ [[fallthrough]];
+ case 8:
+ ++current;
+ break;
+ case 10:
+ default:
+ break;
+ }
+ for (; current < end; ++current) {
+ value *= base;
+ char c = *current;
+ MOZ_ASSERT((base == 10 && IsAsciiDigit(c)) ||
+ (base == 8 && c >= '0' && c <= '7') ||
+ (base == 16 && IsAsciiHexDigit(c)));
+ if (IsAsciiDigit(c)) {
+ value += c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ value += c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ value += c - 'A' + 10;
+ }
+ }
+
+ if (value <= maxNumber) {
+ number = value;
+ return NS_OK;
+ }
+
+ // The error case
+ number = 0;
+ return NS_ERROR_FAILURE;
+}
+
+// IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser
+/* static */
+nsresult nsStandardURL::NormalizeIPv4(const nsACString& host,
+ nsCString& result) {
+ int32_t bases[4] = {10, 10, 10, 10};
+ bool onlyBase10 = true; // Track this as a special case
+ int32_t dotIndex[3]; // The positions of the dots in the string
+
+ // Use "length" rather than host.Length() after call to
+ // ValidateIPv4Number because of potential trailing period.
+ nsDependentCSubstring filteredHost;
+ bool trailingDot = false;
+ if (host.Length() > 0 && host.Last() == '.') {
+ trailingDot = true;
+ filteredHost.Rebind(host.BeginReading(), host.Length() - 1);
+ } else {
+ filteredHost.Rebind(host.BeginReading(), host.Length());
+ }
+
+ int32_t length = static_cast<int32_t>(filteredHost.Length());
+ int32_t dotCount = ValidateIPv4Number(filteredHost, bases, dotIndex,
+ onlyBase10, length, trailingDot);
+ if (dotCount < 0 || length <= 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Max values specified by the spec
+ static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu, 0xffffu,
+ 0xffu};
+ uint32_t ipv4;
+ int32_t start = (dotCount > 0 ? dotIndex[dotCount - 1] + 1 : 0);
+
+ // parse the last part first
+ nsresult res;
+ // Doing a special case for all items being base 10 gives ~35% speedup
+ res = (onlyBase10
+ ? ParseIPv4Number10(Substring(host, start, length - start), ipv4,
+ upperBounds[dotCount])
+ : ParseIPv4Number(Substring(host, start, length - start),
+ bases[dotCount], ipv4, upperBounds[dotCount]));
+ if (NS_FAILED(res)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // parse remaining parts starting from first part
+ int32_t lastUsed = -1;
+ for (int32_t i = 0; i < dotCount; i++) {
+ uint32_t number;
+ start = lastUsed + 1;
+ lastUsed = dotIndex[i];
+ res =
+ (onlyBase10 ? ParseIPv4Number10(
+ Substring(host, start, lastUsed - start), number, 255)
+ : ParseIPv4Number(Substring(host, start, lastUsed - start),
+ bases[i], number, 255));
+ if (NS_FAILED(res)) {
+ return NS_ERROR_FAILURE;
+ }
+ ipv4 += number << (8 * (3 - i));
+ }
+
+ // A special case for ipv4 URL like "127." should have the same result as
+ // "127".
+ if (dotCount == 1 && dotIndex[0] == length - 1) {
+ ipv4 = (ipv4 & 0xff000000) >> 24;
+ }
+
+ uint8_t ipSegments[4];
+ NetworkEndian::writeUint32(ipSegments, ipv4);
+ result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1],
+ ipSegments[2], ipSegments[3]);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::NormalizeIDN(const nsCString& host, nsCString& result) {
+ result.Truncate();
+ mDisplayHost.Truncate();
+ nsresult rv;
+
+ if (!gIDN) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Even if it's already ACE, we must still call ConvertUTF8toACE in order
+ // for the input normalization to take place.
+ rv = gIDN->ConvertUTF8toACE(host, result);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If the ASCII representation doesn't contain the xn-- token then we don't
+ // need to call ConvertToDisplayIDN as that would not change anything.
+ if (!StringBeginsWith(result, "xn--"_ns) &&
+ result.Find(".xn--"_ns) == kNotFound) {
+ mCheckedIfHostA = true;
+ return NS_OK;
+ }
+
+ bool isAscii = true;
+ nsAutoCString displayHost;
+ rv = gIDN->ConvertToDisplayIDN(result, &isAscii, displayHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mCheckedIfHostA = true;
+ if (!isAscii) {
+ mDisplayHost = displayHost;
+ }
+ return NS_OK;
+}
+
+bool nsStandardURL::ValidIPv6orHostname(const char* host, uint32_t length) {
+ if (!host || !*host) {
+ // Should not be NULL or empty string
+ return false;
+ }
+
+ if (length != strlen(host)) {
+ // Embedded null
+ return false;
+ }
+
+ bool openBracket = host[0] == '[';
+ bool closeBracket = host[length - 1] == ']';
+
+ if (openBracket && closeBracket) {
+ return net_IsValidIPv6Addr(Substring(host + 1, length - 2));
+ }
+
+ if (openBracket || closeBracket) {
+ // Fail if only one of the brackets is present
+ return false;
+ }
+
+ const char* end = host + length;
+ const char* iter = host;
+ for (; iter != end && *iter; ++iter) {
+ if (ASCIIMask::IsMasked(sInvalidHostChars, *iter)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char* path) {
+ net_CoalesceDirs(coalesceFlag, path);
+ int32_t newLen = strlen(path);
+ if (newLen < mPath.mLen) {
+ int32_t diff = newLen - mPath.mLen;
+ mPath.mLen = newLen;
+ mDirectory.mLen += diff;
+ mFilepath.mLen += diff;
+ ShiftFromBasename(diff);
+ }
+}
+
+uint32_t nsStandardURL::AppendSegmentToBuf(char* buf, uint32_t i,
+ const char* str,
+ const URLSegment& segInput,
+ URLSegment& segOutput,
+ const nsCString* escapedStr,
+ bool useEscaped, int32_t* diff) {
+ MOZ_ASSERT(segInput.mLen == segOutput.mLen);
+
+ if (diff) {
+ *diff = 0;
+ }
+
+ if (segInput.mLen > 0) {
+ if (useEscaped) {
+ MOZ_ASSERT(diff);
+ segOutput.mLen = escapedStr->Length();
+ *diff = segOutput.mLen - segInput.mLen;
+ memcpy(buf + i, escapedStr->get(), segOutput.mLen);
+ } else {
+ memcpy(buf + i, str + segInput.mPos, segInput.mLen);
+ }
+ segOutput.mPos = i;
+ i += segOutput.mLen;
+ } else {
+ segOutput.mPos = i;
+ }
+ return i;
+}
+
+uint32_t nsStandardURL::AppendToBuf(char* buf, uint32_t i, const char* str,
+ uint32_t len) {
+ memcpy(buf + i, str, len);
+ return i + len;
+}
+
+static bool ContainsOnlyAsciiDigits(const nsDependentCSubstring& input) {
+ for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
+ if (!IsAsciiDigit(*c)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool ContainsOnlyAsciiHexDigits(const nsDependentCSubstring& input) {
+ for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
+ if (!IsAsciiHexDigit(*c)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// https://url.spec.whatwg.org/#ends-in-a-number-checker
+static bool EndsInANumber(const nsCString& input) {
+ // 1. Let parts be the result of strictly splitting input on U+002E (.).
+ nsTArray<nsDependentCSubstring> parts;
+ for (const nsDependentCSubstring& part : input.Split('.')) {
+ parts.AppendElement(part);
+ }
+
+ if (parts.Length() == 0) {
+ return false;
+ }
+
+ // 2.If the last item in parts is the empty string, then:
+ // 1. If parts’s size is 1, then return false.
+ // 2. Remove the last item from parts.
+ if (parts.LastElement().IsEmpty()) {
+ if (parts.Length() == 1) {
+ return false;
+ }
+ Unused << parts.PopLastElement();
+ }
+
+ // 3. Let last be the last item in parts.
+ const nsDependentCSubstring& last = parts.LastElement();
+
+ // 4. If last is non-empty and contains only ASCII digits, then return true.
+ // The erroneous input "09" will be caught by the IPv4 parser at a later
+ // stage.
+ if (!last.IsEmpty()) {
+ if (ContainsOnlyAsciiDigits(last)) {
+ return true;
+ }
+ }
+
+ // 5. If parsing last as an IPv4 number does not return failure, then return
+ // true. This is equivalent to checking that last is "0X" or "0x", followed by
+ // zero or more ASCII hex digits.
+ if (StringBeginsWith(last, "0x"_ns) || StringBeginsWith(last, "0X"_ns)) {
+ if (ContainsOnlyAsciiHexDigits(Substring(last, 2))) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// basic algorithm:
+// 1- escape url segments (for improved GetSpec efficiency)
+// 2- allocate spec buffer
+// 3- write url segments
+// 4- update url segment positions and lengths
+nsresult nsStandardURL::BuildNormalizedSpec(const char* spec,
+ const Encoding* encoding) {
+ // Assumptions: all member URLSegments must be relative the |spec| argument
+ // passed to this function.
+
+ // buffers for holding escaped url segments (these will remain empty unless
+ // escaping is required).
+ nsAutoCString encUsername, encPassword, encHost, encDirectory, encBasename,
+ encExtension, encQuery, encRef;
+ bool useEncUsername, useEncPassword, useEncHost = false, useEncDirectory,
+ useEncBasename, useEncExtension,
+ useEncQuery, useEncRef;
+ nsAutoCString portbuf;
+
+ //
+ // escape each URL segment, if necessary, and calculate approximate normalized
+ // spec length.
+ //
+ // [scheme://][username[:password]@]host[:port]/path[?query_string][#ref]
+
+ uint32_t approxLen = 0;
+
+ // the scheme is already ASCII
+ if (mScheme.mLen > 0) {
+ approxLen +=
+ mScheme.mLen + 3; // includes room for "://", which we insert always
+ }
+
+ // encode URL segments; convert UTF-8 to origin charset and possibly escape.
+ // results written to encXXX variables only if |spec| is not already in the
+ // appropriate encoding.
+ {
+ nsSegmentEncoder encoder;
+ nsSegmentEncoder queryEncoder(encoding);
+ // Username@
+ approxLen += encoder.EncodeSegmentCount(spec, mUsername, esc_Username,
+ encUsername, useEncUsername, 0);
+ approxLen += 1; // reserve length for @
+ // :password - we insert the ':' even if there's no actual password if
+ // "user:@" was in the spec
+ if (mPassword.mLen > 0) {
+ approxLen += 1 + encoder.EncodeSegmentCount(spec, mPassword, esc_Password,
+ encPassword, useEncPassword);
+ }
+ // mHost is handled differently below due to encoding differences
+ MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
+ if (mPort != -1 && mPort != mDefaultPort) {
+ // :port
+ portbuf.AppendInt(mPort);
+ approxLen += portbuf.Length() + 1;
+ }
+
+ approxLen +=
+ 1; // reserve space for possible leading '/' - may not be needed
+ // Should just use mPath? These are pessimistic, and thus waste space
+ approxLen += encoder.EncodeSegmentCount(spec, mDirectory, esc_Directory,
+ encDirectory, useEncDirectory, 1);
+ approxLen += encoder.EncodeSegmentCount(spec, mBasename, esc_FileBaseName,
+ encBasename, useEncBasename);
+ approxLen += encoder.EncodeSegmentCount(spec, mExtension, esc_FileExtension,
+ encExtension, useEncExtension, 1);
+
+ // These next ones *always* add their leading character even if length is 0
+ // Handles items like "http://#"
+ // ?query
+ if (mQuery.mLen >= 0) {
+ approxLen += 1 + queryEncoder.EncodeSegmentCount(spec, mQuery, esc_Query,
+ encQuery, useEncQuery);
+ }
+ // #ref
+
+ if (mRef.mLen >= 0) {
+ approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref, encRef,
+ useEncRef);
+ }
+ }
+
+ // do not escape the hostname, if IPv6 address literal, mHost will
+ // already point to a [ ] delimited IPv6 address literal.
+ // However, perform Unicode normalization on it, as IDN does.
+ // Note that we don't disallow URLs without a host - file:, etc
+ if (mHost.mLen > 0) {
+ nsAutoCString tempHost;
+ NS_UnescapeURL(spec + mHost.mPos, mHost.mLen, esc_AlwaysCopy | esc_Host,
+ tempHost);
+ if (tempHost.Contains('\0')) {
+ return NS_ERROR_MALFORMED_URI; // null embedded in hostname
+ }
+ if (tempHost.Contains(' ')) {
+ return NS_ERROR_MALFORMED_URI; // don't allow spaces in the hostname
+ }
+ nsresult rv = NormalizeIDN(tempHost, encHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!SegmentIs(spec, mScheme, "resource") &&
+ !SegmentIs(spec, mScheme, "chrome")) {
+ nsAutoCString ipString;
+ if (encHost.Length() > 0 && encHost.First() == '[' &&
+ encHost.Last() == ']' &&
+ ValidIPv6orHostname(encHost.get(), encHost.Length())) {
+ rv = (nsresult)rusturl_parse_ipv6addr(&encHost, &ipString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ encHost = ipString;
+ } else {
+ if (EndsInANumber(encHost)) {
+ rv = NormalizeIPv4(encHost, ipString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ encHost = ipString;
+ }
+ }
+ }
+
+ // NormalizeIDN always copies, if the call was successful.
+ useEncHost = true;
+ approxLen += encHost.Length();
+
+ if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ } else {
+ // empty host means empty mDisplayHost
+ mDisplayHost.Truncate();
+ mCheckedIfHostA = true;
+ }
+
+ // We must take a copy of every single segment because they are pointing to
+ // the |spec| while we are changing their value, in case we must use
+ // encoded strings.
+ URLSegment username(mUsername);
+ URLSegment password(mPassword);
+ URLSegment host(mHost);
+ URLSegment path(mPath);
+ URLSegment directory(mDirectory);
+ URLSegment basename(mBasename);
+ URLSegment extension(mExtension);
+ URLSegment query(mQuery);
+ URLSegment ref(mRef);
+
+ // The encoded string could be longer than the original input, so we need
+ // to check the final URI isn't longer than the max length.
+ if (approxLen + 1 > StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ //
+ // generate the normalized URL string
+ //
+ // approxLen should be correct or 1 high
+ if (!mSpec.SetLength(approxLen + 1,
+ fallible)) { // buf needs a trailing '\0' below
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ char* buf = mSpec.BeginWriting();
+ uint32_t i = 0;
+ int32_t diff = 0;
+
+ if (mScheme.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, mScheme, mScheme);
+ net_ToLowerCase(buf + mScheme.mPos, mScheme.mLen);
+ i = AppendToBuf(buf, i, "://", 3);
+ }
+
+ // record authority starting position
+ mAuthority.mPos = i;
+
+ // append authority
+ if (mUsername.mLen > 0 || mPassword.mLen > 0) {
+ if (mUsername.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, username, mUsername, &encUsername,
+ useEncUsername, &diff);
+ ShiftFromPassword(diff);
+ } else {
+ mUsername.mLen = -1;
+ }
+ if (password.mLen > 0) {
+ buf[i++] = ':';
+ i = AppendSegmentToBuf(buf, i, spec, password, mPassword, &encPassword,
+ useEncPassword, &diff);
+ ShiftFromHost(diff);
+ } else {
+ mPassword.mLen = -1;
+ }
+ buf[i++] = '@';
+ } else {
+ mUsername.mLen = -1;
+ mPassword.mLen = -1;
+ }
+ if (host.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, host, mHost, &encHost, useEncHost,
+ &diff);
+ ShiftFromPath(diff);
+
+ net_ToLowerCase(buf + mHost.mPos, mHost.mLen);
+ MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
+ if (mPort != -1 && mPort != mDefaultPort) {
+ buf[i++] = ':';
+ // Already formatted while building approxLen
+ i = AppendToBuf(buf, i, portbuf.get(), portbuf.Length());
+ }
+ }
+
+ // record authority length
+ mAuthority.mLen = i - mAuthority.mPos;
+
+ // path must always start with a "/"
+ if (mPath.mLen <= 0) {
+ LOG(("setting path=/"));
+ mDirectory.mPos = mFilepath.mPos = mPath.mPos = i;
+ mDirectory.mLen = mFilepath.mLen = mPath.mLen = 1;
+ // basename must exist, even if empty (bug 113508)
+ mBasename.mPos = i + 1;
+ mBasename.mLen = 0;
+ buf[i++] = '/';
+ } else {
+ uint32_t leadingSlash = 0;
+ if (spec[path.mPos] != '/') {
+ LOG(("adding leading slash to path\n"));
+ leadingSlash = 1;
+ buf[i++] = '/';
+ // basename must exist, even if empty (bugs 113508, 429347)
+ if (mBasename.mLen == -1) {
+ mBasename.mPos = basename.mPos = i;
+ mBasename.mLen = basename.mLen = 0;
+ }
+ }
+
+ // record corrected (file)path starting position
+ mPath.mPos = mFilepath.mPos = i - leadingSlash;
+
+ i = AppendSegmentToBuf(buf, i, spec, directory, mDirectory, &encDirectory,
+ useEncDirectory, &diff);
+ ShiftFromBasename(diff);
+
+ // the directory must end with a '/'
+ if (buf[i - 1] != '/') {
+ buf[i++] = '/';
+ mDirectory.mLen++;
+ }
+
+ i = AppendSegmentToBuf(buf, i, spec, basename, mBasename, &encBasename,
+ useEncBasename, &diff);
+ ShiftFromExtension(diff);
+
+ // make corrections to directory segment if leadingSlash
+ if (leadingSlash) {
+ mDirectory.mPos = mPath.mPos;
+ if (mDirectory.mLen >= 0) {
+ mDirectory.mLen += leadingSlash;
+ } else {
+ mDirectory.mLen = 1;
+ }
+ }
+
+ if (mExtension.mLen >= 0) {
+ buf[i++] = '.';
+ i = AppendSegmentToBuf(buf, i, spec, extension, mExtension, &encExtension,
+ useEncExtension, &diff);
+ ShiftFromQuery(diff);
+ }
+ // calculate corrected filepath length
+ mFilepath.mLen = i - mFilepath.mPos;
+
+ if (mQuery.mLen >= 0) {
+ buf[i++] = '?';
+ i = AppendSegmentToBuf(buf, i, spec, query, mQuery, &encQuery,
+ useEncQuery, &diff);
+ ShiftFromRef(diff);
+ }
+ if (mRef.mLen >= 0) {
+ buf[i++] = '#';
+ i = AppendSegmentToBuf(buf, i, spec, ref, mRef, &encRef, useEncRef,
+ &diff);
+ }
+ // calculate corrected path length
+ mPath.mLen = i - mPath.mPos;
+ }
+
+ buf[i] = '\0';
+
+ // https://url.spec.whatwg.org/#path-state (1.4.1.2)
+ // https://url.spec.whatwg.org/#windows-drive-letter
+ if (SegmentIs(buf, mScheme, "file")) {
+ char* path = &buf[mPath.mPos];
+ if (mPath.mLen >= 3 && path[0] == '/' && IsAsciiAlpha(path[1]) &&
+ path[2] == '|') {
+ buf[mPath.mPos + 2] = ':';
+ }
+ }
+
+ if (mDirectory.mLen > 1) {
+ netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
+ if (SegmentIs(buf, mScheme, "ftp")) {
+ coalesceFlag =
+ (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT |
+ NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+ }
+ CoalescePath(coalesceFlag, buf + mDirectory.mPos);
+ }
+ mSpec.Truncate(strlen(buf));
+ NS_ASSERTION(mSpec.Length() <= approxLen,
+ "We've overflowed the mSpec buffer!");
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "The spec should never be this long, we missed a check.");
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+}
+
+bool nsStandardURL::SegmentIs(const URLSegment& seg, const char* val,
+ bool ignoreCase) {
+ // one or both may be null
+ if (!val || mSpec.IsEmpty()) {
+ return (!val && (mSpec.IsEmpty() || seg.mLen < 0));
+ }
+ if (seg.mLen < 0) {
+ return false;
+ }
+ // if the first |seg.mLen| chars of |val| match, then |val| must
+ // also be null terminated at |seg.mLen|.
+ if (ignoreCase) {
+ return !nsCRT::strncasecmp(mSpec.get() + seg.mPos, val, seg.mLen) &&
+ (val[seg.mLen] == '\0');
+ }
+
+ return !strncmp(mSpec.get() + seg.mPos, val, seg.mLen) &&
+ (val[seg.mLen] == '\0');
+}
+
+bool nsStandardURL::SegmentIs(const char* spec, const URLSegment& seg,
+ const char* val, bool ignoreCase) {
+ // one or both may be null
+ if (!val || !spec) {
+ return (!val && (!spec || seg.mLen < 0));
+ }
+ if (seg.mLen < 0) {
+ return false;
+ }
+ // if the first |seg.mLen| chars of |val| match, then |val| must
+ // also be null terminated at |seg.mLen|.
+ if (ignoreCase) {
+ return !nsCRT::strncasecmp(spec + seg.mPos, val, seg.mLen) &&
+ (val[seg.mLen] == '\0');
+ }
+
+ return !strncmp(spec + seg.mPos, val, seg.mLen) && (val[seg.mLen] == '\0');
+}
+
+bool nsStandardURL::SegmentIs(const URLSegment& seg1, const char* val,
+ const URLSegment& seg2, bool ignoreCase) {
+ if (seg1.mLen != seg2.mLen) {
+ return false;
+ }
+ if (seg1.mLen == -1 || (!val && mSpec.IsEmpty())) {
+ return true; // both are empty
+ }
+ if (!val) {
+ return false;
+ }
+ if (ignoreCase) {
+ return !nsCRT::strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos,
+ seg1.mLen);
+ }
+
+ return !strncmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
+}
+
+int32_t nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len,
+ const char* val, uint32_t valLen) {
+ if (val && valLen) {
+ if (len == 0) {
+ mSpec.Insert(val, pos, valLen);
+ } else {
+ mSpec.Replace(pos, len, nsDependentCString(val, valLen));
+ }
+ return valLen - len;
+ }
+
+ // else remove the specified segment
+ mSpec.Cut(pos, len);
+ return -int32_t(len);
+}
+
+int32_t nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len,
+ const nsACString& val) {
+ if (len == 0) {
+ mSpec.Insert(val, pos);
+ } else {
+ mSpec.Replace(pos, len, val);
+ }
+ return val.Length() - len;
+}
+
+nsresult nsStandardURL::ParseURL(const char* spec, int32_t specLen) {
+ nsresult rv;
+
+ if (specLen > (int32_t)StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ //
+ // parse given URL string
+ //
+ uint32_t schemePos = mScheme.mPos;
+ int32_t schemeLen = mScheme.mLen;
+ uint32_t authorityPos = mAuthority.mPos;
+ int32_t authorityLen = mAuthority.mLen;
+ uint32_t pathPos = mPath.mPos;
+ int32_t pathLen = mPath.mLen;
+ rv = mParser->ParseURL(spec, specLen, &schemePos, &schemeLen, &authorityPos,
+ &authorityLen, &pathPos, &pathLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mScheme.mPos = schemePos;
+ mScheme.mLen = schemeLen;
+ mAuthority.mPos = authorityPos;
+ mAuthority.mLen = authorityLen;
+ mPath.mPos = pathPos;
+ mPath.mLen = pathLen;
+
+#ifdef DEBUG
+ if (mScheme.mLen <= 0) {
+ printf("spec=%s\n", spec);
+ NS_WARNING("malformed url: no scheme");
+ }
+#endif
+
+ if (mAuthority.mLen > 0) {
+ uint32_t usernamePos = mUsername.mPos;
+ int32_t usernameLen = mUsername.mLen;
+ uint32_t passwordPos = mPassword.mPos;
+ int32_t passwordLen = mPassword.mLen;
+ uint32_t hostPos = mHost.mPos;
+ int32_t hostLen = mHost.mLen;
+ rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen,
+ &usernamePos, &usernameLen, &passwordPos,
+ &passwordLen, &hostPos, &hostLen, &mPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mUsername.mPos = usernamePos;
+ mUsername.mLen = usernameLen;
+ mPassword.mPos = passwordPos;
+ mPassword.mLen = passwordLen;
+ mHost.mPos = hostPos;
+ mHost.mLen = hostLen;
+
+ // Don't allow mPort to be set to this URI's default port
+ if (mPort == mDefaultPort) {
+ mPort = -1;
+ }
+
+ mUsername.mPos += mAuthority.mPos;
+ mPassword.mPos += mAuthority.mPos;
+ mHost.mPos += mAuthority.mPos;
+ }
+
+ if (mPath.mLen > 0) {
+ rv = ParsePath(spec, mPath.mPos, mPath.mLen);
+ }
+
+ return rv;
+}
+
+nsresult nsStandardURL::ParsePath(const char* spec, uint32_t pathPos,
+ int32_t pathLen) {
+ LOG(("ParsePath: %s pathpos %d len %d\n", spec, pathPos, pathLen));
+
+ if (pathLen > (int32_t)StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ uint32_t filePathPos = mFilepath.mPos;
+ int32_t filePathLen = mFilepath.mLen;
+ uint32_t queryPos = mQuery.mPos;
+ int32_t queryLen = mQuery.mLen;
+ uint32_t refPos = mRef.mPos;
+ int32_t refLen = mRef.mLen;
+ nsresult rv =
+ mParser->ParsePath(spec + pathPos, pathLen, &filePathPos, &filePathLen,
+ &queryPos, &queryLen, &refPos, &refLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mFilepath.mPos = filePathPos;
+ mFilepath.mLen = filePathLen;
+ mQuery.mPos = queryPos;
+ mQuery.mLen = queryLen;
+ mRef.mPos = refPos;
+ mRef.mLen = refLen;
+
+ mFilepath.mPos += pathPos;
+ mQuery.mPos += pathPos;
+ mRef.mPos += pathPos;
+
+ if (mFilepath.mLen > 0) {
+ uint32_t directoryPos = mDirectory.mPos;
+ int32_t directoryLen = mDirectory.mLen;
+ uint32_t basenamePos = mBasename.mPos;
+ int32_t basenameLen = mBasename.mLen;
+ uint32_t extensionPos = mExtension.mPos;
+ int32_t extensionLen = mExtension.mLen;
+ rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen,
+ &directoryPos, &directoryLen, &basenamePos,
+ &basenameLen, &extensionPos, &extensionLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mDirectory.mPos = directoryPos;
+ mDirectory.mLen = directoryLen;
+ mBasename.mPos = basenamePos;
+ mBasename.mLen = basenameLen;
+ mExtension.mPos = extensionPos;
+ mExtension.mLen = extensionLen;
+
+ mDirectory.mPos += mFilepath.mPos;
+ mBasename.mPos += mFilepath.mPos;
+ mExtension.mPos += mFilepath.mPos;
+ }
+ return NS_OK;
+}
+
+char* nsStandardURL::AppendToSubstring(uint32_t pos, int32_t len,
+ const char* tail) {
+ // Verify pos and length are within boundaries
+ if (pos > mSpec.Length()) {
+ return nullptr;
+ }
+ if (len < 0) {
+ return nullptr;
+ }
+ if ((uint32_t)len > (mSpec.Length() - pos)) {
+ return nullptr;
+ }
+ if (!tail) {
+ return nullptr;
+ }
+
+ uint32_t tailLen = strlen(tail);
+
+ // Check for int overflow for proposed length of combined string
+ if (UINT32_MAX - ((uint32_t)len + 1) < tailLen) {
+ return nullptr;
+ }
+
+ char* result = (char*)moz_xmalloc(len + tailLen + 1);
+ memcpy(result, mSpec.get() + pos, len);
+ memcpy(result + len, tail, tailLen);
+ result[len + tailLen] = '\0';
+ return result;
+}
+
+nsresult nsStandardURL::ReadSegment(nsIBinaryInputStream* stream,
+ URLSegment& seg) {
+ nsresult rv;
+
+ uint32_t pos = seg.mPos;
+ rv = stream->Read32(&pos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ seg.mPos = pos;
+
+ uint32_t len = seg.mLen;
+ rv = stream->Read32(&len);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ CheckedInt<int32_t> checkedLen(len);
+ if (!checkedLen.isValid()) {
+ seg.mLen = -1;
+ } else {
+ seg.mLen = len;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsStandardURL::WriteSegment(nsIBinaryOutputStream* stream,
+ const URLSegment& seg) {
+ nsresult rv;
+
+ rv = stream->Write32(seg.mPos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Write32(uint32_t(seg.mLen));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+#define SHIFT_FROM(name, what) \
+ void nsStandardURL::name(int32_t diff) { \
+ if (!diff) return; \
+ if ((what).mLen >= 0) { \
+ CheckedInt<int32_t> pos = (uint32_t)(what).mPos; \
+ pos += diff; \
+ MOZ_ASSERT(pos.isValid()); \
+ (what).mPos = pos.value(); \
+ } else { \
+ MOZ_RELEASE_ASSERT((what).mLen == -1); \
+ }
+
+#define SHIFT_FROM_NEXT(name, what, next) \
+ SHIFT_FROM(name, what) \
+ next(diff); \
+ }
+
+#define SHIFT_FROM_LAST(name, what) \
+ SHIFT_FROM(name, what) \
+ }
+
+SHIFT_FROM_NEXT(ShiftFromAuthority, mAuthority, ShiftFromUsername)
+SHIFT_FROM_NEXT(ShiftFromUsername, mUsername, ShiftFromPassword)
+SHIFT_FROM_NEXT(ShiftFromPassword, mPassword, ShiftFromHost)
+SHIFT_FROM_NEXT(ShiftFromHost, mHost, ShiftFromPath)
+SHIFT_FROM_NEXT(ShiftFromPath, mPath, ShiftFromFilepath)
+SHIFT_FROM_NEXT(ShiftFromFilepath, mFilepath, ShiftFromDirectory)
+SHIFT_FROM_NEXT(ShiftFromDirectory, mDirectory, ShiftFromBasename)
+SHIFT_FROM_NEXT(ShiftFromBasename, mBasename, ShiftFromExtension)
+SHIFT_FROM_NEXT(ShiftFromExtension, mExtension, ShiftFromQuery)
+SHIFT_FROM_NEXT(ShiftFromQuery, mQuery, ShiftFromRef)
+SHIFT_FROM_LAST(ShiftFromRef, mRef)
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIClassInfo
+//----------------------------------------------------------------------------
+
+NS_IMPL_CLASSINFO(nsStandardURL, nullptr, nsIClassInfo::THREADSAFE,
+ NS_STANDARDURL_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(nsStandardURL)
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISupports
+//----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsStandardURL)
+NS_IMPL_RELEASE(nsStandardURL)
+
+NS_INTERFACE_MAP_BEGIN(nsStandardURL)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL)
+ NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ NS_IMPL_QUERY_CLASSINFO(nsStandardURL)
+ NS_INTERFACE_MAP_ENTRY(nsISensitiveInfoHiddenURI)
+ // see nsStandardURL::Equals
+ if (aIID.Equals(kThisImplCID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsISizeOf)
+NS_INTERFACE_MAP_END
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIURI
+//----------------------------------------------------------------------------
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSpec(nsACString& result) {
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "The spec should never be this long, we missed a check.");
+ result = mSpec;
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString& result) {
+ nsresult rv = GetSpec(result);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (mPassword.mLen > 0) {
+ result.ReplaceLiteral(mPassword.mPos, mPassword.mLen, "****");
+ }
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetSpecIgnoringRef(nsACString& result) {
+ // URI without ref is 0 to one char before ref
+ if (mRef.mLen < 0) {
+ return GetSpec(result);
+ }
+
+ URLSegment noRef(0, mRef.mPos - 1);
+ result = Segment(noRef);
+ MOZ_ASSERT(mCheckedIfHostA);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::CheckIfHostIsAscii() {
+ nsresult rv;
+ if (mCheckedIfHostA) {
+ return NS_OK;
+ }
+
+ mCheckedIfHostA = true;
+
+ if (!gIDN) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsAutoCString displayHost;
+ bool isAscii;
+ rv = gIDN->ConvertToDisplayIDN(Host(), &isAscii, displayHost);
+ if (NS_FAILED(rv)) {
+ mDisplayHost.Truncate();
+ mCheckedIfHostA = false;
+ return rv;
+ }
+
+ if (!isAscii) {
+ mDisplayHost = displayHost;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetDisplaySpec(nsACString& aUnicodeSpec) {
+ aUnicodeSpec.Assign(mSpec);
+ MOZ_ASSERT(mCheckedIfHostA);
+ if (!mDisplayHost.IsEmpty()) {
+ aUnicodeSpec.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
+ nsAutoCString unicodeHostPort;
+
+ nsresult rv = GetDisplayHost(unicodeHostPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (StringBeginsWith(Hostport(), "["_ns)) {
+ aUnicodeHostPort.AssignLiteral("[");
+ aUnicodeHostPort.Append(unicodeHostPort);
+ aUnicodeHostPort.AppendLiteral("]");
+ } else {
+ aUnicodeHostPort.Assign(unicodeHostPort);
+ }
+
+ uint32_t pos = mHost.mPos + mHost.mLen;
+ if (pos < mPath.mPos) {
+ aUnicodeHostPort += Substring(mSpec, pos, mPath.mPos - pos);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetDisplayHost(nsACString& aUnicodeHost) {
+ MOZ_ASSERT(mCheckedIfHostA);
+ if (mDisplayHost.IsEmpty()) {
+ return GetAsciiHost(aUnicodeHost);
+ }
+
+ aUnicodeHost = mDisplayHost;
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPrePath(nsACString& result) {
+ result = Prepath();
+ MOZ_ASSERT(mCheckedIfHostA);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetDisplayPrePath(nsACString& result) {
+ result = Prepath();
+ MOZ_ASSERT(mCheckedIfHostA);
+ if (!mDisplayHost.IsEmpty()) {
+ result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
+ }
+ return NS_OK;
+}
+
+// result is strictly US-ASCII
+NS_IMETHODIMP
+nsStandardURL::GetScheme(nsACString& result) {
+ result = Scheme();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetUserPass(nsACString& result) {
+ result = Userpass();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetUsername(nsACString& result) {
+ result = Username();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPassword(nsACString& result) {
+ result = Password();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHostPort(nsACString& result) {
+ return GetAsciiHostPort(result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHost(nsACString& result) { return GetAsciiHost(result); }
+
+NS_IMETHODIMP
+nsStandardURL::GetPort(int32_t* result) {
+ // should never be more than 16 bit
+ MOZ_ASSERT(mPort <= std::numeric_limits<uint16_t>::max());
+ *result = mPort;
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetPathQueryRef(nsACString& result) {
+ result = Path();
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiSpec(nsACString& result) {
+ result = mSpec;
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiHostPort(nsACString& result) {
+ result = Hostport();
+ return NS_OK;
+}
+
+// result is ASCII
+NS_IMETHODIMP
+nsStandardURL::GetAsciiHost(nsACString& result) {
+ result = Host();
+ return NS_OK;
+}
+
+static bool IsSpecialProtocol(const nsACString& input) {
+ nsACString::const_iterator start, end;
+ input.BeginReading(start);
+ nsACString::const_iterator iterator(start);
+ input.EndReading(end);
+
+ while (iterator != end && *iterator != ':') {
+ iterator++;
+ }
+
+ nsAutoCString protocol(nsDependentCSubstring(start.get(), iterator.get()));
+
+ return protocol.LowerCaseEqualsLiteral("http") ||
+ protocol.LowerCaseEqualsLiteral("https") ||
+ protocol.LowerCaseEqualsLiteral("ftp") ||
+ protocol.LowerCaseEqualsLiteral("ws") ||
+ protocol.LowerCaseEqualsLiteral("wss") ||
+ protocol.LowerCaseEqualsLiteral("file") ||
+ protocol.LowerCaseEqualsLiteral("gopher");
+}
+
+nsresult nsStandardURL::SetSpecInternal(const nsACString& input) {
+ return SetSpecWithEncoding(input, nullptr);
+}
+
+nsresult nsStandardURL::SetSpecWithEncoding(const nsACString& input,
+ const Encoding* encoding) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get()));
+
+ if (input.Length() > StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI;
+ net_FilterURIString(flat, filteredURI);
+
+ if (filteredURI.Length() == 0) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // Make a backup of the current URL
+ nsStandardURL prevURL(false, false);
+ prevURL.CopyMembers(this, eHonorRef, ""_ns);
+ Clear();
+
+ if (IsSpecialProtocol(filteredURI)) {
+ // Bug 652186: Replace all backslashes with slashes when parsing paths
+ // Stop when we reach the query or the hash.
+ auto* start = filteredURI.BeginWriting();
+ auto* end = filteredURI.EndWriting();
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ const char* spec = filteredURI.get();
+ int32_t specLength = filteredURI.Length();
+
+ // parse the given URL...
+ nsresult rv = ParseURL(spec, specLength);
+ if (mScheme.mLen <= 0) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ if (NS_SUCCEEDED(rv)) {
+ // finally, use the URLSegment member variables to build a normalized
+ // copy of |spec|
+ rv = BuildNormalizedSpec(spec, encoding);
+ }
+
+ // Make sure that a URLTYPE_AUTHORITY has a non-empty hostname.
+ if (mURLType == URLTYPE_AUTHORITY && mHost.mLen == -1) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+
+ if (NS_FAILED(rv)) {
+ Clear();
+ // If parsing the spec has failed, restore the old URL
+ // so we don't end up with an empty URL.
+ CopyMembers(&prevURL, eHonorRef, ""_ns);
+ return rv;
+ }
+
+ if (LOG_ENABLED()) {
+ LOG((" spec = %s\n", mSpec.get()));
+ LOG((" port = %d\n", mPort));
+ LOG((" scheme = (%u,%d)\n", (uint32_t)mScheme.mPos,
+ (int32_t)mScheme.mLen));
+ LOG((" authority = (%u,%d)\n", (uint32_t)mAuthority.mPos,
+ (int32_t)mAuthority.mLen));
+ LOG((" username = (%u,%d)\n", (uint32_t)mUsername.mPos,
+ (int32_t)mUsername.mLen));
+ LOG((" password = (%u,%d)\n", (uint32_t)mPassword.mPos,
+ (int32_t)mPassword.mLen));
+ LOG((" hostname = (%u,%d)\n", (uint32_t)mHost.mPos, (int32_t)mHost.mLen));
+ LOG((" path = (%u,%d)\n", (uint32_t)mPath.mPos, (int32_t)mPath.mLen));
+ LOG((" filepath = (%u,%d)\n", (uint32_t)mFilepath.mPos,
+ (int32_t)mFilepath.mLen));
+ LOG((" directory = (%u,%d)\n", (uint32_t)mDirectory.mPos,
+ (int32_t)mDirectory.mLen));
+ LOG((" basename = (%u,%d)\n", (uint32_t)mBasename.mPos,
+ (int32_t)mBasename.mLen));
+ LOG((" extension = (%u,%d)\n", (uint32_t)mExtension.mPos,
+ (int32_t)mExtension.mLen));
+ LOG((" query = (%u,%d)\n", (uint32_t)mQuery.mPos,
+ (int32_t)mQuery.mLen));
+ LOG((" ref = (%u,%d)\n", (uint32_t)mRef.mPos, (int32_t)mRef.mLen));
+ }
+
+ SanityCheck();
+ return rv;
+}
+
+nsresult nsStandardURL::SetScheme(const nsACString& input) {
+ // Strip tabs, newlines, carriage returns from input
+ nsAutoCString scheme(input);
+ scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme.get()));
+
+ if (scheme.IsEmpty()) {
+ NS_WARNING("cannot remove the scheme from an url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mScheme.mLen < 0) {
+ NS_WARNING("uninitialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!net_IsValidScheme(scheme)) {
+ NS_WARNING("the given url scheme contains invalid characters");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mSpec.Length() + input.Length() - Scheme().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ InvalidateCache();
+
+ int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme);
+
+ if (shift) {
+ mScheme.mLen = scheme.Length();
+ ShiftFromAuthority(shift);
+ }
+
+ // ensure new scheme is lowercase
+ //
+ // XXX the string code unfortunately doesn't provide a ToLowerCase
+ // that operates on a substring.
+ net_ToLowerCase((char*)mSpec.get(), mScheme.mLen);
+
+ // If the scheme changes the default port also changes.
+ if (Scheme() == "http"_ns || Scheme() == "ws"_ns) {
+ mDefaultPort = 80;
+ } else if (Scheme() == "https"_ns || Scheme() == "wss"_ns) {
+ mDefaultPort = 443;
+ }
+ if (mPort == mDefaultPort) {
+ MOZ_ALWAYS_SUCCEEDS(SetPort(-1));
+ }
+
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetUserPass(const nsACString& input) {
+ const nsPromiseFlatCString& userpass = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetUserPass [userpass=%s]\n", userpass.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (userpass.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_WARNING("cannot set user:pass on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mAuthority.mLen < 0) {
+ NS_WARNING("uninitialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mSpec.Length() + input.Length() - Userpass(true).Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ InvalidateCache();
+
+ NS_ASSERTION(mHost.mLen >= 0, "uninitialized");
+
+ nsresult rv;
+ uint32_t usernamePos, passwordPos;
+ int32_t usernameLen, passwordLen;
+
+ rv = mParser->ParseUserInfo(userpass.get(), userpass.Length(), &usernamePos,
+ &usernameLen, &passwordPos, &passwordLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // build new user:pass in |buf|
+ nsAutoCString buf;
+ if (usernameLen > 0 || passwordLen > 0) {
+ nsSegmentEncoder encoder;
+ bool ignoredOut;
+ usernameLen = encoder.EncodeSegmentCount(
+ userpass.get(), URLSegment(usernamePos, usernameLen),
+ esc_Username | esc_AlwaysCopy, buf, ignoredOut);
+ if (passwordLen > 0) {
+ buf.Append(':');
+ passwordLen = encoder.EncodeSegmentCount(
+ userpass.get(), URLSegment(passwordPos, passwordLen),
+ esc_Password | esc_AlwaysCopy, buf, ignoredOut);
+ } else {
+ passwordLen = -1;
+ }
+ if (mUsername.mLen < 0 && mPassword.mLen < 0) {
+ buf.Append('@');
+ }
+ }
+
+ int32_t shift = 0;
+
+ if (mUsername.mLen < 0 && mPassword.mLen < 0) {
+ // no existing user:pass
+ if (!buf.IsEmpty()) {
+ mSpec.Insert(buf, mHost.mPos);
+ mUsername.mPos = mHost.mPos;
+ shift = buf.Length();
+ }
+ } else {
+ // replace existing user:pass
+ uint32_t userpassLen = 0;
+ if (mUsername.mLen > 0) {
+ userpassLen += mUsername.mLen;
+ }
+ if (mPassword.mLen > 0) {
+ userpassLen += (mPassword.mLen + 1);
+ }
+ if (buf.IsEmpty()) {
+ // remove `@` character too
+ userpassLen++;
+ }
+ mSpec.Replace(mAuthority.mPos, userpassLen, buf);
+ shift = buf.Length() - userpassLen;
+ }
+ if (shift) {
+ ShiftFromHost(shift);
+ MOZ_DIAGNOSTIC_ASSERT(mAuthority.mLen >= -shift);
+ mAuthority.mLen += shift;
+ }
+ // update positions and lengths
+ mUsername.mLen = usernameLen > 0 ? usernameLen : -1;
+ mUsername.mPos = mAuthority.mPos;
+ mPassword.mLen = passwordLen > 0 ? passwordLen : -1;
+ if (passwordLen > 0) {
+ if (mUsername.mLen > 0) {
+ mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ } else {
+ mPassword.mPos = mAuthority.mPos + 1;
+ }
+ }
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetUsername(const nsACString& input) {
+ const nsPromiseFlatCString& username = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetUsername [username=%s]\n", username.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (username.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_WARNING("cannot set username on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mSpec.Length() + input.Length() - Username().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ InvalidateCache();
+
+ // escape username if necessary
+ nsAutoCString buf;
+ nsSegmentEncoder encoder;
+ const nsACString& escUsername =
+ encoder.EncodeSegment(username, esc_Username, buf);
+
+ int32_t shift = 0;
+
+ if (mUsername.mLen < 0 && escUsername.IsEmpty()) {
+ return NS_OK;
+ }
+
+ if (mUsername.mLen < 0 && mPassword.mLen < 0) {
+ MOZ_ASSERT(!escUsername.IsEmpty(), "Should not be empty at this point");
+ mUsername.mPos = mAuthority.mPos;
+ mSpec.Insert(escUsername + "@"_ns, mUsername.mPos);
+ shift = escUsername.Length() + 1;
+ mUsername.mLen = escUsername.Length() > 0 ? escUsername.Length() : -1;
+ } else {
+ uint32_t pos = mUsername.mLen < 0 ? mAuthority.mPos : mUsername.mPos;
+ int32_t len = mUsername.mLen < 0 ? 0 : mUsername.mLen;
+
+ if (mPassword.mLen < 0 && escUsername.IsEmpty()) {
+ len++; // remove the @ character too
+ }
+ shift = ReplaceSegment(pos, len, escUsername);
+ mUsername.mLen = escUsername.Length() > 0 ? escUsername.Length() : -1;
+ mUsername.mPos = pos;
+ }
+
+ if (shift) {
+ mAuthority.mLen += shift;
+ ShiftFromPassword(shift);
+ }
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetPassword(const nsACString& input) {
+ const nsPromiseFlatCString& password = PromiseFlatCString(input);
+
+ auto clearedPassword = MakeScopeExit([&password, this]() {
+ // Check that if this method is called with the empty string then the
+ // password is definitely cleared when exiting this method.
+ if (password.IsEmpty()) {
+ MOZ_DIAGNOSTIC_ASSERT(this->Password().IsEmpty());
+ }
+ Unused << this; // silence compiler -Wunused-lambda-capture
+ });
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ LOG(("nsStandardURL::SetPassword [password=%s]\n", password.get()));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (password.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_WARNING("cannot set password on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mSpec.Length() + input.Length() - Password().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (password.IsEmpty()) {
+ if (mPassword.mLen > 0) {
+ // cut(":password")
+ int32_t len = mPassword.mLen;
+ if (mUsername.mLen < 0) {
+ len++; // also cut the @ character
+ }
+ len++; // for the : character
+ mSpec.Cut(mPassword.mPos - 1, len);
+ ShiftFromHost(-len);
+ mAuthority.mLen -= len;
+ mPassword.mLen = -1;
+ }
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+ }
+
+ // escape password if necessary
+ nsAutoCString buf;
+ nsSegmentEncoder encoder;
+ const nsACString& escPassword =
+ encoder.EncodeSegment(password, esc_Password, buf);
+
+ int32_t shift;
+
+ if (mPassword.mLen < 0) {
+ if (mUsername.mLen > 0) {
+ mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ mSpec.Insert(":"_ns + escPassword, mPassword.mPos - 1);
+ shift = escPassword.Length() + 1;
+ } else {
+ mPassword.mPos = mAuthority.mPos + 1;
+ mSpec.Insert(":"_ns + escPassword + "@"_ns, mPassword.mPos - 1);
+ shift = escPassword.Length() + 2;
+ }
+ } else {
+ shift = ReplaceSegment(mPassword.mPos, mPassword.mLen, escPassword);
+ }
+
+ if (shift) {
+ mPassword.mLen = escPassword.Length();
+ mAuthority.mLen += shift;
+ ShiftFromHost(shift);
+ }
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+}
+
+void nsStandardURL::FindHostLimit(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd) {
+ for (int32_t i = 0; gHostLimitDigits[i]; ++i) {
+ nsACString::const_iterator c(aStart);
+ if (FindCharInReadable(gHostLimitDigits[i], c, aEnd)) {
+ aEnd = c;
+ }
+ }
+}
+
+// If aValue only has a host part and no port number, the port
+// will not be reset!!!
+nsresult nsStandardURL::SetHostPort(const nsACString& aValue) {
+ // We cannot simply call nsIURI::SetHost because that would treat the name as
+ // an IPv6 address (like http:://[server:443]/). We also cannot call
+ // nsIURI::SetHostPort because that isn't implemented. Sadfaces.
+
+ nsACString::const_iterator start, end;
+ aValue.BeginReading(start);
+ aValue.EndReading(end);
+ nsACString::const_iterator iter(start);
+ bool isIPv6 = false;
+
+ FindHostLimit(start, end);
+
+ if (*start == '[') { // IPv6 address
+ if (!FindCharInReadable(']', iter, end)) {
+ // the ] character is missing
+ return NS_ERROR_MALFORMED_URI;
+ }
+ // iter now at the ']' character
+ isIPv6 = true;
+ } else {
+ nsACString::const_iterator iter2(start);
+ if (FindCharInReadable(']', iter2, end)) {
+ // if the first char isn't [ then there should be no ] character
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ FindCharInReadable(':', iter, end);
+
+ if (!isIPv6 && iter != end) {
+ nsACString::const_iterator iter2(iter);
+ iter2++; // Skip over the first ':' character
+ if (FindCharInReadable(':', iter2, end)) {
+ // If there is more than one ':' character it suggests an IPv6
+ // The format should be [2001::1]:80 where the port is optional
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ nsresult rv = SetHost(Substring(start, iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (iter == end) {
+ // does not end in colon
+ return NS_OK;
+ }
+
+ iter++; // advance over the colon
+ if (iter == end) {
+ // port number is missing
+ return NS_OK;
+ }
+
+ nsCString portStr(Substring(iter, end));
+ int32_t port = portStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ // Failure parsing the port number
+ return NS_OK;
+ }
+
+ Unused << SetPort(port);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetHost(const nsACString& input) {
+ nsAutoCString hostname(input);
+ hostname.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ nsACString::const_iterator start, end;
+ hostname.BeginReading(start);
+ hostname.EndReading(end);
+
+ FindHostLimit(start, end);
+
+ // Do percent decoding on the the input.
+ nsAutoCString flat;
+ NS_UnescapeURL(hostname.BeginReading(), end - start,
+ esc_AlwaysCopy | esc_Host, flat);
+ const char* host = flat.get();
+
+ LOG(("nsStandardURL::SetHost [host=%s]\n", host));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (flat.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_WARNING("cannot set host on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (flat.IsEmpty()) {
+ // Setting an empty hostname is not allowed for
+ // URLTYPE_STANDARD and URLTYPE_AUTHORITY.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (strlen(host) < flat.Length()) {
+ return NS_ERROR_MALFORMED_URI; // found embedded null
+ }
+
+ // For consistency with SetSpec/nsURLParsers, don't allow spaces
+ // in the hostname.
+ if (strchr(host, ' ')) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (mSpec.Length() + strlen(host) - Host().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ InvalidateCache();
+
+ uint32_t len;
+ nsAutoCString hostBuf;
+ nsresult rv = NormalizeIDN(flat, hostBuf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!SegmentIs(mScheme, "resource") && !SegmentIs(mScheme, "chrome")) {
+ nsAutoCString ipString;
+ if (hostBuf.Length() > 0 && hostBuf.First() == '[' &&
+ hostBuf.Last() == ']' &&
+ ValidIPv6orHostname(hostBuf.get(), hostBuf.Length())) {
+ rv = (nsresult)rusturl_parse_ipv6addr(&hostBuf, &ipString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ hostBuf = ipString;
+ } else {
+ if (EndsInANumber(hostBuf)) {
+ rv = NormalizeIPv4(hostBuf, ipString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ hostBuf = ipString;
+ }
+ }
+ }
+
+ // NormalizeIDN always copies if the call was successful
+ host = hostBuf.get();
+ len = hostBuf.Length();
+
+ if (!ValidIPv6orHostname(host, len)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (mHost.mLen < 0) {
+ int port_length = 0;
+ if (mPort != -1) {
+ nsAutoCString buf;
+ buf.Assign(':');
+ buf.AppendInt(mPort);
+ port_length = buf.Length();
+ }
+ if (mAuthority.mLen > 0) {
+ mHost.mPos = mAuthority.mPos + mAuthority.mLen - port_length;
+ mHost.mLen = 0;
+ } else if (mScheme.mLen > 0) {
+ mHost.mPos = mScheme.mPos + mScheme.mLen + 3;
+ mHost.mLen = 0;
+ }
+ }
+
+ int32_t shift = ReplaceSegment(mHost.mPos, mHost.mLen, host, len);
+
+ if (shift) {
+ mHost.mLen = len;
+ mAuthority.mLen += shift;
+ ShiftFromPath(shift);
+ }
+
+ // Now canonicalize the host to lowercase
+ net_ToLowerCase(mSpec.BeginWriting() + mHost.mPos, mHost.mLen);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetPort(int32_t port) {
+ LOG(("nsStandardURL::SetPort [port=%d]\n", port));
+
+ if ((port == mPort) || (mPort == -1 && port == mDefaultPort)) {
+ return NS_OK;
+ }
+
+ // ports must be >= 0 and 16 bit
+ // -1 == use default
+ if (port < -1 || port > std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ NS_WARNING("cannot set port on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ InvalidateCache();
+ if (port == mDefaultPort) {
+ port = -1;
+ }
+
+ ReplacePortInSpec(port);
+
+ mPort = port;
+ return NS_OK;
+}
+
+/**
+ * Replaces the existing port in mSpec with aNewPort.
+ *
+ * The caller is responsible for:
+ * - Calling InvalidateCache (since our mSpec is changing).
+ * - Checking whether aNewPort is mDefaultPort (in which case the
+ * caller should pass aNewPort=-1).
+ */
+void nsStandardURL::ReplacePortInSpec(int32_t aNewPort) {
+ NS_ASSERTION(aNewPort != mDefaultPort || mDefaultPort == -1,
+ "Caller should check its passed-in value and pass -1 instead of "
+ "mDefaultPort, to avoid encoding default port into mSpec");
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ // Create the (possibly empty) string that we're planning to replace:
+ nsAutoCString buf;
+ if (mPort != -1) {
+ buf.Assign(':');
+ buf.AppendInt(mPort);
+ }
+ // Find the position & length of that string:
+ const uint32_t replacedLen = buf.Length();
+ const uint32_t replacedStart =
+ mAuthority.mPos + mAuthority.mLen - replacedLen;
+
+ // Create the (possibly empty) replacement string:
+ if (aNewPort == -1) {
+ buf.Truncate();
+ } else {
+ buf.Assign(':');
+ buf.AppendInt(aNewPort);
+ }
+ // Perform the replacement:
+ mSpec.Replace(replacedStart, replacedLen, buf);
+
+ // Bookkeeping to reflect the new length:
+ int32_t shift = buf.Length() - replacedLen;
+ mAuthority.mLen += shift;
+ ShiftFromPath(shift);
+}
+
+nsresult nsStandardURL::SetPathQueryRef(const nsACString& input) {
+ const nsPromiseFlatCString& path = PromiseFlatCString(input);
+ LOG(("nsStandardURL::SetPathQueryRef [path=%s]\n", path.get()));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ InvalidateCache();
+
+ if (!path.IsEmpty()) {
+ nsAutoCString spec;
+
+ spec.Assign(mSpec.get(), mPath.mPos);
+ if (path.First() != '/') {
+ spec.Append('/');
+ }
+ spec.Append(path);
+
+ return SetSpecInternal(spec);
+ }
+ if (mPath.mLen >= 1) {
+ mSpec.Cut(mPath.mPos + 1, mPath.mLen - 1);
+ // these contain only a '/'
+ mPath.mLen = 1;
+ mDirectory.mLen = 1;
+ mFilepath.mLen = 1;
+ // these are no longer defined
+ mBasename.mLen = -1;
+ mExtension.mLen = -1;
+ mQuery.mLen = -1;
+ mRef.mLen = -1;
+ }
+ return NS_OK;
+}
+
+// When updating this also update SubstitutingURL::Mutator
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsStandardURL::Mutator, nsIURISetters,
+ nsIURIMutator, nsIStandardURLMutator,
+ nsIURLMutator, nsIFileURLMutator,
+ nsISerializable)
+
+NS_IMETHODIMP
+nsStandardURL::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsStandardURL::Mutator> mutator = new nsStandardURL::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Equals(nsIURI* unknownOther, bool* result) {
+ return EqualsInternal(unknownOther, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::EqualsExceptRef(nsIURI* unknownOther, bool* result) {
+ return EqualsInternal(unknownOther, eIgnoreRef, result);
+}
+
+nsresult nsStandardURL::EqualsInternal(
+ nsIURI* unknownOther, nsStandardURL::RefHandlingEnum refHandlingMode,
+ bool* result) {
+ NS_ENSURE_ARG_POINTER(unknownOther);
+ MOZ_ASSERT(result, "null pointer");
+
+ RefPtr<nsStandardURL> other;
+ nsresult rv =
+ unknownOther->QueryInterface(kThisImplCID, getter_AddRefs(other));
+ if (NS_FAILED(rv)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // First, check whether one URIs is an nsIFileURL while the other
+ // is not. If that's the case, they're different.
+ if (mSupportsFileURL != other->mSupportsFileURL) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // Next check parts of a URI that, if different, automatically make the
+ // URIs different
+ if (!SegmentIs(mScheme, other->mSpec.get(), other->mScheme) ||
+ // Check for host manually, since conversion to file will
+ // ignore the host!
+ !SegmentIs(mHost, other->mSpec.get(), other->mHost) ||
+ !SegmentIs(mQuery, other->mSpec.get(), other->mQuery) ||
+ !SegmentIs(mUsername, other->mSpec.get(), other->mUsername) ||
+ !SegmentIs(mPassword, other->mSpec.get(), other->mPassword) ||
+ Port() != other->Port()) {
+ // No need to compare files or other URI parts -- these are different
+ // beasties
+ *result = false;
+ return NS_OK;
+ }
+
+ if (refHandlingMode == eHonorRef &&
+ !SegmentIs(mRef, other->mSpec.get(), other->mRef)) {
+ *result = false;
+ return NS_OK;
+ }
+
+ // Then check for exact identity of URIs. If we have it, they're equal
+ if (SegmentIs(mDirectory, other->mSpec.get(), other->mDirectory) &&
+ SegmentIs(mBasename, other->mSpec.get(), other->mBasename) &&
+ SegmentIs(mExtension, other->mSpec.get(), other->mExtension)) {
+ *result = true;
+ return NS_OK;
+ }
+
+ // At this point, the URIs are not identical, but they only differ in the
+ // directory/filename/extension. If these are file URLs, then get the
+ // corresponding file objects and compare those, since two filenames that
+ // differ, eg, only in case could still be equal.
+ if (mSupportsFileURL) {
+ // Assume not equal for failure cases... but failures in GetFile are
+ // really failures, more or less, so propagate them to caller.
+ *result = false;
+
+ rv = EnsureFile();
+ nsresult rv2 = other->EnsureFile();
+ // special case for resource:// urls that don't resolve to files
+ if (rv == NS_ERROR_NO_INTERFACE && rv == rv2) {
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("nsStandardURL::Equals [this=%p spec=%s] failed to ensure file",
+ this, mSpec.get()));
+ return rv;
+ }
+ NS_ASSERTION(mFile, "EnsureFile() lied!");
+ rv = rv2;
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsStandardURL::Equals [other=%p spec=%s] other failed to ensure "
+ "file",
+ other.get(), other->mSpec.get()));
+ return rv;
+ }
+ NS_ASSERTION(other->mFile, "EnsureFile() lied!");
+ return mFile->Equals(other->mFile, result);
+ }
+
+ // The URLs are not identical, and they do not correspond to the
+ // same file, so they are different.
+ *result = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::SchemeIs(const char* scheme, bool* result) {
+ MOZ_ASSERT(result, "null pointer");
+ if (!scheme) {
+ *result = false;
+ return NS_OK;
+ }
+
+ *result = SegmentIs(mScheme, scheme);
+ return NS_OK;
+}
+
+/* virtual */ nsStandardURL* nsStandardURL::StartClone() {
+ nsStandardURL* clone = new nsStandardURL();
+ return clone;
+}
+
+nsresult nsStandardURL::Clone(nsIURI** aURI) {
+ return CloneInternal(eHonorRef, ""_ns, aURI);
+}
+
+nsresult nsStandardURL::CloneInternal(
+ nsStandardURL::RefHandlingEnum aRefHandlingMode, const nsACString& aNewRef,
+ nsIURI** aClone)
+
+{
+ RefPtr<nsStandardURL> clone = StartClone();
+ if (!clone) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Copy local members into clone.
+ // Also copies the cached members mFile, mDisplayHost
+ clone->CopyMembers(this, aRefHandlingMode, aNewRef, true);
+
+ clone.forget(aClone);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::CopyMembers(
+ nsStandardURL* source, nsStandardURL::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef, bool copyCached) {
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ mSpec = source->mSpec;
+ mDefaultPort = source->mDefaultPort;
+ mPort = source->mPort;
+ mScheme = source->mScheme;
+ mAuthority = source->mAuthority;
+ mUsername = source->mUsername;
+ mPassword = source->mPassword;
+ mHost = source->mHost;
+ mPath = source->mPath;
+ mFilepath = source->mFilepath;
+ mDirectory = source->mDirectory;
+ mBasename = source->mBasename;
+ mExtension = source->mExtension;
+ mQuery = source->mQuery;
+ mRef = source->mRef;
+ mURLType = source->mURLType;
+ mParser = source->mParser;
+ mSupportsFileURL = source->mSupportsFileURL;
+ mCheckedIfHostA = source->mCheckedIfHostA;
+ mDisplayHost = source->mDisplayHost;
+
+ if (copyCached) {
+ mFile = source->mFile;
+ } else {
+ InvalidateCache(true);
+ }
+
+ if (refHandlingMode == eIgnoreRef) {
+ SetRef(""_ns);
+ } else if (refHandlingMode == eReplaceRef) {
+ SetRef(newRef);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Resolve(const nsACString& in, nsACString& out) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(in);
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString buf;
+ net_FilterURIString(flat, buf);
+
+ const char* relpath = buf.get();
+ int32_t relpathLen = buf.Length();
+
+ char* result = nullptr;
+
+ LOG(("nsStandardURL::Resolve [this=%p spec=%s relpath=%s]\n", this,
+ mSpec.get(), relpath));
+
+ NS_ASSERTION(mParser, "no parser: unitialized");
+
+ // NOTE: there is no need for this function to produce normalized
+ // output. normalization will occur when the result is used to
+ // initialize a nsStandardURL object.
+
+ if (mScheme.mLen < 0) {
+ NS_WARNING("unable to Resolve URL: this URL not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+ URLSegment scheme;
+ char* resultPath = nullptr;
+ bool relative = false;
+ uint32_t offset = 0;
+ netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
+
+ nsAutoCString baseProtocol(Scheme());
+ nsAutoCString protocol;
+ rv = net_ExtractURLScheme(buf, protocol);
+
+ // Normally, if we parse a scheme, then it's an absolute URI. But because
+ // we still support a deprecated form of relative URIs such as: http:file or
+ // http:/path/file we can't do that for all protocols.
+ // So we just make sure that if there a protocol, it's the same as the
+ // current one, otherwise we treat it as an absolute URI.
+ if (NS_SUCCEEDED(rv) && protocol != baseProtocol) {
+ out = buf;
+ return NS_OK;
+ }
+
+ // relative urls should never contain a host, so we always want to use
+ // the noauth url parser.
+ // use it to extract a possible scheme
+ uint32_t schemePos = scheme.mPos;
+ int32_t schemeLen = scheme.mLen;
+ rv = mParser->ParseURL(relpath, relpathLen, &schemePos, &schemeLen, nullptr,
+ nullptr, nullptr, nullptr);
+
+ // if the parser fails (for example because there is no valid scheme)
+ // reset the scheme and assume a relative url
+ if (NS_FAILED(rv)) {
+ scheme.Reset();
+ }
+
+ scheme.mPos = schemePos;
+ scheme.mLen = schemeLen;
+
+ protocol.Assign(Segment(scheme));
+
+ // We need to do backslash replacement for the following cases:
+ // 1. The input is an absolute path with a http/https/ftp scheme
+ // 2. The input is a relative path, and the base URL has a http/https/ftp
+ // scheme
+ if ((protocol.IsEmpty() && IsSpecialProtocol(baseProtocol)) ||
+ IsSpecialProtocol(protocol)) {
+ auto* start = buf.BeginWriting();
+ auto* end = buf.EndWriting();
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ if (scheme.mLen >= 0) {
+ // add some flags to coalesceFlag if it is an ftp-url
+ // need this later on when coalescing the resulting URL
+ if (SegmentIs(relpath, scheme, "ftp", true)) {
+ coalesceFlag =
+ (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT |
+ NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+ }
+ // this URL appears to be absolute
+ // but try to find out more
+ if (SegmentIs(mScheme, relpath, scheme, true)) {
+ // mScheme and Scheme are the same
+ // but this can still be relative
+ if (strncmp(relpath + scheme.mPos + scheme.mLen, "://", 3) == 0) {
+ // now this is really absolute
+ // because a :// follows the scheme
+ result = NS_xstrdup(relpath);
+ } else {
+ // This is a deprecated form of relative urls like
+ // http:file or http:/path/file
+ // we will support it for now ...
+ relative = true;
+ offset = scheme.mLen + 1;
+ }
+ } else {
+ // the schemes are not the same, we are also done
+ // because we have to assume this is absolute
+ result = NS_xstrdup(relpath);
+ }
+ } else {
+ // add some flags to coalesceFlag if it is an ftp-url
+ // need this later on when coalescing the resulting URL
+ if (SegmentIs(mScheme, "ftp")) {
+ coalesceFlag =
+ (netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT |
+ NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
+ }
+ if (relpath[0] == '/' && relpath[1] == '/') {
+ // this URL //host/path is almost absolute
+ result = AppendToSubstring(mScheme.mPos, mScheme.mLen + 1, relpath);
+ } else {
+ // then it must be relative
+ relative = true;
+ }
+ }
+ if (relative) {
+ uint32_t len = 0;
+ const char* realrelpath = relpath + offset;
+ switch (*realrelpath) {
+ case '/':
+ // overwrite everything after the authority
+ len = mAuthority.mPos + mAuthority.mLen;
+ break;
+ case '?':
+ // overwrite the existing ?query and #ref
+ if (mQuery.mLen >= 0) {
+ len = mQuery.mPos - 1;
+ } else if (mRef.mLen >= 0) {
+ len = mRef.mPos - 1;
+ } else {
+ len = mPath.mPos + mPath.mLen;
+ }
+ break;
+ case '#':
+ case '\0':
+ // overwrite the existing #ref
+ if (mRef.mLen < 0) {
+ len = mPath.mPos + mPath.mLen;
+ } else {
+ len = mRef.mPos - 1;
+ }
+ break;
+ default:
+ if (coalesceFlag & NET_COALESCE_DOUBLE_SLASH_IS_ROOT) {
+ if (Filename().Equals("%2F"_ns, nsCaseInsensitiveCStringComparator)) {
+ // if ftp URL ends with %2F then simply
+ // append relative part because %2F also
+ // marks the root directory with ftp-urls
+ len = mFilepath.mPos + mFilepath.mLen;
+ } else {
+ // overwrite everything after the directory
+ len = mDirectory.mPos + mDirectory.mLen;
+ }
+ } else {
+ // overwrite everything after the directory
+ len = mDirectory.mPos + mDirectory.mLen;
+ }
+ }
+ result = AppendToSubstring(0, len, realrelpath);
+ // locate result path
+ resultPath = result + mPath.mPos;
+ }
+ if (!result) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (resultPath) {
+ net_CoalesceDirs(coalesceFlag, resultPath);
+ } else {
+ // locate result path
+ resultPath = strstr(result, "://");
+ if (resultPath) {
+ // If there are multiple slashes after :// we must ignore them
+ // otherwise net_CoalesceDirs may think the host is a part of the path.
+ resultPath += 3;
+ if (protocol.IsEmpty() && Scheme() != "file") {
+ while (*resultPath == '/') {
+ resultPath++;
+ }
+ }
+ resultPath = strchr(resultPath, '/');
+ if (resultPath) {
+ net_CoalesceDirs(coalesceFlag, resultPath);
+ }
+ }
+ }
+ out.Adopt(result);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetCommonBaseSpec(nsIURI* uri2, nsACString& aResult) {
+ NS_ENSURE_ARG_POINTER(uri2);
+
+ // if uri's are equal, then return uri as is
+ bool isEquals = false;
+ if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals) {
+ return GetSpec(aResult);
+ }
+
+ aResult.Truncate();
+
+ // check pre-path; if they don't match, then return empty string
+ RefPtr<nsStandardURL> stdurl2;
+ nsresult rv = uri2->QueryInterface(kThisImplCID, getter_AddRefs(stdurl2));
+ isEquals = NS_SUCCEEDED(rv) &&
+ SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme) &&
+ SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost) &&
+ SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername) &&
+ SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword) &&
+ (Port() == stdurl2->Port());
+ if (!isEquals) {
+ return NS_OK;
+ }
+
+ // scan for first mismatched character
+ const char *thisIndex, *thatIndex, *startCharPos;
+ startCharPos = mSpec.get() + mDirectory.mPos;
+ thisIndex = startCharPos;
+ thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
+ while ((*thisIndex == *thatIndex) && *thisIndex) {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // backup to just after previous slash so we grab an appropriate path
+ // segment such as a directory (not partial segments)
+ // todo: also check for file matches which include '?' and '#'
+ while ((thisIndex != startCharPos) && (*(thisIndex - 1) != '/')) {
+ thisIndex--;
+ }
+
+ // grab spec from beginning to thisIndex
+ aResult = Substring(mSpec, mScheme.mPos, thisIndex - mSpec.get());
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetRelativeSpec(nsIURI* uri2, nsACString& aResult) {
+ NS_ENSURE_ARG_POINTER(uri2);
+
+ aResult.Truncate();
+
+ // if uri's are equal, then return empty string
+ bool isEquals = false;
+ if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals) {
+ return NS_OK;
+ }
+
+ RefPtr<nsStandardURL> stdurl2;
+ nsresult rv = uri2->QueryInterface(kThisImplCID, getter_AddRefs(stdurl2));
+ isEquals = NS_SUCCEEDED(rv) &&
+ SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme) &&
+ SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost) &&
+ SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername) &&
+ SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword) &&
+ (Port() == stdurl2->Port());
+ if (!isEquals) {
+ return uri2->GetSpec(aResult);
+ }
+
+ // scan for first mismatched character
+ const char *thisIndex, *thatIndex, *startCharPos;
+ startCharPos = mSpec.get() + mDirectory.mPos;
+ thisIndex = startCharPos;
+ thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
+
+#ifdef XP_WIN
+ bool isFileScheme = SegmentIs(mScheme, "file");
+ if (isFileScheme) {
+ // on windows, we need to match the first segment of the path
+ // if these don't match then we need to return an absolute path
+ // skip over any leading '/' in path
+ while ((*thisIndex == *thatIndex) && (*thisIndex == '/')) {
+ thisIndex++;
+ thatIndex++;
+ }
+ // look for end of first segment
+ while ((*thisIndex == *thatIndex) && *thisIndex && (*thisIndex != '/')) {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // if we didn't match through the first segment, return absolute path
+ if ((*thisIndex != '/') || (*thatIndex != '/')) {
+ return uri2->GetSpec(aResult);
+ }
+ }
+#endif
+
+ while ((*thisIndex == *thatIndex) && *thisIndex) {
+ thisIndex++;
+ thatIndex++;
+ }
+
+ // backup to just after previous slash so we grab an appropriate path
+ // segment such as a directory (not partial segments)
+ // todo: also check for file matches with '#' and '?'
+ while ((*(thatIndex - 1) != '/') && (thatIndex != startCharPos)) {
+ thatIndex--;
+ }
+
+ const char* limit = mSpec.get() + mFilepath.mPos + mFilepath.mLen;
+
+ // need to account for slashes and add corresponding "../"
+ for (; thisIndex <= limit && *thisIndex; ++thisIndex) {
+ if (*thisIndex == '/') {
+ aResult.AppendLiteral("../");
+ }
+ }
+
+ // grab spec from thisIndex to end
+ uint32_t startPos = stdurl2->mScheme.mPos + thatIndex - stdurl2->mSpec.get();
+ aResult.Append(
+ Substring(stdurl2->mSpec, startPos, stdurl2->mSpec.Length() - startPos));
+
+ return rv;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIURL
+//----------------------------------------------------------------------------
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFilePath(nsACString& result) {
+ result = Filepath();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetQuery(nsACString& result) {
+ result = Query();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasQuery(bool* result) {
+ *result = (mQuery.mLen >= 0);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetRef(nsACString& result) {
+ result = Ref();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasRef(bool* result) {
+ *result = (mRef.mLen >= 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasUserPass(bool* result) {
+ *result = (mUsername.mLen >= 0) || (mPassword.mLen >= 0);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetDirectory(nsACString& result) {
+ result = Directory();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileName(nsACString& result) {
+ result = Filename();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileBaseName(nsACString& result) {
+ result = Basename();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileExtension(nsACString& result) {
+ result = Extension();
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetFilePath(const nsACString& input) {
+ nsAutoCString str(input);
+ str.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+ const char* filepath = str.get();
+
+ LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ // if there isn't a filepath, then there can't be anything
+ // after the path either. this url is likely uninitialized.
+ if (mFilepath.mLen < 0) {
+ return SetPathQueryRef(str);
+ }
+
+ if (!str.IsEmpty()) {
+ nsAutoCString spec;
+ uint32_t dirPos, basePos, extPos;
+ int32_t dirLen, baseLen, extLen;
+ nsresult rv;
+
+ if (IsSpecialProtocol(mSpec)) {
+ // Bug 1873955: Replace all backslashes with slashes when parsing paths
+ // Stop when we reach the query or the hash.
+ auto* start = str.BeginWriting();
+ auto* end = str.EndWriting();
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ rv = mParser->ParseFilePath(filepath, str.Length(), &dirPos, &dirLen,
+ &basePos, &baseLen, &extPos, &extLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // build up new candidate spec
+ spec.Assign(mSpec.get(), mPath.mPos);
+
+ // ensure leading '/'
+ if (filepath[dirPos] != '/') {
+ spec.Append('/');
+ }
+
+ nsSegmentEncoder encoder;
+
+ // append encoded filepath components
+ if (dirLen > 0) {
+ encoder.EncodeSegment(
+ Substring(filepath + dirPos, filepath + dirPos + dirLen),
+ esc_Directory | esc_AlwaysCopy, spec);
+ }
+ if (baseLen > 0) {
+ encoder.EncodeSegment(
+ Substring(filepath + basePos, filepath + basePos + baseLen),
+ esc_FileBaseName | esc_AlwaysCopy, spec);
+ }
+ if (extLen >= 0) {
+ spec.Append('.');
+ if (extLen > 0) {
+ encoder.EncodeSegment(
+ Substring(filepath + extPos, filepath + extPos + extLen),
+ esc_FileExtension | esc_AlwaysCopy, spec);
+ }
+ }
+
+ // compute the ending position of the current filepath
+ if (mFilepath.mLen >= 0) {
+ uint32_t end = mFilepath.mPos + mFilepath.mLen;
+ if (mSpec.Length() > end) {
+ spec.Append(mSpec.get() + end, mSpec.Length() - end);
+ }
+ }
+
+ return SetSpecInternal(spec);
+ }
+ if (mPath.mLen > 1) {
+ mSpec.Cut(mPath.mPos + 1, mFilepath.mLen - 1);
+ // left shift query, and ref
+ ShiftFromQuery(1 - mFilepath.mLen);
+ // One character for '/', and if we have a query or ref we add their
+ // length and one extra for each '?' or '#' characters
+ mPath.mLen = 1 + (mQuery.mLen >= 0 ? (mQuery.mLen + 1) : 0) +
+ (mRef.mLen >= 0 ? (mRef.mLen + 1) : 0);
+ // these contain only a '/'
+ mDirectory.mLen = 1;
+ mFilepath.mLen = 1;
+ // these are no longer defined
+ mBasename.mLen = -1;
+ mExtension.mLen = -1;
+ }
+ return NS_OK;
+}
+
+inline bool IsUTFEncoding(const Encoding* aEncoding) {
+ return aEncoding == UTF_8_ENCODING || aEncoding == UTF_16BE_ENCODING ||
+ aEncoding == UTF_16LE_ENCODING;
+}
+
+nsresult nsStandardURL::SetQuery(const nsACString& input) {
+ return SetQueryWithEncoding(input, nullptr);
+}
+
+nsresult nsStandardURL::SetQueryWithEncoding(const nsACString& input,
+ const Encoding* encoding) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ const char* query = flat.get();
+
+ LOG(("nsStandardURL::SetQuery [query=%s]\n", query));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ if (IsUTFEncoding(encoding)) {
+ encoding = nullptr;
+ }
+
+ if (mPath.mLen < 0) {
+ return SetPathQueryRef(flat);
+ }
+
+ if (mSpec.Length() + input.Length() - Query().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (flat.IsEmpty()) {
+ // remove existing query
+ if (mQuery.mLen >= 0) {
+ // remove query and leading '?'
+ mSpec.Cut(mQuery.mPos - 1, mQuery.mLen + 1);
+ ShiftFromRef(-(mQuery.mLen + 1));
+ mPath.mLen -= (mQuery.mLen + 1);
+ mQuery.mPos = 0;
+ mQuery.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI(flat);
+ filteredURI.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ query = filteredURI.get();
+ int32_t queryLen = filteredURI.Length();
+ if (query[0] == '?') {
+ query++;
+ queryLen--;
+ }
+
+ if (mQuery.mLen < 0) {
+ if (mRef.mLen < 0) {
+ mQuery.mPos = mSpec.Length();
+ } else {
+ mQuery.mPos = mRef.mPos - 1;
+ }
+ mSpec.Insert('?', mQuery.mPos);
+ mQuery.mPos++;
+ mQuery.mLen = 0;
+ // the insertion pushes these out by 1
+ mPath.mLen++;
+ mRef.mPos++;
+ }
+
+ // encode query if necessary
+ nsAutoCString buf;
+ bool encoded;
+ nsSegmentEncoder encoder(encoding);
+ encoder.EncodeSegmentCount(query, URLSegment(0, queryLen), esc_Query, buf,
+ encoded);
+ if (encoded) {
+ query = buf.get();
+ queryLen = buf.Length();
+ }
+
+ int32_t shift = ReplaceSegment(mQuery.mPos, mQuery.mLen, query, queryLen);
+
+ if (shift) {
+ mQuery.mLen = queryLen;
+ mPath.mLen += shift;
+ ShiftFromRef(shift);
+ }
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetRef(const nsACString& input) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ const char* ref = flat.get();
+
+ LOG(("nsStandardURL::SetRef [ref=%s]\n", ref));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ if (mPath.mLen < 0) {
+ return SetPathQueryRef(flat);
+ }
+
+ if (mSpec.Length() + input.Length() - Ref().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (input.IsEmpty()) {
+ // remove existing ref
+ if (mRef.mLen >= 0) {
+ // remove ref and leading '#'
+ mSpec.Cut(mRef.mPos - 1, mRef.mLen + 1);
+ mPath.mLen -= (mRef.mLen + 1);
+ mRef.mPos = 0;
+ mRef.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI(flat);
+ filteredURI.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ ref = filteredURI.get();
+ int32_t refLen = filteredURI.Length();
+ if (ref[0] == '#') {
+ ref++;
+ refLen--;
+ }
+
+ if (mRef.mLen < 0) {
+ mSpec.Append('#');
+ ++mPath.mLen; // Include the # in the path.
+ mRef.mPos = mSpec.Length();
+ mRef.mLen = 0;
+ }
+
+ // If precent encoding is necessary, `ref` will point to `buf`'s content.
+ // `buf` needs to outlive any use of the `ref` pointer.
+ nsAutoCString buf;
+ // encode ref if necessary
+ bool encoded;
+ nsSegmentEncoder encoder;
+ encoder.EncodeSegmentCount(ref, URLSegment(0, refLen), esc_Ref, buf, encoded);
+ if (encoded) {
+ ref = buf.get();
+ refLen = buf.Length();
+ }
+
+ int32_t shift = ReplaceSegment(mRef.mPos, mRef.mLen, ref, refLen);
+ mPath.mLen += shift;
+ mRef.mLen = refLen;
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetFileNameInternal(const nsACString& input) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ const char* filename = flat.get();
+
+ LOG(("nsStandardURL::SetFileNameInternal [filename=%s]\n", filename));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ if (mPath.mLen < 0) {
+ return SetPathQueryRef(flat);
+ }
+
+ if (mSpec.Length() + input.Length() - Filename().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ int32_t shift = 0;
+
+ if (!(filename && *filename)) {
+ // remove the filename
+ if (mBasename.mLen > 0) {
+ if (mExtension.mLen >= 0) {
+ mBasename.mLen += (mExtension.mLen + 1);
+ }
+ mSpec.Cut(mBasename.mPos, mBasename.mLen);
+ shift = -mBasename.mLen;
+ mBasename.mLen = 0;
+ mExtension.mLen = -1;
+ }
+ } else {
+ nsresult rv;
+ uint32_t basenamePos = 0;
+ int32_t basenameLen = -1;
+ uint32_t extensionPos = 0;
+ int32_t extensionLen = -1;
+ // let the parser locate the basename and extension
+ rv = mParser->ParseFileName(filename, flat.Length(), &basenamePos,
+ &basenameLen, &extensionPos, &extensionLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ URLSegment basename(basenamePos, basenameLen);
+ URLSegment extension(extensionPos, extensionLen);
+
+ if (basename.mLen < 0) {
+ // remove existing filename
+ if (mBasename.mLen >= 0) {
+ uint32_t len = mBasename.mLen;
+ if (mExtension.mLen >= 0) {
+ len += (mExtension.mLen + 1);
+ }
+ mSpec.Cut(mBasename.mPos, len);
+ shift = -int32_t(len);
+ mBasename.mLen = 0;
+ mExtension.mLen = -1;
+ }
+ } else {
+ nsAutoCString newFilename;
+ bool ignoredOut;
+ nsSegmentEncoder encoder;
+ basename.mLen = encoder.EncodeSegmentCount(
+ filename, basename, esc_FileBaseName | esc_AlwaysCopy, newFilename,
+ ignoredOut);
+ if (extension.mLen >= 0) {
+ newFilename.Append('.');
+ extension.mLen = encoder.EncodeSegmentCount(
+ filename, extension, esc_FileExtension | esc_AlwaysCopy,
+ newFilename, ignoredOut);
+ }
+
+ if (mBasename.mLen < 0) {
+ // insert new filename
+ mBasename.mPos = mDirectory.mPos + mDirectory.mLen;
+ mSpec.Insert(newFilename, mBasename.mPos);
+ shift = newFilename.Length();
+ } else {
+ // replace existing filename
+ uint32_t oldLen = uint32_t(mBasename.mLen);
+ if (mExtension.mLen >= 0) {
+ oldLen += (mExtension.mLen + 1);
+ }
+ mSpec.Replace(mBasename.mPos, oldLen, newFilename);
+ shift = newFilename.Length() - oldLen;
+ }
+
+ mBasename.mLen = basename.mLen;
+ mExtension.mLen = extension.mLen;
+ if (mExtension.mLen >= 0) {
+ mExtension.mPos = mBasename.mPos + mBasename.mLen + 1;
+ }
+ }
+ }
+ if (shift) {
+ ShiftFromQuery(shift);
+ mFilepath.mLen += shift;
+ mPath.mLen += shift;
+ }
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetFileBaseNameInternal(const nsACString& input) {
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ nsAutoCString extension;
+ nsresult rv = GetFileExtension(extension);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newFileName(input);
+
+ if (!extension.IsEmpty()) {
+ newFileName.Append('.');
+ newFileName.Append(extension);
+ }
+
+ return SetFileNameInternal(newFileName);
+}
+
+nsresult nsStandardURL::SetFileExtensionInternal(const nsACString& input) {
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ nsAutoCString newFileName;
+ nsresult rv = GetFileBaseName(newFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!input.IsEmpty()) {
+ newFileName.Append('.');
+ newFileName.Append(input);
+ }
+
+ return SetFileNameInternal(newFileName);
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIFileURL
+//----------------------------------------------------------------------------
+
+nsresult nsStandardURL::EnsureFile() {
+ MOZ_ASSERT(mSupportsFileURL,
+ "EnsureFile() called on a URL that doesn't support files!");
+
+ if (mFile) {
+ // Nothing to do
+ return NS_OK;
+ }
+
+ // Parse the spec if we don't have a cached result
+ if (mSpec.IsEmpty()) {
+ NS_WARNING("url not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!SegmentIs(mScheme, "file")) {
+ NS_WARNING("not a file URL");
+ return NS_ERROR_FAILURE;
+ }
+
+ return net_GetFileFromURLSpec(mSpec, getter_AddRefs(mFile));
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetFile(nsIFile** result) {
+ MOZ_ASSERT(mSupportsFileURL,
+ "GetFile() called on a URL that doesn't support files!");
+
+ nsresult rv = EnsureFile();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (LOG_ENABLED()) {
+ LOG(("nsStandardURL::GetFile [this=%p spec=%s resulting_path=%s]\n", this,
+ mSpec.get(), mFile->HumanReadablePath().get()));
+ }
+
+ // clone the file, so the caller can modify it.
+ // XXX nsIFileURL.idl specifies that the consumer must _not_ modify the
+ // nsIFile returned from this method; but it seems that some folks do
+ // (see bug 161921). until we can be sure that all the consumers are
+ // behaving themselves, we'll stay on the safe side and clone the file.
+ // see bug 212724 about fixing the consumers.
+ return mFile->Clone(result);
+}
+
+nsresult nsStandardURL::SetFile(nsIFile* file) {
+ NS_ENSURE_ARG_POINTER(file);
+
+ nsresult rv;
+ nsAutoCString url;
+
+ rv = net_GetURLSpecFromFile(file, url);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t oldURLType = mURLType;
+ uint32_t oldDefaultPort = mDefaultPort;
+ rv = Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, url, nullptr, nullptr);
+
+ if (NS_FAILED(rv)) {
+ // Restore the old url type and default port if the call to Init fails.
+ mURLType = oldURLType;
+ mDefaultPort = oldDefaultPort;
+ return rv;
+ }
+
+ // must clone |file| since its value is not guaranteed to remain constant
+ InvalidateCache();
+ if (NS_FAILED(file->Clone(getter_AddRefs(mFile)))) {
+ NS_WARNING("nsIFile::Clone failed");
+ // failure to clone is not fatal (GetFile will generate mFile)
+ mFile = nullptr;
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIStandardURL
+//----------------------------------------------------------------------------
+
+nsresult nsStandardURL::Init(uint32_t urlType, int32_t defaultPort,
+ const nsACString& spec, const char* charset,
+ nsIURI* baseURI) {
+ if (spec.Length() > StaticPrefs::network_standard_url_max_length() ||
+ defaultPort > std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ switch (urlType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad urlType");
+ return NS_ERROR_INVALID_ARG;
+ }
+ mDefaultPort = defaultPort;
+ mURLType = urlType;
+
+ const auto* encoding =
+ charset ? Encoding::ForLabelNoReplacement(MakeStringSpan(charset))
+ : nullptr;
+ // URI can't be encoded in UTF-16BE or UTF-16LE. Truncate encoding
+ // if it is one of utf encodings (since a null encoding implies
+ // UTF-8, this is safe even if encoding is UTF-8).
+ if (IsUTFEncoding(encoding)) {
+ encoding = nullptr;
+ }
+
+ if (baseURI && net_IsAbsoluteURL(spec)) {
+ baseURI = nullptr;
+ }
+
+ if (!baseURI) {
+ return SetSpecWithEncoding(spec, encoding);
+ }
+
+ nsAutoCString buf;
+ nsresult rv = baseURI->Resolve(spec, buf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return SetSpecWithEncoding(buf, encoding);
+}
+
+nsresult nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort) {
+ InvalidateCache();
+
+ // should never be more than 16 bit
+ if (aNewDefaultPort >= std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // If we're already using the new default-port as a custom port, then clear
+ // it off of our mSpec & set mPort to -1, to indicate that we'll be using
+ // the default from now on (which happens to match what we already had).
+ if (mPort == aNewDefaultPort) {
+ ReplacePortInSpec(-1);
+ mPort = -1;
+ }
+ mDefaultPort = aNewDefaultPort;
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISerializable
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsStandardURL::Read(nsIObjectInputStream* stream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsStandardURL::ReadPrivate(nsIObjectInputStream* stream) {
+ MOZ_ASSERT(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host");
+
+ // If we exit early, make sure to clear the URL so we don't fail the sanity
+ // check in the destructor
+ auto clearOnExit = MakeScopeExit([&] { Clear(); });
+
+ nsresult rv;
+
+ uint32_t urlType;
+ rv = stream->Read32(&urlType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mURLType = urlType;
+ switch (mURLType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad urlType");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = stream->Read32((uint32_t*)&mPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Read32((uint32_t*)&mDefaultPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = NS_ReadOptionalCString(stream, mSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mScheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mAuthority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mUsername);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mPassword);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mFilepath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mDirectory);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mBasename);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mExtension);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // handle forward compatibility from older serializations that included mParam
+ URLSegment old_param;
+ rv = ReadSegment(stream, old_param);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mQuery);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ReadSegment(stream, mRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString oldOriginCharset;
+ rv = NS_ReadOptionalCString(stream, oldOriginCharset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool isMutable;
+ rv = stream->ReadBoolean(&isMutable);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ Unused << isMutable;
+
+ bool supportsFileURL;
+ rv = stream->ReadBoolean(&supportsFileURL);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mSupportsFileURL = supportsFileURL;
+
+ // wait until object is set up, then modify path to include the param
+ if (old_param.mLen >= 0) { // note that mLen=0 is ";"
+ // If this wasn't empty, it marks characters between the end of the
+ // file and start of the query - mPath should include the param,
+ // query and ref already. Bump the mFilePath and
+ // directory/basename/extension components to include this.
+ mFilepath.Merge(mSpec, ';', old_param);
+ mDirectory.Merge(mSpec, ';', old_param);
+ mBasename.Merge(mSpec, ';', old_param);
+ mExtension.Merge(mSpec, ';', old_param);
+ }
+
+ rv = CheckIfHostIsAscii();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!IsValid()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ clearOnExit.release();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Write(nsIObjectOutputStream* stream) {
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "The spec should never be this long, we missed a check.");
+ nsresult rv;
+
+ rv = stream->Write32(mURLType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Write32(uint32_t(mPort));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Write32(uint32_t(mDefaultPort));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = NS_WriteOptionalStringZ(stream, mSpec.get());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mScheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mAuthority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mUsername);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mPassword);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mFilepath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mDirectory);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mBasename);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mExtension);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // for backwards compatibility since we removed mParam. Note that this will
+ // mean that an older browser will read "" for mParam, and the param(s) will
+ // be part of mPath (as they after the removal of special handling). It only
+ // matters if you downgrade a browser to before the patch.
+ URLSegment empty;
+ rv = WriteSegment(stream, empty);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mQuery);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = WriteSegment(stream, mRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // former origin charset
+ rv = NS_WriteOptionalStringZ(stream, "");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // former mMutable
+ rv = stream->WriteBoolean(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->WriteBoolean(mSupportsFileURL);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // mDisplayHost is just a cache that can be recovered as needed.
+
+ return NS_OK;
+}
+
+inline ipc::StandardURLSegment ToIPCSegment(
+ const nsStandardURL::URLSegment& aSegment) {
+ return ipc::StandardURLSegment(aSegment.mPos, aSegment.mLen);
+}
+
+[[nodiscard]] inline bool FromIPCSegment(
+ const nsACString& aSpec, const ipc::StandardURLSegment& aSegment,
+ nsStandardURL::URLSegment& aTarget) {
+ // This seems to be just an empty segment.
+ if (aSegment.length() == -1) {
+ aTarget = nsStandardURL::URLSegment();
+ return true;
+ }
+
+ // A value of -1 means an empty segment, but < -1 is undefined.
+ if (NS_WARN_IF(aSegment.length() < -1)) {
+ return false;
+ }
+
+ CheckedInt<uint32_t> segmentLen = aSegment.position();
+ segmentLen += aSegment.length();
+ // Make sure the segment does not extend beyond the spec.
+ if (NS_WARN_IF(!segmentLen.isValid() ||
+ segmentLen.value() > aSpec.Length())) {
+ return false;
+ }
+
+ aTarget.mPos = aSegment.position();
+ aTarget.mLen = aSegment.length();
+
+ return true;
+}
+
+void nsStandardURL::Serialize(URIParams& aParams) {
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "The spec should never be this long, we missed a check.");
+ StandardURLParams params;
+
+ params.urlType() = mURLType;
+ params.port() = mPort;
+ params.defaultPort() = mDefaultPort;
+ params.spec() = mSpec;
+ params.scheme() = ToIPCSegment(mScheme);
+ params.authority() = ToIPCSegment(mAuthority);
+ params.username() = ToIPCSegment(mUsername);
+ params.password() = ToIPCSegment(mPassword);
+ params.host() = ToIPCSegment(mHost);
+ params.path() = ToIPCSegment(mPath);
+ params.filePath() = ToIPCSegment(mFilepath);
+ params.directory() = ToIPCSegment(mDirectory);
+ params.baseName() = ToIPCSegment(mBasename);
+ params.extension() = ToIPCSegment(mExtension);
+ params.query() = ToIPCSegment(mQuery);
+ params.ref() = ToIPCSegment(mRef);
+ params.supportsFileURL() = !!mSupportsFileURL;
+ params.isSubstituting() = false;
+ // mDisplayHost is just a cache that can be recovered as needed.
+
+ aParams = params;
+}
+
+bool nsStandardURL::Deserialize(const URIParams& aParams) {
+ MOZ_ASSERT(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host");
+ MOZ_ASSERT(!mFile, "Shouldn't have cached file");
+
+ if (aParams.type() != URIParams::TStandardURLParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ // If we exit early, make sure to clear the URL so we don't fail the sanity
+ // check in the destructor
+ auto clearOnExit = MakeScopeExit([&] { Clear(); });
+
+ const StandardURLParams& params = aParams.get_StandardURLParams();
+
+ mURLType = params.urlType();
+ switch (mURLType) {
+ case URLTYPE_STANDARD:
+ mParser = net_GetStdURLParser();
+ break;
+ case URLTYPE_AUTHORITY:
+ mParser = net_GetAuthURLParser();
+ break;
+ case URLTYPE_NO_AUTHORITY:
+ mParser = net_GetNoAuthURLParser();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad urlType");
+ return false;
+ }
+
+ mPort = params.port();
+ mDefaultPort = params.defaultPort();
+ mSpec = params.spec();
+ NS_ENSURE_TRUE(
+ mSpec.Length() <= StaticPrefs::network_standard_url_max_length(), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.scheme(), mScheme), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.authority(), mAuthority), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.username(), mUsername), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.password(), mPassword), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.host(), mHost), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.path(), mPath), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.filePath(), mFilepath), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.directory(), mDirectory), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.baseName(), mBasename), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.extension(), mExtension), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.query(), mQuery), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.ref(), mRef), false);
+
+ mSupportsFileURL = params.supportsFileURL();
+
+ nsresult rv = CheckIfHostIsAscii();
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // Some sanity checks
+ NS_ENSURE_TRUE(mScheme.mPos == 0, false);
+ NS_ENSURE_TRUE(mScheme.mLen > 0, false);
+ // Make sure scheme is followed by :// (3 characters)
+ NS_ENSURE_TRUE(mScheme.mLen < INT32_MAX - 3, false); // avoid overflow
+ NS_ENSURE_TRUE(mSpec.Length() >= (uint32_t)mScheme.mLen + 3, false);
+ NS_ENSURE_TRUE(
+ nsDependentCSubstring(mSpec, mScheme.mLen, 3).EqualsLiteral("://"),
+ false);
+ NS_ENSURE_TRUE(mPath.mLen != -1 && mSpec.CharAt(mPath.mPos) == '/', false);
+ NS_ENSURE_TRUE(mPath.mPos == mFilepath.mPos, false);
+ NS_ENSURE_TRUE(mQuery.mLen == -1 ||
+ (mQuery.mPos > 0 && mSpec.CharAt(mQuery.mPos - 1) == '?'),
+ false);
+ NS_ENSURE_TRUE(
+ mRef.mLen == -1 || (mRef.mPos > 0 && mSpec.CharAt(mRef.mPos - 1) == '#'),
+ false);
+
+ if (!IsValid()) {
+ return false;
+ }
+
+ clearOnExit.release();
+
+ return true;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISizeOf
+//----------------------------------------------------------------------------
+
+size_t nsStandardURL::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mSpec.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mDisplayHost.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mParser
+ // - mFile
+}
+
+size_t nsStandardURL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+} // namespace net
+} // namespace mozilla
+
+// For unit tests. Including nsStandardURL.h seems to cause problems
+nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result) {
+ return mozilla::net::nsStandardURL::NormalizeIPv4(host, result);
+}
+
+// For unit tests. Including nsStandardURL.h seems to cause problems
+nsresult Test_ParseIPv4Number(const nsACString& input, int32_t base,
+ uint32_t& number, uint32_t maxNumber) {
+ return mozilla::net::ParseIPv4Number(input, base, number, maxNumber);
+}
+
+int32_t Test_ValidateIPv4Number(const nsACString& host, int32_t bases[4],
+ int32_t dotIndex[3], bool& onlyBase10,
+ int32_t length) {
+ return mozilla::net::ValidateIPv4Number(host, bases, dotIndex, onlyBase10,
+ length, false);
+}
diff --git a/netwerk/base/nsStandardURL.h b/netwerk/base/nsStandardURL.h
new file mode 100644
index 0000000000..a4f644e722
--- /dev/null
+++ b/netwerk/base/nsStandardURL.h
@@ -0,0 +1,626 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStandardURL_h__
+#define nsStandardURL_h__
+
+#include <bitset>
+
+#include "nsString.h"
+#include "nsISerializable.h"
+#include "nsIFileURL.h"
+#include "nsIStandardURL.h"
+#include "mozilla/Encoding.h"
+#include "nsCOMPtr.h"
+#include "nsURLHelper.h"
+#include "nsISizeOf.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsISensitiveInfoHiddenURI.h"
+#include "nsIURIMutator.h"
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+# define DEBUG_DUMP_URLS_AT_SHUTDOWN
+#endif
+
+class nsIBinaryInputStream;
+class nsIBinaryOutputStream;
+class nsIIDNService;
+class nsIPrefBranch;
+class nsIFile;
+class nsIURLParser;
+
+namespace mozilla {
+class Encoding;
+namespace net {
+
+template <typename T>
+class URLSegmentNumber {
+ T mData{0};
+ bool mParity{false};
+
+ public:
+ URLSegmentNumber() = default;
+ explicit URLSegmentNumber(T data) : mData(data) {
+ mParity = CalculateParity();
+ }
+ bool operator==(URLSegmentNumber value) const { return mData == value.mData; }
+ bool operator!=(URLSegmentNumber value) const { return mData != value.mData; }
+ bool operator>(URLSegmentNumber value) const { return mData > value.mData; }
+ URLSegmentNumber operator+(int32_t value) const {
+ return URLSegmentNumber(mData + value);
+ }
+ URLSegmentNumber operator+(uint32_t value) const {
+ return URLSegmentNumber(mData + value);
+ }
+ URLSegmentNumber operator-(int32_t value) const {
+ return URLSegmentNumber(mData - value);
+ }
+ URLSegmentNumber operator-(uint32_t value) const {
+ return URLSegmentNumber(mData - value);
+ }
+ URLSegmentNumber operator+=(URLSegmentNumber value) {
+ mData += value.mData;
+ mParity = CalculateParity();
+ return *this;
+ }
+ URLSegmentNumber operator+=(T value) {
+ mData += value;
+ mParity = CalculateParity();
+ return *this;
+ }
+ URLSegmentNumber operator-=(URLSegmentNumber value) {
+ mData -= value.mData;
+ mParity = CalculateParity();
+ return *this;
+ }
+ URLSegmentNumber operator-=(T value) {
+ mData -= value;
+ mParity = CalculateParity();
+ return *this;
+ }
+ operator T() const { return mData; }
+ URLSegmentNumber& operator=(T value) {
+ mData = value;
+ mParity = CalculateParity();
+ return *this;
+ }
+ URLSegmentNumber& operator++() {
+ ++mData;
+ mParity = CalculateParity();
+ return *this;
+ }
+ URLSegmentNumber operator++(int) {
+ URLSegmentNumber value = *this;
+ *this += 1;
+ return value;
+ }
+ bool CalculateParity() const {
+ std::bitset<32> bits((uint32_t)mData);
+ return bits.count() % 2 == 0 ? false : true;
+ }
+ bool Parity() const { return mParity; }
+};
+
+//-----------------------------------------------------------------------------
+// standard URL implementation
+//-----------------------------------------------------------------------------
+
+class nsStandardURL : public nsIFileURL,
+ public nsIStandardURL,
+ public nsISerializable,
+ public nsISizeOf,
+ public nsISensitiveInfoHiddenURI
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ ,
+ public LinkedListElement<nsStandardURL>
+#endif
+{
+ protected:
+ virtual ~nsStandardURL();
+ explicit nsStandardURL(bool aSupportsFileURL = false, bool aTrackURL = true);
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIURL
+ NS_DECL_NSIFILEURL
+ NS_DECL_NSISTANDARDURL
+ NS_DECL_NSISERIALIZABLE
+ NS_DECL_NSISENSITIVEINFOHIDDENURI
+
+ // nsISizeOf
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ static void InitGlobalObjects();
+ static void ShutdownGlobalObjects();
+
+ public: /* internal -- HPUX compiler can't handle this being private */
+ //
+ // location and length of an url segment relative to mSpec
+ //
+ struct URLSegment {
+#ifdef EARLY_BETA_OR_EARLIER
+ URLSegmentNumber<uint32_t> mPos{0};
+ URLSegmentNumber<int32_t> mLen{-1};
+#else
+ uint32_t mPos{0};
+ int32_t mLen{-1};
+#endif
+
+ URLSegment() = default;
+ URLSegment(uint32_t pos, int32_t len) : mPos(pos), mLen(len) {}
+ URLSegment(const URLSegment& aCopy) = default;
+ void Reset() {
+ mPos = 0;
+ mLen = -1;
+ }
+ // Merge another segment following this one to it if they're contiguous
+ // Assumes we have something like "foo;bar" where this object is 'foo' and
+ // right is 'bar'.
+ void Merge(const nsCString& spec, const char separator,
+ const URLSegment& right) {
+ if (mLen >= 0 && *(spec.get() + mPos + mLen) == separator &&
+ mPos + mLen + 1 == right.mPos) {
+ mLen += 1 + right.mLen;
+ }
+ }
+ };
+
+ //
+ // URL segment encoder : performs charset conversion and URL escaping.
+ //
+ class nsSegmentEncoder {
+ public:
+ explicit nsSegmentEncoder(const Encoding* encoding = nullptr);
+
+ // Encode the given segment if necessary, and return the length of
+ // the encoded segment. The encoded segment is appended to |aOut|
+ // if and only if encoding is required.
+ int32_t EncodeSegmentCount(const char* str, const URLSegment& aSeg,
+ int16_t mask, nsCString& aOut, bool& appended,
+ uint32_t extraLen = 0);
+
+ // Encode the given string if necessary, and return a reference to
+ // the encoded string. Returns a reference to |result| if encoding
+ // is required. Otherwise, a reference to |str| is returned.
+ const nsACString& EncodeSegment(const nsACString& str, int16_t mask,
+ nsCString& result);
+
+ private:
+ const Encoding* mEncoding;
+ };
+ friend class nsSegmentEncoder;
+
+ static nsresult NormalizeIPv4(const nsACString& host, nsCString& result);
+
+ protected:
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum { eIgnoreRef, eHonorRef, eReplaceRef };
+
+ // Helper to share code between Equals and EqualsExceptRef
+ // NOTE: *not* virtual, because no one needs to override this so far...
+ nsresult EqualsInternal(nsIURI* unknownOther, RefHandlingEnum refHandlingMode,
+ bool* result);
+
+ virtual nsStandardURL* StartClone();
+
+ // Helper to share code between Clone methods.
+ nsresult CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& aNewRef, nsIURI** aClone);
+ // Helper method that copies member variables from the source StandardURL
+ // if copyCached = true, it will also copy mFile and mDisplayHost
+ nsresult CopyMembers(nsStandardURL* source, RefHandlingEnum mode,
+ const nsACString& newRef, bool copyCached = false);
+
+ // Helper for subclass implementation of GetFile(). Subclasses that map
+ // URIs to files in a special way should implement this method. It should
+ // ensure that our mFile is initialized, if it's possible.
+ // returns NS_ERROR_NO_INTERFACE if the url does not map to a file
+ virtual nsresult EnsureFile();
+
+ virtual nsresult Clone(nsIURI** aURI);
+ virtual nsresult SetSpecInternal(const nsACString& input);
+ virtual nsresult SetScheme(const nsACString& input);
+ virtual nsresult SetUserPass(const nsACString& input);
+ virtual nsresult SetUsername(const nsACString& input);
+ virtual nsresult SetPassword(const nsACString& input);
+ virtual nsresult SetHostPort(const nsACString& aValue);
+ virtual nsresult SetHost(const nsACString& input);
+ virtual nsresult SetPort(int32_t port);
+ virtual nsresult SetPathQueryRef(const nsACString& input);
+ virtual nsresult SetRef(const nsACString& input);
+ virtual nsresult SetFilePath(const nsACString& input);
+ virtual nsresult SetQuery(const nsACString& input);
+ virtual nsresult SetQueryWithEncoding(const nsACString& input,
+ const Encoding* encoding);
+ bool Deserialize(const mozilla::ipc::URIParams&);
+ nsresult ReadPrivate(nsIObjectInputStream* stream);
+
+ private:
+ nsresult Init(uint32_t urlType, int32_t defaultPort, const nsACString& spec,
+ const char* charset, nsIURI* baseURI);
+ nsresult SetDefaultPort(int32_t aNewDefaultPort);
+ nsresult SetFile(nsIFile* file);
+
+ nsresult SetFileNameInternal(const nsACString& input);
+ nsresult SetFileBaseNameInternal(const nsACString& input);
+ nsresult SetFileExtensionInternal(const nsACString& input);
+
+ int32_t Port() { return mPort == -1 ? mDefaultPort : mPort; }
+
+ void ReplacePortInSpec(int32_t aNewPort);
+ void Clear();
+ void InvalidateCache(bool invalidateCachedFile = true);
+
+ bool ValidIPv6orHostname(const char* host, uint32_t length);
+ static bool IsValidOfBase(unsigned char c, const uint32_t base);
+ nsresult NormalizeIDN(const nsCString& host, nsCString& result);
+ nsresult CheckIfHostIsAscii();
+ void CoalescePath(netCoalesceFlags coalesceFlag, char* path);
+
+ uint32_t AppendSegmentToBuf(char*, uint32_t, const char*,
+ const URLSegment& input, URLSegment& output,
+ const nsCString* esc = nullptr,
+ bool useEsc = false, int32_t* diff = nullptr);
+ uint32_t AppendToBuf(char*, uint32_t, const char*, uint32_t);
+
+ nsresult BuildNormalizedSpec(const char* spec, const Encoding* encoding);
+ nsresult SetSpecWithEncoding(const nsACString& input,
+ const Encoding* encoding);
+
+ bool SegmentIs(const URLSegment& seg, const char* val,
+ bool ignoreCase = false);
+ bool SegmentIs(const char* spec, const URLSegment& seg, const char* val,
+ bool ignoreCase = false);
+ bool SegmentIs(const URLSegment& seg1, const char* val,
+ const URLSegment& seg2, bool ignoreCase = false);
+
+ int32_t ReplaceSegment(uint32_t pos, uint32_t len, const char* val,
+ uint32_t valLen);
+ int32_t ReplaceSegment(uint32_t pos, uint32_t len, const nsACString& val);
+
+ nsresult ParseURL(const char* spec, int32_t specLen);
+ nsresult ParsePath(const char* spec, uint32_t pathPos, int32_t pathLen = -1);
+
+ char* AppendToSubstring(uint32_t pos, int32_t len, const char* tail);
+
+ // dependent substring helpers
+ nsDependentCSubstring Segment(uint32_t pos, int32_t len); // see below
+ nsDependentCSubstring Segment(const URLSegment& s) {
+ return Segment(s.mPos, s.mLen);
+ }
+
+ // dependent substring getters
+ nsDependentCSubstring Prepath(); // see below
+ nsDependentCSubstring Scheme() { return Segment(mScheme); }
+ nsDependentCSubstring Userpass(bool includeDelim = false); // see below
+ nsDependentCSubstring Username() { return Segment(mUsername); }
+ nsDependentCSubstring Password() { return Segment(mPassword); }
+ nsDependentCSubstring Hostport(); // see below
+ nsDependentCSubstring Host(); // see below
+ nsDependentCSubstring Path() { return Segment(mPath); }
+ nsDependentCSubstring Filepath() { return Segment(mFilepath); }
+ nsDependentCSubstring Directory() { return Segment(mDirectory); }
+ nsDependentCSubstring Filename(); // see below
+ nsDependentCSubstring Basename() { return Segment(mBasename); }
+ nsDependentCSubstring Extension() { return Segment(mExtension); }
+ nsDependentCSubstring Query() { return Segment(mQuery); }
+ nsDependentCSubstring Ref() { return Segment(mRef); }
+
+ // shift the URLSegments to the right by diff
+ void ShiftFromAuthority(int32_t diff);
+ void ShiftFromUsername(int32_t diff);
+ void ShiftFromPassword(int32_t diff);
+ void ShiftFromHost(int32_t diff);
+ void ShiftFromPath(int32_t diff);
+ void ShiftFromFilepath(int32_t diff);
+ void ShiftFromDirectory(int32_t diff);
+ void ShiftFromBasename(int32_t diff);
+ void ShiftFromExtension(int32_t diff);
+ void ShiftFromQuery(int32_t diff);
+ void ShiftFromRef(int32_t diff);
+
+ // fastload helper functions
+ nsresult ReadSegment(nsIBinaryInputStream*, URLSegment&);
+ nsresult WriteSegment(nsIBinaryOutputStream*, const URLSegment&);
+
+ void FindHostLimit(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd);
+
+ // Asserts that the URL has sane values
+ void SanityCheck();
+
+ // Checks if the URL has a valid representation.
+ bool IsValid();
+
+ // mSpec contains the normalized version of the URL spec (UTF-8 encoded).
+ nsCString mSpec;
+ int32_t mDefaultPort{-1};
+ int32_t mPort{-1};
+
+ // url parts (relative to mSpec)
+ URLSegment mScheme;
+ URLSegment mAuthority;
+ URLSegment mUsername;
+ URLSegment mPassword;
+ URLSegment mHost;
+ URLSegment mPath;
+ URLSegment mFilepath;
+ URLSegment mDirectory;
+ URLSegment mBasename;
+ URLSegment mExtension;
+ URLSegment mQuery;
+ URLSegment mRef;
+
+ nsCOMPtr<nsIURLParser> mParser;
+
+ // mFile is protected so subclasses can access it directly
+ protected:
+ nsCOMPtr<nsIFile> mFile; // cached result for nsIFileURL::GetFile
+
+ private:
+ // cached result for nsIURI::GetDisplayHost
+ nsCString mDisplayHost;
+
+ enum { eEncoding_Unknown, eEncoding_ASCII, eEncoding_UTF8 };
+
+ uint32_t mURLType : 2; // nsIStandardURL::URLTYPE_xxx
+ uint32_t mSupportsFileURL : 1; // QI to nsIFileURL?
+ uint32_t mCheckedIfHostA : 1; // If set to true, it means either that
+ // mDisplayHost has a been initialized, or
+ // that the hostname is not punycode
+
+ // global objects.
+ static StaticRefPtr<nsIIDNService> gIDN;
+ static const char gHostLimitDigits[];
+
+ public:
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ void PrintSpec() const { printf(" %s\n", mSpec.get()); }
+#endif
+
+ public:
+ // We make this implementation a template so that we can avoid writing
+ // the same code for SubstitutingURL (which extends nsStandardURL)
+ template <class T>
+ class TemplatedMutator : public nsIURIMutator,
+ public BaseURIMutator<T>,
+ public nsIStandardURLMutator,
+ public nsIURLMutator,
+ public nsIFileURLMutator,
+ public nsISerializable {
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(BaseURIMutator<T>::mURI)
+
+ [[nodiscard]] NS_IMETHOD Deserialize(
+ const mozilla::ipc::URIParams& aParams) override {
+ return BaseURIMutator<T>::InitFromIPCParams(aParams);
+ }
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return BaseURIMutator<T>::InitFromInputStream(aStream);
+ }
+
+ [[nodiscard]] NS_IMETHOD Finalize(nsIURI** aURI) override {
+ BaseURIMutator<T>::mURI.forget(aURI);
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHOD SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return BaseURIMutator<T>::InitFromSpec(aSpec);
+ }
+
+ [[nodiscard]] NS_IMETHOD Init(uint32_t aURLType, int32_t aDefaultPort,
+ const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ RefPtr<T> uri;
+ if (BaseURIMutator<T>::mURI) {
+ // We don't need a new URI object if we already have one
+ BaseURIMutator<T>::mURI.swap(uri);
+ } else {
+ uri = Create();
+ }
+ nsresult rv =
+ uri->Init(aURLType, aDefaultPort, aSpec, aCharset, aBaseURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ BaseURIMutator<T>::mURI = std::move(uri);
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHODIMP SetDefaultPort(
+ int32_t aNewDefaultPort, nsIURIMutator** aMutator) override {
+ if (!BaseURIMutator<T>::mURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return BaseURIMutator<T>::mURI->SetDefaultPort(aNewDefaultPort);
+ }
+
+ [[nodiscard]] NS_IMETHOD SetFileName(const nsACString& aFileName,
+ nsIURIMutator** aMutator) override {
+ if (!BaseURIMutator<T>::mURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return BaseURIMutator<T>::mURI->SetFileNameInternal(aFileName);
+ }
+
+ [[nodiscard]] NS_IMETHOD SetFileBaseName(
+ const nsACString& aFileBaseName, nsIURIMutator** aMutator) override {
+ if (!BaseURIMutator<T>::mURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return BaseURIMutator<T>::mURI->SetFileBaseNameInternal(aFileBaseName);
+ }
+
+ [[nodiscard]] NS_IMETHOD SetFileExtension(
+ const nsACString& aFileExtension, nsIURIMutator** aMutator) override {
+ if (!BaseURIMutator<T>::mURI) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return BaseURIMutator<T>::mURI->SetFileExtensionInternal(aFileExtension);
+ }
+
+ T* Create() override { return new T(mMarkedFileURL); }
+
+ [[nodiscard]] NS_IMETHOD MarkFileURL() override {
+ mMarkedFileURL = true;
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHOD SetFile(nsIFile* aFile) override {
+ RefPtr<T> uri;
+ if (BaseURIMutator<T>::mURI) {
+ // We don't need a new URI object if we already have one
+ BaseURIMutator<T>::mURI.swap(uri);
+ } else {
+ uri = new T(/* aSupportsFileURL = */ true);
+ }
+
+ nsresult rv = uri->SetFile(aFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ BaseURIMutator<T>::mURI.swap(uri);
+ return NS_OK;
+ }
+
+ explicit TemplatedMutator() = default;
+
+ private:
+ virtual ~TemplatedMutator() = default;
+
+ bool mMarkedFileURL = false;
+
+ friend T;
+ };
+
+ class Mutator final : public TemplatedMutator<nsStandardURL> {
+ NS_DECL_ISUPPORTS
+ public:
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+ };
+
+ friend BaseURIMutator<nsStandardURL>;
+};
+
+#define NS_THIS_STANDARDURL_IMPL_CID \
+ { /* b8e3e97b-1ccd-4b45-af5a-79596770f5d7 */ \
+ 0xb8e3e97b, 0x1ccd, 0x4b45, { \
+ 0xaf, 0x5a, 0x79, 0x59, 0x67, 0x70, 0xf5, 0xd7 \
+ } \
+ }
+
+//-----------------------------------------------------------------------------
+// Dependent substring getters
+//-----------------------------------------------------------------------------
+
+inline nsDependentCSubstring nsStandardURL::Segment(uint32_t pos, int32_t len) {
+ if (len < 0) {
+ pos = 0;
+ len = 0;
+ }
+ return Substring(mSpec, pos, uint32_t(len));
+}
+
+inline nsDependentCSubstring nsStandardURL::Prepath() {
+ uint32_t len = 0;
+ if (mAuthority.mLen >= 0) len = mAuthority.mPos + mAuthority.mLen;
+ return Substring(mSpec, 0, len);
+}
+
+inline nsDependentCSubstring nsStandardURL::Userpass(bool includeDelim) {
+ uint32_t pos = 0, len = 0;
+ if (mUsername.mLen > 0 || mPassword.mLen > 0) {
+ if (mUsername.mLen > 0) {
+ pos = mUsername.mPos;
+ len = mUsername.mLen;
+ if (mPassword.mLen >= 0) {
+ len += (mPassword.mLen + 1);
+ }
+ } else {
+ pos = mPassword.mPos - 1;
+ len = mPassword.mLen + 1;
+ }
+
+ if (includeDelim) len++;
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline nsDependentCSubstring nsStandardURL::Hostport() {
+ uint32_t pos = 0, len = 0;
+ if (mAuthority.mLen > 0) {
+ pos = mHost.mPos;
+ len = mAuthority.mPos + mAuthority.mLen - pos;
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline nsDependentCSubstring nsStandardURL::Host() {
+ uint32_t pos = 0, len = 0;
+ if (mHost.mLen > 0) {
+ pos = mHost.mPos;
+ len = mHost.mLen;
+ if (mSpec.CharAt(pos) == '[' && mSpec.CharAt(pos + len - 1) == ']') {
+ pos++;
+ len -= 2;
+ }
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline nsDependentCSubstring nsStandardURL::Filename() {
+ uint32_t pos = 0, len = 0;
+ // if there is no basename, then there can be no extension
+ if (mBasename.mLen > 0) {
+ pos = mBasename.mPos;
+ len = mBasename.mLen;
+ if (mExtension.mLen >= 0) len += (mExtension.mLen + 1);
+ }
+ return Substring(mSpec, pos, len);
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStandardURL_h__
diff --git a/netwerk/base/nsStreamListenerTee.cpp b/netwerk/base/nsStreamListenerTee.cpp
new file mode 100644
index 0000000000..4de5db56f5
--- /dev/null
+++ b/netwerk/base/nsStreamListenerTee.cpp
@@ -0,0 +1,185 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamListenerTee.h"
+#include "nsProxyRelease.h"
+#include "nsIRequest.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsStreamListenerTee, nsIStreamListener, nsIRequestObserver,
+ nsIStreamListenerTee, nsIThreadRetargetableStreamListener,
+ nsIMultiPartChannelListener)
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnStartRequest(nsIRequest* request) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(request);
+ if (multiPartChannel) {
+ mIsMultiPart = true;
+ }
+
+ nsresult rv1 = mListener->OnStartRequest(request);
+ nsresult rv2 = NS_OK;
+ if (mObserver) rv2 = mObserver->OnStartRequest(request);
+
+ // Preserve NS_SUCCESS_XXX in rv1 in case mObserver didn't throw
+ return (NS_FAILED(rv2) && NS_SUCCEEDED(rv1)) ? rv2 : rv1;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnStopRequest(nsIRequest* request, nsresult status) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+ // it is critical that we close out the input stream tee
+ if (mInputTee) {
+ mInputTee->SetSink(nullptr);
+ mInputTee = nullptr;
+ }
+
+ if (!mIsMultiPart) {
+ // release sink on the same thread where the data was written (bug 716293)
+ if (mEventTarget) {
+ NS_ProxyRelease("nsStreamListenerTee::mSink", mEventTarget,
+ mSink.forget());
+ } else {
+ mSink = nullptr;
+ }
+ }
+
+ nsresult rv = mListener->OnStopRequest(request, status);
+ if (!mIsMultiPart) {
+ mListener = nullptr;
+ }
+ if (mObserver) {
+ mObserver->OnStopRequest(request, status);
+ if (!mIsMultiPart) {
+ mObserver = nullptr;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnDataAvailable(nsIRequest* request, nsIInputStream* input,
+ uint64_t offset, uint32_t count) {
+ NS_ENSURE_TRUE(mListener, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mSink, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIInputStream> tee;
+ nsresult rv;
+
+ if (!mInputTee) {
+ if (mEventTarget) {
+ rv = NS_NewInputStreamTeeAsync(getter_AddRefs(tee), input, mSink,
+ mEventTarget);
+ } else {
+ rv = NS_NewInputStreamTee(getter_AddRefs(tee), input, mSink);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ mInputTee = do_QueryInterface(tee, &rv);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ // re-initialize the input tee since the input stream may have changed.
+ rv = mInputTee->SetSource(input);
+ if (NS_FAILED(rv)) return rv;
+
+ tee = mInputTee;
+ }
+
+ return mListener->OnDataAvailable(request, tee, offset, count);
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnAfterLastPart(nsresult aStatus) {
+ // release sink on the same thread where the data was written (bug 716293)
+ if (mEventTarget) {
+ NS_ProxyRelease("nsStreamListenerTee::mSink", mEventTarget, mSink.forget());
+ } else {
+ mSink = nullptr;
+ }
+
+ if (nsCOMPtr<nsIMultiPartChannelListener> multi =
+ do_QueryInterface(mListener)) {
+ multi->OnAfterLastPart(aStatus);
+ }
+ if (!SameCOMIdentity(mListener, mObserver)) {
+ if (nsCOMPtr<nsIMultiPartChannelListener> multi =
+ do_QueryInterface(mObserver)) {
+ multi->OnAfterLastPart(aStatus);
+ }
+ }
+
+ mObserver = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::CheckListenerChain() {
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!mObserver) {
+ return rv;
+ }
+ retargetableListener = do_QueryInterface(mObserver, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::OnDataFinished(nsresult aStatus) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->OnDataFinished(aStatus);
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!mObserver) {
+ return rv;
+ }
+ retargetableListener = do_QueryInterface(mObserver, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->OnDataFinished(aStatus);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::Init(nsIStreamListener* listener, nsIOutputStream* sink,
+ nsIRequestObserver* requestObserver) {
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_ARG_POINTER(sink);
+ mListener = listener;
+ mSink = sink;
+ mObserver = requestObserver;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamListenerTee::InitAsync(nsIStreamListener* listener,
+ nsIEventTarget* eventTarget,
+ nsIOutputStream* sink,
+ nsIRequestObserver* requestObserver) {
+ NS_ENSURE_ARG_POINTER(eventTarget);
+ mEventTarget = eventTarget;
+ return Init(listener, sink, requestObserver);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamListenerTee.h b/netwerk/base/nsStreamListenerTee.h
new file mode 100644
index 0000000000..87328e02cd
--- /dev/null
+++ b/netwerk/base/nsStreamListenerTee.h
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamListenerTee_h__
+#define nsStreamListenerTee_h__
+
+#include "nsIStreamListenerTee.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIInputStreamTee.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "nsIEventTarget.h"
+#include "nsIMultiPartChannel.h"
+
+namespace mozilla {
+namespace net {
+
+class nsStreamListenerTee : public nsIStreamListenerTee,
+ public nsIMultiPartChannelListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSISTREAMLISTENERTEE
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+
+ nsStreamListenerTee() = default;
+
+ private:
+ virtual ~nsStreamListenerTee() = default;
+
+ nsCOMPtr<nsIInputStreamTee> mInputTee;
+ nsCOMPtr<nsIOutputStream> mSink;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIRequestObserver> mObserver;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+ bool mIsMultiPart = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsStreamListenerWrapper.cpp b/netwerk/base/nsStreamListenerWrapper.cpp
new file mode 100644
index 0000000000..3502f3ada3
--- /dev/null
+++ b/netwerk/base/nsStreamListenerWrapper.cpp
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamListenerWrapper.h"
+#ifdef DEBUG
+# include "MainThreadUtils.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsStreamListenerWrapper, nsIStreamListener,
+ nsIRequestObserver, nsIMultiPartChannelListener,
+ nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsStreamListenerWrapper::OnAfterLastPart(nsresult aStatus) {
+ if (nsCOMPtr<nsIMultiPartChannelListener> listener =
+ do_QueryInterface(mListener)) {
+ nsresult rv = NS_OK;
+ if (nsCOMPtr<nsIMultiPartChannelListener> listener =
+ do_QueryInterface(mListener)) {
+ rv = listener->OnAfterLastPart(aStatus);
+ }
+ mListener = nullptr;
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamListenerWrapper::CheckListenerChain() {
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStreamListenerWrapper::OnDataFinished(nsresult aStatus) {
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener);
+ if (retargetableListener) {
+ return retargetableListener->OnDataFinished(aStatus);
+ }
+
+ return NS_OK;
+}
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamListenerWrapper.h b/netwerk/base/nsStreamListenerWrapper.h
new file mode 100644
index 0000000000..a950837297
--- /dev/null
+++ b/netwerk/base/nsStreamListenerWrapper.h
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamListenerWrapper_h__
+#define nsStreamListenerWrapper_h__
+
+#include "nsCOMPtr.h"
+#include "nsIRequest.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIMultiPartChannel.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace net {
+
+// Wrapper class to make replacement of nsHttpChannel's listener
+// from JavaScript possible. It is workaround for bug 433711 and 682305.
+class nsStreamListenerWrapper final
+ : public nsIMultiPartChannelListener,
+ public nsIThreadRetargetableStreamListener {
+ public:
+ explicit nsStreamListenerWrapper(nsIStreamListener* listener)
+ : mListener(listener) {
+ MOZ_ASSERT(mListener, "no stream listener specified");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_SAFE_NSISTREAMLISTENER(mListener)
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ // Don't use NS_FORWARD_NSIREQUESTOBSERVER(mListener->) here, because we need
+ // to release mListener in OnStopRequest, and IDL-generated function doesn't.
+ NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
+ // OnStartRequest can come after OnStopRequest in certain cases (multipart
+ // listeners)
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
+ do_QueryInterface(aRequest);
+ if (multiPartChannel) {
+ mIsMulti = true;
+ }
+ return mListener->OnStartRequest(aRequest);
+ }
+ NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) override {
+ nsresult rv = mListener->OnStopRequest(aRequest, aStatusCode);
+ if (!mIsMulti) {
+ // Multipart channels can call OnStartRequest again
+ mListener = nullptr;
+ }
+ return rv;
+ }
+
+ private:
+ bool mIsMulti{false};
+ ~nsStreamListenerWrapper() = default;
+ nsCOMPtr<nsIStreamListener> mListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStreamListenerWrapper_h__
diff --git a/netwerk/base/nsStreamLoader.cpp b/netwerk/base/nsStreamLoader.cpp
new file mode 100644
index 0000000000..f73b260de3
--- /dev/null
+++ b/netwerk/base/nsStreamLoader.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamLoader.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsError.h"
+#include "mozilla/ProfilerLabels.h"
+
+#include <limits>
+
+namespace mozilla {
+namespace net {
+
+nsStreamLoader::nsStreamLoader() = default;
+
+NS_IMETHODIMP
+nsStreamLoader::Init(nsIStreamLoaderObserver* aStreamObserver,
+ nsIRequestObserver* aRequestObserver) {
+ NS_ENSURE_ARG_POINTER(aStreamObserver);
+ mObserver = aStreamObserver;
+ mRequestObserver = aRequestObserver;
+ return NS_OK;
+}
+
+nsresult nsStreamLoader::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsStreamLoader> it = new nsStreamLoader();
+ return it->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS(nsStreamLoader, nsIStreamLoader, nsIRequestObserver,
+ nsIStreamListener, nsIThreadRetargetableStreamListener)
+
+NS_IMETHODIMP
+nsStreamLoader::GetNumBytesRead(uint32_t* aNumBytes) {
+ *aNumBytes = mBytesRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::GetRequest(nsIRequest** aRequest) {
+ nsCOMPtr<nsIRequest> req = mRequest;
+ req.forget(aRequest);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnStartRequest(nsIRequest* request) {
+ nsCOMPtr<nsIChannel> chan(do_QueryInterface(request));
+ if (chan) {
+ int64_t contentLength = -1;
+ chan->GetContentLength(&contentLength);
+ if (contentLength >= 0) {
+ // On 64bit platforms size of uint64_t coincides with the size of size_t,
+ // so we want to compare with the minimum from size_t and int64_t.
+ if (static_cast<uint64_t>(contentLength) >
+ std::min(std::numeric_limits<size_t>::max(),
+ static_cast<size_t>(std::numeric_limits<int64_t>::max()))) {
+ // Too big to fit into size_t, so let's bail.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ // preallocate buffer
+ if (!mData.initCapacity(contentLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ if (mRequestObserver) {
+ mRequestObserver->OnStartRequest(request);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ AUTO_PROFILER_LABEL("nsStreamLoader::OnStopRequest", NETWORK);
+
+ if (mObserver) {
+ // provide nsIStreamLoader::request during call to OnStreamComplete
+ mRequest = request;
+ size_t length = mData.length();
+ uint8_t* elems = mData.extractOrCopyRawBuffer();
+ nsresult rv =
+ mObserver->OnStreamComplete(this, mContext, aStatus, length, elems);
+ if (rv != NS_SUCCESS_ADOPTED_DATA) {
+ // The observer didn't take ownership of the extracted data buffer, so
+ // put it back into mData.
+ mData.replaceRawBuffer(elems, length);
+ }
+ // done.. cleanup
+ ReleaseData();
+ mRequest = nullptr;
+ mObserver = nullptr;
+ }
+
+ if (mRequestObserver) {
+ mRequestObserver->OnStopRequest(request, aStatus);
+ mRequestObserver = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsStreamLoader::WriteSegmentFun(nsIInputStream* inStr, void* closure,
+ const char* fromSegment,
+ uint32_t toOffset, uint32_t count,
+ uint32_t* writeCount) {
+ nsStreamLoader* self = (nsStreamLoader*)closure;
+
+ if (!self->mData.append(fromSegment, count)) {
+ self->mData.clearAndFree();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamLoader::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) {
+ uint32_t countRead;
+ nsresult rv = inStr->ReadSegments(WriteSegmentFun, this, count, &countRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mBytesRead += countRead;
+ return NS_OK;
+}
+
+void nsStreamLoader::ReleaseData() { mData.clearAndFree(); }
+
+NS_IMETHODIMP
+nsStreamLoader::CheckListenerChain() { return NS_OK; }
+
+NS_IMETHODIMP
+nsStreamLoader::OnDataFinished(nsresult) { return NS_OK; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamLoader.h b/netwerk/base/nsStreamLoader.h
new file mode 100644
index 0000000000..1960c41db3
--- /dev/null
+++ b/netwerk/base/nsStreamLoader.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamLoader_h__
+#define nsStreamLoader_h__
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Vector.h"
+
+class nsIRequest;
+
+namespace mozilla {
+namespace net {
+
+class nsStreamLoader final : public nsIStreamLoader {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsStreamLoader();
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ protected:
+ ~nsStreamLoader() = default;
+
+ static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*, uint32_t,
+ uint32_t, uint32_t*);
+
+ // Utility method to free mData, if present, and update other state to
+ // reflect that no data has been allocated.
+ void ReleaseData();
+
+ nsCOMPtr<nsIStreamLoaderObserver> mObserver;
+ nsCOMPtr<nsISupports> mContext; // the observer's context
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIRequestObserver> mRequestObserver;
+
+ mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed> mBytesRead;
+
+ // Buffer to accumulate incoming data. We preallocate if contentSize is
+ // available.
+ mozilla::Vector<uint8_t, 0> mData;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStreamLoader_h__
diff --git a/netwerk/base/nsStreamTransportService.cpp b/netwerk/base/nsStreamTransportService.cpp
new file mode 100644
index 0000000000..e1369bbcb5
--- /dev/null
+++ b/netwerk/base/nsStreamTransportService.cpp
@@ -0,0 +1,429 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamTransportService.h"
+#include "ErrorList.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsNetSegmentUtils.h"
+#include "nsTransportUtils.h"
+#include "nsStreamUtils.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIPipe.h"
+#include "nsITransport.h"
+#include "nsIObserverService.h"
+#include "nsThreadPool.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsInputStreamTransport
+//
+// Implements nsIInputStream as a wrapper around the real input stream. This
+// allows the transport to support seeking, range-limiting, progress reporting,
+// and close-when-done semantics while utilizing NS_AsyncCopy.
+//-----------------------------------------------------------------------------
+
+class nsInputStreamTransport : public nsITransport,
+ public nsIAsyncInputStream,
+ public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ nsInputStreamTransport(nsIInputStream* source, bool closeWhenDone)
+ : mSource(source), mCloseWhenDone(closeWhenDone) {
+ mAsyncSource = do_QueryInterface(mSource);
+ }
+
+ private:
+ virtual ~nsInputStreamTransport() = default;
+
+ Mutex mMutex MOZ_UNANNOTATED{"nsInputStreamTransport::mMutex"};
+
+ // This value is protected by mutex.
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
+
+ nsCOMPtr<nsIAsyncInputStream> mPipeIn;
+
+ // while the copy is active, these members may only be accessed from the
+ // nsIInputStream implementation.
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsIInputStream> mSource;
+
+ // It can be null.
+ nsCOMPtr<nsIAsyncInputStream> mAsyncSource;
+
+ int64_t mOffset{0};
+ const bool mCloseWhenDone;
+
+ // this variable serves as a lock to prevent the state of the transport
+ // from being modified once the copy is in progress.
+ bool mInProgress{false};
+};
+
+NS_IMPL_ADDREF(nsInputStreamTransport);
+NS_IMPL_RELEASE(nsInputStreamTransport);
+
+NS_INTERFACE_MAP_BEGIN(nsInputStreamTransport)
+ NS_INTERFACE_MAP_ENTRY(nsITransport)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, !!mAsyncSource)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, !!mAsyncSource)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransport)
+NS_INTERFACE_MAP_END
+
+/** nsITransport **/
+
+NS_IMETHODIMP
+nsInputStreamTransport::OpenInputStream(uint32_t flags, uint32_t segsize,
+ uint32_t segcount,
+ nsIInputStream** result) {
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // XXX if the caller requests an unbuffered stream, then perhaps
+ // we'd want to simply return mSource; however, then we would
+ // not be reading mSource on a background thread. is this ok?
+
+ bool nonblocking = !(flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ NS_NewPipe2(getter_AddRefs(mPipeIn), getter_AddRefs(pipeOut), nonblocking,
+ true, segsize, segcount);
+
+ mInProgress = true;
+
+ // startup async copy process...
+ rv = NS_AsyncCopy(this, pipeOut, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
+ segsize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *result = do_AddRef(mPipeIn).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::OpenOutputStream(uint32_t flags, uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream** result) {
+ // this transport only supports reading!
+ MOZ_ASSERT_UNREACHABLE("nsInputStreamTransport::OpenOutputStream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::Close(nsresult reason) {
+ if (NS_SUCCEEDED(reason)) reason = NS_BASE_STREAM_CLOSED;
+
+ return mPipeIn->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::SetEventSink(nsITransportEventSink* sink,
+ nsIEventTarget* target) {
+ NS_ENSURE_TRUE(!mInProgress, NS_ERROR_IN_PROGRESS);
+
+ if (target) {
+ return net_NewTransportEventSinkProxy(getter_AddRefs(mEventSink), sink,
+ target);
+ }
+
+ mEventSink = sink;
+ return NS_OK;
+}
+
+/** nsIInputStream **/
+
+NS_IMETHODIMP
+nsInputStreamTransport::Close() {
+ if (mCloseWhenDone) mSource->Close();
+
+ // make additional reads return early...
+ mOffset = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::Available(uint64_t* result) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::StreamStatus() { return mSource->StreamStatus(); }
+
+NS_IMETHODIMP
+nsInputStreamTransport::Read(char* buf, uint32_t count, uint32_t* result) {
+ nsresult rv = mSource->Read(buf, count, result);
+
+ if (NS_SUCCEEDED(rv)) {
+ mOffset += *result;
+ if (mEventSink) {
+ mEventSink->OnTransportStatus(this, NS_NET_STATUS_READING, mOffset, -1);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* result) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsInputStreamTransport::IsNonBlocking(bool* result) {
+ *result = false;
+ return NS_OK;
+}
+
+// nsIAsyncInputStream interface
+
+NS_IMETHODIMP
+nsInputStreamTransport::CloseWithStatus(nsresult aStatus) { return Close(); }
+
+NS_IMETHODIMP
+nsInputStreamTransport::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ NS_ENSURE_STATE(!!mAsyncSource);
+
+ nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (NS_WARN_IF(mAsyncWaitCallback && aCallback &&
+ mAsyncWaitCallback != aCallback)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mAsyncWaitCallback = aCallback;
+ }
+
+ return mAsyncSource->AsyncWait(callback, aFlags, aRequestedCount,
+ aEventTarget);
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+nsInputStreamTransport::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mMutex);
+
+ // We have been canceled in the meanwhile.
+ if (!mAsyncWaitCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncWaitCallback);
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamReady(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsStreamTransportService
+//-----------------------------------------------------------------------------
+
+nsStreamTransportService::nsStreamTransportService() = default;
+
+nsStreamTransportService::~nsStreamTransportService() {
+ NS_ASSERTION(!mPool, "thread pool wasn't shutdown");
+}
+
+nsresult nsStreamTransportService::Init() {
+ // Can't be used multithreaded before this
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ MOZ_ASSERT(!mPool);
+ mPool = new nsThreadPool();
+
+ // Configure the pool
+ mPool->SetName("StreamTrans"_ns);
+ mPool->SetThreadLimit(25);
+ mPool->SetIdleThreadLimit(5);
+ mPool->SetIdleThreadTimeoutRegressive(true);
+ mPool->SetIdleThreadTimeout(PR_SecondsToInterval(30));
+ MOZ_POP_THREAD_SAFETY
+
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsStreamTransportService, nsIStreamTransportService,
+ nsIEventTarget, nsIObserver)
+
+NS_IMETHODIMP
+nsStreamTransportService::DispatchFromScript(nsIRunnable* task,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event(task);
+ return Dispatch(event.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::Dispatch(already_AddRefed<nsIRunnable> task,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event(task); // so it gets released on failure paths
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED);
+ return pool->Dispatch(event.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::RegisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::UnregisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(bool)
+nsStreamTransportService::IsOnCurrentThreadInfallible() {
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ pool = mPool;
+ }
+ if (!pool) {
+ return false;
+ }
+ return pool->IsOnCurrentThread();
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::IsOnCurrentThread(bool* result) {
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED);
+ return pool->IsOnCurrentThread(result);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::CreateInputTransport(nsIInputStream* stream,
+ bool closeWhenDone,
+ nsITransport** result) {
+ RefPtr<nsInputStreamTransport> trans =
+ new nsInputStreamTransport(stream, closeWhenDone);
+ trans.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ NS_ASSERTION(strcmp(topic, "xpcom-shutdown-threads") == 0, "oops");
+
+ {
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ mIsShutdown = true;
+ pool = mPool.forget();
+ }
+
+ if (pool) {
+ pool->Shutdown();
+ }
+ }
+ return NS_OK;
+}
+
+class AvailableEvent final : public Runnable {
+ public:
+ AvailableEvent(nsIInputStream* stream, nsIInputAvailableCallback* callback)
+ : Runnable("net::AvailableEvent"),
+ mStream(stream),
+ mCallback(callback),
+ mDoingCallback(false),
+ mSize(0),
+ mResultForCallback(NS_OK) {
+ mCallbackTarget = GetCurrentSerialEventTarget();
+ }
+
+ NS_IMETHOD Run() override {
+ if (mDoingCallback) {
+ // pong
+ mCallback->OnInputAvailableComplete(mSize, mResultForCallback);
+ mCallback = nullptr;
+ } else {
+ // ping
+ mResultForCallback = mStream->Available(&mSize);
+ mStream = nullptr;
+ mDoingCallback = true;
+
+ nsCOMPtr<nsIRunnable> event(this); // overly cute
+ mCallbackTarget->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+ mCallbackTarget = nullptr;
+ }
+ return NS_OK;
+ }
+
+ private:
+ virtual ~AvailableEvent() = default;
+
+ nsCOMPtr<nsIInputStream> mStream;
+ nsCOMPtr<nsIInputAvailableCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ bool mDoingCallback;
+ uint64_t mSize;
+ nsresult mResultForCallback;
+};
+
+NS_IMETHODIMP
+nsStreamTransportService::InputAvailable(nsIInputStream* stream,
+ nsIInputAvailableCallback* callback) {
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ nsCOMPtr<nsIRunnable> event = new AvailableEvent(stream, callback);
+ return pool->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamTransportService.h b/netwerk/base/nsStreamTransportService.h
new file mode 100644
index 0000000000..1229c0ca44
--- /dev/null
+++ b/netwerk/base/nsStreamTransportService.h
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamTransportService_h__
+#define nsStreamTransportService_h__
+
+#include "nsIStreamTransportService.h"
+#include "nsIEventTarget.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/Mutex.h"
+
+class nsIThreadPool;
+
+namespace mozilla {
+namespace net {
+
+class nsStreamTransportService final : public nsIStreamTransportService,
+ public nsIEventTarget,
+ public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMTRANSPORTSERVICE
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+ nsStreamTransportService();
+
+ private:
+ ~nsStreamTransportService();
+
+ nsCOMPtr<nsIThreadPool> mPool MOZ_GUARDED_BY(mShutdownLock);
+
+ mozilla::Mutex mShutdownLock{"nsStreamTransportService.mShutdownLock"};
+ bool mIsShutdown MOZ_GUARDED_BY(mShutdownLock){false};
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/base/nsSyncStreamListener.cpp b/netwerk/base/nsSyncStreamListener.cpp
new file mode 100644
index 0000000000..7e5ec94231
--- /dev/null
+++ b/netwerk/base/nsSyncStreamListener.cpp
@@ -0,0 +1,169 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsIOService.h"
+#include "nsIPipe.h"
+#include "nsSyncStreamListener.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+using namespace mozilla::net;
+
+nsSyncStreamListener::nsSyncStreamListener() {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_NewPipe(getter_AddRefs(mPipeIn), getter_AddRefs(mPipeOut),
+ mozilla::net::nsIOService::gDefaultSegmentSize,
+ UINT32_MAX, // no size limit
+ false, false);
+}
+
+nsresult nsSyncStreamListener::WaitForData() {
+ mKeepWaiting = true;
+
+ if (!mozilla::SpinEventLoopUntil("nsSyncStreamListener::Create"_ns,
+ [&]() { return !mKeepWaiting; })) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsSyncStreamListener, nsIStreamListener, nsIRequestObserver,
+ nsIInputStream, nsISyncStreamListener)
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsISyncStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::GetInputStream(nsIInputStream** result) {
+ *result = do_AddRef(this).take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnStartRequest(nsIRequest* request) { return NS_OK; }
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* stream, uint64_t offset,
+ uint32_t count) {
+ uint32_t bytesWritten;
+
+ nsresult rv = mPipeOut->WriteFrom(stream, count, &bytesWritten);
+
+ // if we get an error, then return failure. this will cause the
+ // channel to be canceled, and as a result our OnStopRequest method
+ // will be called immediately. because of this we do not need to
+ // set mStatus or mKeepWaiting here.
+ if (NS_FAILED(rv)) return rv;
+
+ // we expect that all data will be written to the pipe because
+ // the pipe was created to have "infinite" room.
+ NS_ASSERTION(bytesWritten == count, "did not write all data");
+
+ mKeepWaiting = false; // unblock Read
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::OnStopRequest(nsIRequest* request, nsresult status) {
+ mStatus = status;
+ mKeepWaiting = false; // unblock Read
+ mDone = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsSyncStreamListener::nsIInputStream
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsSyncStreamListener::Close() {
+ mStatus = NS_BASE_STREAM_CLOSED;
+ mDone = true;
+
+ // It'd be nice if we could explicitly cancel the request at this point,
+ // but we don't have a reference to it, so the best we can do is close the
+ // pipe so that the next OnDataAvailable event will fail.
+ if (mPipeIn) {
+ mPipeIn->Close();
+ mPipeIn = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::Available(uint64_t* result) {
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ mStatus = mPipeIn->Available(result);
+ if (NS_SUCCEEDED(mStatus) && (*result == 0) && !mDone) {
+ nsresult rv = WaitForData();
+ if (NS_FAILED(rv)) {
+ // Note that `WaitForData` could fail `mStatus`. Do not overwrite if it's
+ // the case.
+ mStatus = NS_SUCCEEDED(mStatus) ? rv : mStatus;
+ } else if (NS_SUCCEEDED(mStatus)) {
+ mStatus = mPipeIn->Available(result);
+ }
+ }
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::StreamStatus() {
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ mStatus = mPipeIn->StreamStatus();
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::Read(char* buf, uint32_t bufLen, uint32_t* result) {
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *result = 0;
+ return NS_OK;
+ }
+
+ uint64_t avail64;
+ if (NS_FAILED(Available(&avail64))) return mStatus;
+
+ uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)bufLen);
+ mStatus = mPipeIn->Read(buf, avail, result);
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* result) {
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *result = 0;
+ return NS_OK;
+ }
+
+ uint64_t avail64;
+ if (NS_FAILED(Available(&avail64))) return mStatus;
+
+ uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)count);
+ mStatus = mPipeIn->ReadSegments(writer, closure, avail, result);
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsSyncStreamListener::IsNonBlocking(bool* result) {
+ *result = false;
+ return NS_OK;
+}
diff --git a/netwerk/base/nsSyncStreamListener.h b/netwerk/base/nsSyncStreamListener.h
new file mode 100644
index 0000000000..9bd9908535
--- /dev/null
+++ b/netwerk/base/nsSyncStreamListener.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSyncStreamListener_h__
+#define nsSyncStreamListener_h__
+
+#include "nsISyncStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+//-----------------------------------------------------------------------------
+
+class nsSyncStreamListener final : public nsISyncStreamListener,
+ public nsIInputStream {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISYNCSTREAMLISTENER
+ NS_DECL_NSIINPUTSTREAM
+
+ private:
+ // Factory method:
+ friend nsresult NS_NewSyncStreamListener(nsIStreamListener** result,
+ nsIInputStream** stream);
+ nsSyncStreamListener();
+ ~nsSyncStreamListener() = default;
+
+ nsresult Init();
+
+ nsresult WaitForData();
+
+ nsCOMPtr<nsIInputStream> mPipeIn;
+ nsCOMPtr<nsIOutputStream> mPipeOut;
+ nsresult mStatus{NS_OK};
+ bool mKeepWaiting{false};
+ bool mDone{false};
+};
+
+#endif // nsSyncStreamListener_h__
diff --git a/netwerk/base/nsTransportUtils.cpp b/netwerk/base/nsTransportUtils.cpp
new file mode 100644
index 0000000000..df53ead198
--- /dev/null
+++ b/netwerk/base/nsTransportUtils.cpp
@@ -0,0 +1,133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nsITransport.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "nsTransportUtils.h"
+
+using namespace mozilla;
+
+//-----------------------------------------------------------------------------
+
+class nsTransportStatusEvent;
+
+class nsTransportEventSinkProxy : public nsITransportEventSink {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTEVENTSINK
+
+ nsTransportEventSinkProxy(nsITransportEventSink* sink, nsIEventTarget* target)
+ : mSink(sink),
+ mTarget(target),
+ mLock("nsTransportEventSinkProxy.mLock"),
+ mLastEvent(nullptr) {
+ NS_ADDREF(mSink);
+ }
+
+ private:
+ virtual ~nsTransportEventSinkProxy() {
+ // our reference to mSink could be the last, so be sure to release
+ // it on the target thread. otherwise, we could get into trouble.
+ NS_ProxyRelease("nsTransportEventSinkProxy::mSink", mTarget,
+ dont_AddRef(mSink));
+ }
+
+ public:
+ nsITransportEventSink* mSink;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ Mutex mLock MOZ_UNANNOTATED;
+ nsTransportStatusEvent* mLastEvent;
+};
+
+class nsTransportStatusEvent : public Runnable {
+ public:
+ nsTransportStatusEvent(nsTransportEventSinkProxy* proxy,
+ nsITransport* transport, nsresult status,
+ int64_t progress, int64_t progressMax)
+ : Runnable("nsTransportStatusEvent"),
+ mProxy(proxy),
+ mTransport(transport),
+ mStatus(status),
+ mProgress(progress),
+ mProgressMax(progressMax) {}
+
+ ~nsTransportStatusEvent() {
+ auto ReleaseTransport = [transport(std::move(mTransport))]() mutable {};
+ if (!net::OnSocketThread()) {
+ net::gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "nsHttpConnection::~nsHttpConnection", std::move(ReleaseTransport)));
+ }
+ }
+
+ NS_IMETHOD Run() override {
+ // since this event is being handled, we need to clear the proxy's ref.
+ // if not coalescing all, then last event may not equal self!
+ {
+ MutexAutoLock lock(mProxy->mLock);
+ if (mProxy->mLastEvent == this) mProxy->mLastEvent = nullptr;
+ }
+
+ mProxy->mSink->OnTransportStatus(mTransport, mStatus, mProgress,
+ mProgressMax);
+ return NS_OK;
+ }
+
+ RefPtr<nsTransportEventSinkProxy> mProxy;
+
+ // parameters to OnTransportStatus
+ nsCOMPtr<nsITransport> mTransport;
+ nsresult mStatus;
+ int64_t mProgress;
+ int64_t mProgressMax;
+};
+
+NS_IMPL_ISUPPORTS(nsTransportEventSinkProxy, nsITransportEventSink)
+
+NS_IMETHODIMP
+nsTransportEventSinkProxy::OnTransportStatus(nsITransport* transport,
+ nsresult status, int64_t progress,
+ int64_t progressMax) {
+ nsresult rv = NS_OK;
+ RefPtr<nsTransportStatusEvent> event;
+ {
+ MutexAutoLock lock(mLock);
+
+ // try to coalesce events! ;-)
+ if (mLastEvent && (mLastEvent->mStatus == status)) {
+ mLastEvent->mStatus = status;
+ mLastEvent->mProgress = progress;
+ mLastEvent->mProgressMax = progressMax;
+ } else {
+ event = new nsTransportStatusEvent(this, transport, status, progress,
+ progressMax);
+ if (!event) rv = NS_ERROR_OUT_OF_MEMORY;
+ mLastEvent = event; // weak ref
+ }
+ }
+ if (event) {
+ rv = mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to post transport status event");
+
+ MutexAutoLock lock(mLock); // cleanup.. don't reference anymore!
+ mLastEvent = nullptr;
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult net_NewTransportEventSinkProxy(nsITransportEventSink** result,
+ nsITransportEventSink* sink,
+ nsIEventTarget* target) {
+ RefPtr<nsTransportEventSinkProxy> res =
+ new nsTransportEventSinkProxy(sink, target);
+ res.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsTransportUtils.h b/netwerk/base/nsTransportUtils.h
new file mode 100644
index 0000000000..141b9e4eda
--- /dev/null
+++ b/netwerk/base/nsTransportUtils.h
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTransportUtils_h__
+#define nsTransportUtils_h__
+
+#include "nsITransport.h"
+
+/**
+ * This function returns a proxy object for a transport event sink instance.
+ * The transport event sink will be called on the thread indicated by the
+ * given event target. Like events are automatically coalesced. This means
+ * that for example if the status value is the same from event to event, and
+ * the previous event has not yet been delivered, then only one event will
+ * be delivered. The progress reported will be that from the second event.
+
+ * Coalescing events can help prevent a backlog of unprocessed transport
+ * events in the case that the target thread is overworked.
+ */
+nsresult net_NewTransportEventSinkProxy(nsITransportEventSink** aResult,
+ nsITransportEventSink* aSink,
+ nsIEventTarget* aTarget);
+
+#endif // nsTransportUtils_h__
diff --git a/netwerk/base/nsUDPSocket.cpp b/netwerk/base/nsUDPSocket.cpp
new file mode 100644
index 0000000000..991c28acf8
--- /dev/null
+++ b/netwerk/base/nsUDPSocket.cpp
@@ -0,0 +1,1553 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Telemetry.h"
+
+#include "nsQueryObject.h"
+#include "nsSocketTransport2.h"
+#include "nsUDPSocket.h"
+#include "nsProxyRelease.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIOService.h"
+#include "prnetdb.h"
+#include "prio.h"
+#include "nsNetAddr.h"
+#include "nsNetSegmentUtils.h"
+#include "IOActivityMonitor.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "prerror.h"
+#include "nsThreadUtils.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsICancelable.h"
+#include "nsIPipe.h"
+#include "nsWrapperCacheInlines.h"
+#include "HttpConnectionUDP.h"
+#include "mozilla/ProfilerBandwidthCounter.h"
+#include "mozilla/StaticPrefs_network.h"
+
+#if defined(FUZZING)
+# include "FuzzyLayer.h"
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+static const uint32_t UDP_PACKET_CHUNK_SIZE = 1400;
+
+//-----------------------------------------------------------------------------
+
+using nsUDPSocketFunc = void (nsUDPSocket::*)();
+
+static nsresult PostEvent(nsUDPSocket* s, nsUDPSocketFunc func) {
+ if (!gSocketTransportService) return NS_ERROR_FAILURE;
+
+ return gSocketTransportService->Dispatch(
+ NewRunnableMethod("net::PostEvent", s, func), NS_DISPATCH_NORMAL);
+}
+
+static nsresult ResolveHost(const nsACString& host,
+ const OriginAttributes& aOriginAttributes,
+ nsIDNSListener* listener) {
+ nsresult rv;
+
+ nsCOMPtr<nsIDNSService> dns =
+ do_GetService("@mozilla.org/network/dns-service;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+ return dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr,
+ listener, nullptr, aOriginAttributes,
+ getter_AddRefs(tmpOutstanding));
+}
+
+static nsresult CheckIOStatus(const NetAddr* aAddr) {
+ MOZ_ASSERT(gIOService);
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (gIOService->IsOffline() &&
+ (StaticPrefs::network_disable_localhost_when_offline() ||
+ !aAddr->IsLoopbackAddr())) {
+ return NS_ERROR_OFFLINE;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+class SetSocketOptionRunnable : public Runnable {
+ public:
+ SetSocketOptionRunnable(nsUDPSocket* aSocket, const PRSocketOptionData& aOpt)
+ : Runnable("net::SetSocketOptionRunnable"),
+ mSocket(aSocket),
+ mOpt(aOpt) {}
+
+ NS_IMETHOD Run() override { return mSocket->SetSocketOption(mOpt); }
+
+ private:
+ RefPtr<nsUDPSocket> mSocket;
+ PRSocketOptionData mOpt;
+};
+
+//-----------------------------------------------------------------------------
+// nsUDPOutputStream impl
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS(nsUDPOutputStream, nsIOutputStream)
+
+nsUDPOutputStream::nsUDPOutputStream(nsUDPSocket* aSocket, PRFileDesc* aFD,
+ PRNetAddr& aPrClientAddr)
+ : mSocket(aSocket),
+ mFD(aFD),
+ mPrClientAddr(aPrClientAddr),
+ mIsClosed(false) {}
+
+NS_IMETHODIMP nsUDPOutputStream::Close() {
+ if (mIsClosed) return NS_BASE_STREAM_CLOSED;
+
+ mIsClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::Flush() { return NS_OK; }
+
+NS_IMETHODIMP nsUDPOutputStream::StreamStatus() {
+ return mIsClosed ? NS_BASE_STREAM_CLOSED : NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ if (mIsClosed) return NS_BASE_STREAM_CLOSED;
+
+ *_retval = 0;
+ int32_t count =
+ PR_SendTo(mFD, aBuf, aCount, 0, &mPrClientAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ PRErrorCode code = PR_GetError();
+ return ErrorAccordingToNSPR(code);
+ }
+
+ *_retval = count;
+
+ mSocket->AddOutputBytes(count);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsUDPOutputStream::IsNonBlocking(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsUDPMessage impl
+//-----------------------------------------------------------------------------
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsUDPMessage)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsUDPMessage)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsUDPMessage)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsUDPMessage)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIUDPMessage)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsUDPMessage)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJsobj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsUDPMessage)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsUDPMessage)
+ tmp->mJsobj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+nsUDPMessage::nsUDPMessage(NetAddr* aAddr, nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>&& aData)
+ : mOutputStream(aOutputStream), mData(std::move(aData)) {
+ memcpy(&mAddr, aAddr, sizeof(NetAddr));
+}
+
+nsUDPMessage::~nsUDPMessage() { DropJSObjects(this); }
+
+NS_IMETHODIMP
+nsUDPMessage::GetFromAddr(nsINetAddr** aFromAddr) {
+ NS_ENSURE_ARG_POINTER(aFromAddr);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aFromAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetData(nsACString& aData) {
+ aData.Assign(reinterpret_cast<const char*>(mData.Elements()), mData.Length());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetOutputStream(nsIOutputStream** aOutputStream) {
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ *aOutputStream = do_AddRef(mOutputStream).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetRawData(JSContext* cx, JS::MutableHandle<JS::Value> aRawData) {
+ if (!mJsobj) {
+ ErrorResult error;
+ mJsobj = dom::Uint8Array::Create(cx, nullptr, mData, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+ HoldJSObjects(this);
+ }
+ aRawData.setObject(*mJsobj);
+ return NS_OK;
+}
+
+FallibleTArray<uint8_t>& nsUDPMessage::GetDataAsTArray() { return mData; }
+
+//-----------------------------------------------------------------------------
+// nsUDPSocket
+//-----------------------------------------------------------------------------
+
+nsUDPSocket::nsUDPSocket() {
+ // we want to be able to access the STS directly, and it may not have been
+ // constructed yet. the STS constructor sets gSocketTransportService.
+ if (!gSocketTransportService) {
+ // This call can fail if we're offline, for example.
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ }
+
+ mSts = gSocketTransportService;
+}
+
+nsUDPSocket::~nsUDPSocket() { CloseSocket(); }
+
+void nsUDPSocket::AddOutputBytes(int32_t aBytes) {
+ mByteWriteCount += aBytes;
+ profiler_count_bandwidth_written_bytes(aBytes);
+}
+
+void nsUDPSocket::OnMsgClose() {
+ UDPSOCKET_LOG(("nsUDPSocket::OnMsgClose [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition)) return;
+
+ // tear down socket. this signals the STS to detach our socket handler.
+ mCondition = NS_BINDING_ABORTED;
+
+ // if we are attached, then socket transport service will call our
+ // OnSocketDetached method automatically. Otherwise, we have to call it
+ // (and thus close the socket) manually.
+ if (!mAttached) OnSocketDetached(mFD);
+}
+
+void nsUDPSocket::OnMsgAttach() {
+ UDPSOCKET_LOG(("nsUDPSocket::OnMsgAttach [this=%p]\n", this));
+
+ if (NS_FAILED(mCondition)) return;
+
+ mCondition = TryAttach();
+
+ // if we hit an error while trying to attach then bail...
+ if (NS_FAILED(mCondition)) {
+ UDPSOCKET_LOG(("nsUDPSocket::OnMsgAttach: TryAttach FAILED err=0x%" PRIx32
+ " [this=%p]\n",
+ static_cast<uint32_t>(mCondition), this));
+ NS_ASSERTION(!mAttached, "should not be attached already");
+ OnSocketDetached(mFD);
+ }
+}
+
+nsresult nsUDPSocket::TryAttach() {
+ nsresult rv;
+
+ if (!gSocketTransportService) return NS_ERROR_FAILURE;
+
+ rv = CheckIOStatus(&mAddr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!gSocketTransportService->CanAttachSocket()) {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "net::nsUDPSocket::OnMsgAttach", this, &nsUDPSocket::OnMsgAttach);
+
+ nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ //
+ // ok, we can now attach our socket to the STS for polling
+ //
+ rv = gSocketTransportService->AttachSocket(mFD, this);
+ if (NS_FAILED(rv)) return rv;
+
+ mAttached = true;
+
+ //
+ // now, configure our poll flags for listening...
+ //
+ mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
+ return NS_OK;
+}
+
+namespace {
+//-----------------------------------------------------------------------------
+// UDPMessageProxy
+//-----------------------------------------------------------------------------
+class UDPMessageProxy final : public nsIUDPMessage {
+ public:
+ UDPMessageProxy(NetAddr* aAddr, nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>&& aData)
+ : mOutputStream(aOutputStream), mData(std::move(aData)) {
+ memcpy(&mAddr, aAddr, sizeof(mAddr));
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPMESSAGE
+
+ private:
+ ~UDPMessageProxy() = default;
+
+ NetAddr mAddr;
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMPL_ISUPPORTS(UDPMessageProxy, nsIUDPMessage)
+
+NS_IMETHODIMP
+UDPMessageProxy::GetFromAddr(nsINetAddr** aFromAddr) {
+ NS_ENSURE_ARG_POINTER(aFromAddr);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aFromAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetData(nsACString& aData) {
+ aData.Assign(reinterpret_cast<const char*>(mData.Elements()), mData.Length());
+ return NS_OK;
+}
+
+FallibleTArray<uint8_t>& UDPMessageProxy::GetDataAsTArray() { return mData; }
+
+NS_IMETHODIMP
+UDPMessageProxy::GetRawData(JSContext* cx,
+ JS::MutableHandle<JS::Value> aRawData) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetOutputStream(nsIOutputStream** aOutputStream) {
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ *aOutputStream = do_AddRef(mOutputStream).take();
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+//-----------------------------------------------------------------------------
+// nsUDPSocket::nsASocketHandler
+//-----------------------------------------------------------------------------
+
+void nsUDPSocket::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
+ UDPSOCKET_LOG(
+ ("nsUDPSocket::OnSocketReady: flags=%d [this=%p]\n", outFlags, this));
+ NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops");
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ NS_ASSERTION(outFlags != -1, "unexpected timeout condition reached");
+
+ if (outFlags & (PR_POLL_HUP | PR_POLL_NVAL)) {
+ NS_WARNING("error polling on listening socket");
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ if (mSyncListener) {
+ mSyncListener->OnPacketReceived(this);
+ return;
+ }
+
+ PRNetAddr prClientAddr;
+ int32_t count;
+ // Bug 1252755 - use 9216 bytes to allign with nICEr and transportlayer to
+ // support the maximum size of jumbo frames
+ char buff[9216];
+ count = PR_RecvFrom(mFD, buff, sizeof(buff), 0, &prClientAddr,
+ PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ UDPSOCKET_LOG(
+ ("nsUDPSocket::OnSocketReady: PR_RecvFrom failed [this=%p]\n", this));
+ return;
+ }
+ mByteReadCount += count;
+ profiler_count_bandwidth_read_bytes(count);
+
+ FallibleTArray<uint8_t> data;
+ if (!data.AppendElements(buff, count, fallible)) {
+ UDPSOCKET_LOG((
+ "nsUDPSocket::OnSocketReady: AppendElements FAILED [this=%p]\n", this));
+ mCondition = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+
+ uint32_t segsize = UDP_PACKET_CHUNK_SIZE;
+ uint32_t segcount = 0;
+ net_ResolveSegmentParams(segsize, segcount);
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true,
+ segsize, segcount);
+
+ RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prClientAddr);
+ nsresult rv = NS_AsyncCopy(pipeIn, os, mSts, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ UDP_PACKET_CHUNK_SIZE);
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ NetAddr netAddr(&prClientAddr);
+ nsCOMPtr<nsIUDPMessage> message =
+ new UDPMessageProxy(&netAddr, pipeOut, std::move(data));
+ mListener->OnPacketReceived(this, message);
+}
+
+void nsUDPSocket::OnSocketDetached(PRFileDesc* fd) {
+ UDPSOCKET_LOG(("nsUDPSocket::OnSocketDetached [this=%p]\n", this));
+ // force a failure condition if none set; maybe the STS is shutting down :-/
+ if (NS_SUCCEEDED(mCondition)) mCondition = NS_ERROR_ABORT;
+
+ if (mFD) {
+ NS_ASSERTION(mFD == fd, "wrong file descriptor");
+ CloseSocket();
+ }
+
+ if (mSyncListener) {
+ mSyncListener->OnStopListening(this, mCondition);
+ mSyncListener = nullptr;
+ } else if (mListener) {
+ // need to atomically clear mListener. see our Close() method.
+ RefPtr<nsIUDPSocketListener> listener = nullptr;
+ {
+ MutexAutoLock lock(mLock);
+ listener = ToRefPtr(std::move(mListener));
+ }
+
+ if (listener) {
+ listener->OnStopListening(this, mCondition);
+ NS_ProxyRelease("nsUDPSocket::mListener", mListenerTarget,
+ listener.forget());
+ }
+ }
+}
+
+void nsUDPSocket::IsLocal(bool* aIsLocal) {
+ // If bound to loopback, this UDP socket only accepts local connections.
+ *aIsLocal = mAddr.IsLoopbackAddr();
+}
+
+nsresult nsUDPSocket::GetRemoteAddr(NetAddr* addr) {
+ if (!mSyncListener) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<HttpConnectionUDP> connUDP = do_QueryObject(mSyncListener);
+ if (!connUDP) {
+ return NS_ERROR_FAILURE;
+ }
+ return connUDP->GetPeerAddr(addr);
+}
+
+//-----------------------------------------------------------------------------
+// nsSocket::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsUDPSocket, nsIUDPSocket)
+
+//-----------------------------------------------------------------------------
+// nsSocket::nsISocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsUDPSocket::Init(int32_t aPort, bool aLoopbackOnly, nsIPrincipal* aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc) {
+ NetAddr addr;
+
+ if (aPort < 0) aPort = 0;
+
+ addr.raw.family = AF_INET;
+ addr.inet.port = htons(aPort);
+
+ if (aLoopbackOnly) {
+ addr.inet.ip = htonl(INADDR_LOOPBACK);
+ } else {
+ addr.inet.ip = htonl(INADDR_ANY);
+ }
+
+ return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Init2(const nsACString& aAddr, int32_t aPort,
+ nsIPrincipal* aPrincipal, bool aAddressReuse,
+ uint8_t aOptionalArgc) {
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aPort < 0) {
+ aPort = 0;
+ }
+
+ NetAddr addr;
+ if (NS_FAILED(addr.InitFromString(aAddr, uint16_t(aPort)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (addr.raw.family != PR_AF_INET && addr.raw.family != PR_AF_INET6) {
+ MOZ_ASSERT_UNREACHABLE("Dont accept address other than IPv4 and IPv6");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::InitWithAddress(const NetAddr* aAddr, nsIPrincipal* aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsresult rv;
+
+ rv = CheckIOStatus(aAddr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool addressReuse = (aOptionalArgc == 1) ? aAddressReuse : true;
+
+ if (aPrincipal) {
+ mOriginAttributes = aPrincipal->OriginAttributesRef();
+ }
+ //
+ // configure listening socket...
+ //
+
+ mFD = PR_OpenUDPSocket(aAddr->raw.family);
+ if (!mFD) {
+ NS_WARNING("unable to create UDP socket");
+ return NS_ERROR_FAILURE;
+ }
+
+#ifdef FUZZING
+ if (StaticPrefs::fuzzing_necko_enabled()) {
+ rv = AttachFuzzyIOLayer(mFD);
+ if (NS_FAILED(rv)) {
+ UDPSOCKET_LOG(("Failed to attach fuzzing IOLayer [rv=%" PRIx32 "].\n",
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ UDPSOCKET_LOG(("Successfully attached fuzzing IOLayer.\n"));
+ }
+#endif
+
+ uint16_t port;
+ if (NS_FAILED(aAddr->GetPort(&port))) {
+ NS_WARNING("invalid bind address");
+ goto fail;
+ }
+
+ PRSocketOptionData opt;
+
+ // Linux kernel will sometimes hand out a used port if we bind
+ // to port 0 with SO_REUSEADDR
+ if (port) {
+ opt.option = PR_SockOpt_Reuseaddr;
+ opt.value.reuse_addr = addressReuse;
+ PR_SetSocketOption(mFD, &opt);
+ }
+
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(mFD, &opt);
+
+ PRNetAddr addr;
+ // Temporary work around for IPv6 until bug 1330490 is fixed
+ memset(&addr, 0, sizeof(addr));
+ NetAddrToPRNetAddr(aAddr, &addr);
+
+ if (PR_Bind(mFD, &addr) != PR_SUCCESS) {
+ NS_WARNING("failed to bind socket");
+ goto fail;
+ }
+
+ // get the resulting socket address, which may be different than what
+ // we passed to bind.
+ if (PR_GetSockName(mFD, &addr) != PR_SUCCESS) {
+ NS_WARNING("cannot get socket name");
+ goto fail;
+ }
+
+ PRNetAddrToNetAddr(&addr, &mAddr);
+
+ // create proxy via IOActivityMonitor
+ IOActivityMonitor::MonitorSocket(mFD);
+
+ // wait until AsyncListen is called before polling the socket for
+ // client connections.
+ return NS_OK;
+
+fail:
+ Close();
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Connect(const NetAddr* aAddr) {
+ UDPSOCKET_LOG(("nsUDPSocket::Connect [this=%p]\n", this));
+
+ NS_ENSURE_ARG(aAddr);
+
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv;
+
+ rv = CheckIOStatus(aAddr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+ NS_ASSERTION(onSTSThread, "NOT ON STS THREAD");
+ if (!onSTSThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ if (PR_Connect(mFD, &prAddr, PR_INTERVAL_NO_WAIT) != PR_SUCCESS) {
+ NS_WARNING("Cannot PR_Connect");
+ return NS_ERROR_FAILURE;
+ }
+ PR_SetFDInheritable(mFD, false);
+
+ // get the resulting socket address, which may have been updated.
+ PRNetAddr addr;
+ if (PR_GetSockName(mFD, &addr) != PR_SUCCESS) {
+ NS_WARNING("cannot get socket name");
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddrToNetAddr(&addr, &mAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Close() {
+ {
+ MutexAutoLock lock(mLock);
+ // we want to proxy the close operation to the socket thread if a listener
+ // has been set. otherwise, we should just close the socket here...
+ if (!mListener && !mSyncListener) {
+ // Here we want to go directly with closing the socket since some tests
+ // expects this happen synchronously.
+ CloseSocket();
+
+ return NS_OK;
+ }
+ }
+ return PostEvent(this, &nsUDPSocket::OnMsgClose);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetPort(int32_t* aResult) {
+ // no need to enter the lock here
+ uint16_t result;
+ nsresult rv = mAddr.GetPort(&result);
+ *aResult = static_cast<int32_t>(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetLocalAddr(nsINetAddr** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsINetAddr> result = new nsNetAddr(&mAddr);
+ result.forget(aResult);
+
+ return NS_OK;
+}
+
+void nsUDPSocket::CloseSocket() {
+ if (mFD) {
+ if (gIOService->IsNetTearingDown() &&
+ ((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
+ gSocketTransportService->MaxTimeForPrClosePref())) {
+ // If shutdown last to long, let the socket leak and do not close it.
+ UDPSOCKET_LOG(("Intentional leak"));
+ } else {
+ PRIntervalTime closeStarted = 0;
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ closeStarted = PR_IntervalNow();
+ }
+
+ PR_Close(mFD);
+
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
+ PRIntervalTime now = PR_IntervalNow();
+ if (gIOService->IsNetTearingDown()) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_SHUTDOWN,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(
+ now - gIOService->LastConnectivityChange()) < 60) {
+ Telemetry::Accumulate(
+ Telemetry::PRCLOSE_UDP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(
+ now - gIOService->LastNetworkLinkChange()) < 60) {
+ Telemetry::Accumulate(
+ Telemetry::PRCLOSE_UDP_BLOCKING_TIME_LINK_CHANGE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else if (PR_IntervalToSeconds(
+ now - gIOService->LastOfflineStateChange()) < 60) {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_OFFLINE,
+ PR_IntervalToMilliseconds(now - closeStarted));
+
+ } else {
+ Telemetry::Accumulate(Telemetry::PRCLOSE_UDP_BLOCKING_TIME_NORMAL,
+ PR_IntervalToMilliseconds(now - closeStarted));
+ }
+ }
+ }
+ mFD = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetAddress(NetAddr* aResult) {
+ // no need to enter the lock here
+ memcpy(aResult, &mAddr, sizeof(mAddr));
+ return NS_OK;
+}
+
+namespace {
+//-----------------------------------------------------------------------------
+// SocketListenerProxy
+//-----------------------------------------------------------------------------
+class SocketListenerProxy final : public nsIUDPSocketListener {
+ ~SocketListenerProxy() = default;
+
+ public:
+ explicit SocketListenerProxy(nsIUDPSocketListener* aListener)
+ : mListener(new nsMainThreadPtrHolder<nsIUDPSocketListener>(
+ "SocketListenerProxy::mListener", aListener)),
+ mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ class OnPacketReceivedRunnable : public Runnable {
+ public:
+ OnPacketReceivedRunnable(
+ const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket, nsIUDPMessage* aMessage)
+ : Runnable("net::SocketListenerProxy::OnPacketReceivedRunnable"),
+ mListener(aListener),
+ mSocket(aSocket),
+ mMessage(aMessage) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsIUDPMessage> mMessage;
+ };
+
+ class OnStopListeningRunnable : public Runnable {
+ public:
+ OnStopListeningRunnable(
+ const nsMainThreadPtrHandle<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket, nsresult aStatus)
+ : Runnable("net::SocketListenerProxy::OnStopListeningRunnable"),
+ mListener(aListener),
+ mSocket(aSocket),
+ mStatus(aStatus) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsresult mStatus;
+ };
+
+ private:
+ nsMainThreadPtrHandle<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS(SocketListenerProxy, nsIUDPSocketListener)
+
+NS_IMETHODIMP
+SocketListenerProxy::OnPacketReceived(nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage) {
+ RefPtr<OnPacketReceivedRunnable> r =
+ new OnPacketReceivedRunnable(mListener, aSocket, aMessage);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnStopListening(nsIUDPSocket* aSocket, nsresult aStatus) {
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aSocket, aStatus);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnPacketReceivedRunnable::Run() {
+ NetAddr netAddr;
+ nsCOMPtr<nsINetAddr> nsAddr;
+ mMessage->GetFromAddr(getter_AddRefs(nsAddr));
+ nsAddr->GetNetAddr(&netAddr);
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ mMessage->GetOutputStream(getter_AddRefs(outputStream));
+
+ FallibleTArray<uint8_t>& data = mMessage->GetDataAsTArray();
+
+ nsCOMPtr<nsIUDPMessage> message =
+ new nsUDPMessage(&netAddr, outputStream, std::move(data));
+ mListener->OnPacketReceived(mSocket, message);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketListenerProxy::OnStopListeningRunnable::Run() {
+ mListener->OnStopListening(mSocket, mStatus);
+ return NS_OK;
+}
+
+class SocketListenerProxyBackground final : public nsIUDPSocketListener {
+ ~SocketListenerProxyBackground() = default;
+
+ public:
+ explicit SocketListenerProxyBackground(nsIUDPSocketListener* aListener)
+ : mListener(aListener), mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ class OnPacketReceivedRunnable : public Runnable {
+ public:
+ OnPacketReceivedRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket, nsIUDPMessage* aMessage)
+ : Runnable(
+ "net::SocketListenerProxyBackground::OnPacketReceivedRunnable"),
+ mListener(aListener),
+ mSocket(aSocket),
+ mMessage(aMessage) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsIUDPMessage> mMessage;
+ };
+
+ class OnStopListeningRunnable : public Runnable {
+ public:
+ OnStopListeningRunnable(const nsCOMPtr<nsIUDPSocketListener>& aListener,
+ nsIUDPSocket* aSocket, nsresult aStatus)
+ : Runnable(
+ "net::SocketListenerProxyBackground::OnStopListeningRunnable"),
+ mListener(aListener),
+ mSocket(aSocket),
+ mStatus(aStatus) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsresult mStatus;
+ };
+
+ private:
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS(SocketListenerProxyBackground, nsIUDPSocketListener)
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnPacketReceived(nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage) {
+ RefPtr<OnPacketReceivedRunnable> r =
+ new OnPacketReceivedRunnable(mListener, aSocket, aMessage);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnStopListening(nsIUDPSocket* aSocket,
+ nsresult aStatus) {
+ RefPtr<OnStopListeningRunnable> r =
+ new OnStopListeningRunnable(mListener, aSocket, aStatus);
+ return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnPacketReceivedRunnable::Run() {
+ NetAddr netAddr;
+ nsCOMPtr<nsINetAddr> nsAddr;
+ mMessage->GetFromAddr(getter_AddRefs(nsAddr));
+ nsAddr->GetNetAddr(&netAddr);
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ mMessage->GetOutputStream(getter_AddRefs(outputStream));
+
+ FallibleTArray<uint8_t>& data = mMessage->GetDataAsTArray();
+
+ UDPSOCKET_LOG(("%s [this=%p], len %zu", __FUNCTION__, this, data.Length()));
+ nsCOMPtr<nsIUDPMessage> message =
+ new UDPMessageProxy(&netAddr, outputStream, std::move(data));
+ mListener->OnPacketReceived(mSocket, message);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketListenerProxyBackground::OnStopListeningRunnable::Run() {
+ mListener->OnStopListening(mSocket, mStatus);
+ return NS_OK;
+}
+
+class PendingSend : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ PendingSend(nsUDPSocket* aSocket, uint16_t aPort,
+ FallibleTArray<uint8_t>&& aData)
+ : mSocket(aSocket), mPort(aPort), mData(std::move(aData)) {}
+
+ private:
+ virtual ~PendingSend() = default;
+
+ RefPtr<nsUDPSocket> mSocket;
+ uint16_t mPort;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMPL_ISUPPORTS(PendingSend, nsIDNSListener)
+
+NS_IMETHODIMP
+PendingSend::OnLookupComplete(nsICancelable* request, nsIDNSRecord* aRecord,
+ nsresult status) {
+ if (NS_FAILED(status)) {
+ NS_WARNING("Failed to send UDP packet due to DNS lookup failure");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord);
+ MOZ_ASSERT(rec);
+ NetAddr addr;
+ if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) {
+ uint32_t count;
+ nsresult rv = mSocket->SendWithAddress(&addr, mData.Elements(),
+ mData.Length(), &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+class PendingSendStream : public nsIDNSListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ PendingSendStream(nsUDPSocket* aSocket, uint16_t aPort,
+ nsIInputStream* aStream)
+ : mSocket(aSocket), mPort(aPort), mStream(aStream) {}
+
+ private:
+ virtual ~PendingSendStream() = default;
+
+ RefPtr<nsUDPSocket> mSocket;
+ uint16_t mPort;
+ nsCOMPtr<nsIInputStream> mStream;
+};
+
+NS_IMPL_ISUPPORTS(PendingSendStream, nsIDNSListener)
+
+NS_IMETHODIMP
+PendingSendStream::OnLookupComplete(nsICancelable* request,
+ nsIDNSRecord* aRecord, nsresult status) {
+ if (NS_FAILED(status)) {
+ NS_WARNING("Failed to send UDP packet due to DNS lookup failure");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(aRecord);
+ MOZ_ASSERT(rec);
+ NetAddr addr;
+ if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) {
+ nsresult rv = mSocket->SendBinaryStreamWithAddress(&addr, mStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+class SendRequestRunnable : public Runnable {
+ public:
+ SendRequestRunnable(nsUDPSocket* aSocket, const NetAddr& aAddr,
+ FallibleTArray<uint8_t>&& aData)
+ : Runnable("net::SendRequestRunnable"),
+ mSocket(aSocket),
+ mAddr(aAddr),
+ mData(std::move(aData)) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ RefPtr<nsUDPSocket> mSocket;
+ const NetAddr mAddr;
+ FallibleTArray<uint8_t> mData;
+};
+
+NS_IMETHODIMP
+SendRequestRunnable::Run() {
+ uint32_t count;
+ mSocket->SendWithAddress(&mAddr, mData.Elements(), mData.Length(), &count);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+nsUDPSocket::AsyncListen(nsIUDPSocketListener* aListener) {
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(mSyncListener == nullptr, NS_ERROR_IN_PROGRESS);
+ {
+ MutexAutoLock lock(mLock);
+ mListenerTarget = GetCurrentSerialEventTarget();
+ if (NS_IsMainThread()) {
+ // PNecko usage
+ mListener = new SocketListenerProxy(aListener);
+ } else {
+ // PBackground usage from dom/media/webrtc/transport
+ mListener = new SocketListenerProxyBackground(aListener);
+ }
+ }
+ return PostEvent(this, &nsUDPSocket::OnMsgAttach);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SyncListen(nsIUDPSocketSyncListener* aListener) {
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(mSyncListener == nullptr, NS_ERROR_IN_PROGRESS);
+
+ mSyncListener = aListener;
+
+ return PostEvent(this, &nsUDPSocket::OnMsgAttach);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Send(const nsACString& aHost, uint16_t aPort,
+ const nsTArray<uint8_t>& aData, uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = 0;
+
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.InsertElementsAt(0, aData, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCOMPtr<nsIDNSListener> listener =
+ new PendingSend(this, aPort, std::move(fallibleArray));
+
+ nsresult rv = ResolveHost(aHost, mOriginAttributes, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = aData.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendWithAddr(nsINetAddr* aAddr, const nsTArray<uint8_t>& aData,
+ uint32_t* _retval) {
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ NetAddr netAddr;
+ aAddr->GetNetAddr(&netAddr);
+ return SendWithAddress(&netAddr, aData.Elements(), aData.Length(), _retval);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendWithAddress(const NetAddr* aAddr, const uint8_t* aData,
+ uint32_t aLength, uint32_t* _retval) {
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (StaticPrefs::network_http_http3_block_loopback_ipv6_addr() &&
+ aAddr->raw.family == AF_INET6 && aAddr->IsLoopbackAddr()) {
+ return NS_ERROR_CONNECTION_REFUSED;
+ }
+
+ *_retval = 0;
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+
+ if (onSTSThread) {
+ MutexAutoLock lock(mLock);
+ if (!mFD) {
+ // socket is not initialized or has been closed
+ return NS_ERROR_FAILURE;
+ }
+ int32_t count =
+ PR_SendTo(mFD, aData, aLength, 0, &prAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ PRErrorCode code = PR_GetError();
+ return ErrorAccordingToNSPR(code);
+ }
+ this->AddOutputBytes(count);
+ *_retval = count;
+ } else {
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.AppendElements(aData, aLength, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = mSts->Dispatch(
+ new SendRequestRunnable(this, *aAddr, std::move(fallibleArray)),
+ NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *_retval = aLength;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendBinaryStream(const nsACString& aHost, uint16_t aPort,
+ nsIInputStream* aStream) {
+ NS_ENSURE_ARG(aStream);
+
+ nsCOMPtr<nsIDNSListener> listener =
+ new PendingSendStream(this, aPort, aStream);
+
+ return ResolveHost(aHost, mOriginAttributes, listener);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendBinaryStreamWithAddress(const NetAddr* aAddr,
+ nsIInputStream* aStream) {
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aStream);
+
+ PRNetAddr prAddr;
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prAddr);
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prAddr);
+ return NS_AsyncCopy(aStream, os, mSts, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ UDP_PACKET_CHUNK_SIZE);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::RecvWithAddr(NetAddr* addr, nsTArray<uint8_t>& aData) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ PRNetAddr prAddr;
+ int32_t count;
+ char buff[9216];
+ count = PR_RecvFrom(mFD, buff, sizeof(buff), 0, &prAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ UDPSOCKET_LOG(
+ ("nsUDPSocket::RecvWithAddr: PR_RecvFrom failed [this=%p]\n", this));
+ return NS_OK;
+ }
+ mByteReadCount += count;
+ profiler_count_bandwidth_read_bytes(count);
+ PRNetAddrToNetAddr(&prAddr, addr);
+
+ if (!aData.AppendElements(buff, count, fallible)) {
+ UDPSOCKET_LOG((
+ "nsUDPSocket::OnSocketReady: AppendElements FAILED [this=%p]\n", this));
+ mCondition = NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult nsUDPSocket::SetSocketOption(const PRSocketOptionData& aOpt) {
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+
+ if (!onSTSThread) {
+ // Dispatch to STS thread and re-enter this method there
+ nsCOMPtr<nsIRunnable> runnable = new SetSocketOptionRunnable(this, aOpt);
+ nsresult rv = mSts->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (PR_SetSocketOption(mFD, &aOpt) != PR_SUCCESS) {
+ UDPSOCKET_LOG(
+ ("nsUDPSocket::SetSocketOption [this=%p] failed for type %d, "
+ "error %d\n",
+ this, aOpt.option, PR_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::JoinMulticast(const nsACString& aAddr, const nsACString& aIface) {
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return JoinMulticastInternal(prAddr, prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::JoinMulticastAddr(const NetAddr aAddr, const NetAddr* aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(&aAddr, &prAddr);
+
+ PRNetAddr prIface;
+ if (!aIface) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ NetAddrToPRNetAddr(aIface, &prIface);
+ }
+
+ return JoinMulticastInternal(prAddr, prIface);
+}
+
+nsresult nsUDPSocket::JoinMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface) {
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_AddMember;
+ opt.value.add_member.mcaddr = aAddr;
+ opt.value.add_member.ifaddr = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::LeaveMulticast(const nsACString& aAddr, const nsACString& aIface) {
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return LeaveMulticastInternal(prAddr, prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::LeaveMulticastAddr(const NetAddr aAddr, const NetAddr* aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(&aAddr, &prAddr);
+
+ PRNetAddr prIface;
+ if (!aIface) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ NetAddrToPRNetAddr(aIface, &prIface);
+ }
+
+ return LeaveMulticastInternal(prAddr, prIface);
+}
+
+nsresult nsUDPSocket::LeaveMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface) {
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_DropMember;
+ opt.value.drop_member.mcaddr = aAddr;
+ opt.value.drop_member.ifaddr = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastLoopback(bool* aLoopback) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastLoopback(bool aLoopback) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_McastLoopback;
+ opt.value.mcast_loopback = aLoopback;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetRecvBufferSize(int* size) {
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetRecvBufferSize(int size) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_RecvBufferSize;
+ opt.value.recv_buffer_size = size;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetDontFragment(bool* dontFragment) {
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetDontFragment(bool dontFragment) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_DontFrag;
+ opt.value.dont_fragment = dontFragment;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetSendBufferSize(int* size) {
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetSendBufferSize(int size) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = size;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastInterface(nsACString& aIface) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastInterfaceAddr(NetAddr* aIface) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastInterface(const nsACString& aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return SetMulticastInterfaceInternal(prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastInterfaceAddr(NetAddr aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prIface;
+ NetAddrToPRNetAddr(&aIface, &prIface);
+
+ return SetMulticastInterfaceInternal(prIface);
+}
+
+nsresult nsUDPSocket::SetMulticastInterfaceInternal(const PRNetAddr& aIface) {
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_McastInterface;
+ opt.value.mcast_if = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsUDPSocket.h b/netwerk/base/nsUDPSocket.h
new file mode 100644
index 0000000000..bd616d5206
--- /dev/null
+++ b/netwerk/base/nsUDPSocket.h
@@ -0,0 +1,118 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUDPSocket_h__
+#define nsUDPSocket_h__
+
+#include "nsIUDPSocket.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/net/DNS.h"
+#include "nsIOutputStream.h"
+#include "nsASocketHandler.h"
+#include "nsCycleCollectionParticipant.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsSocketTransportService;
+
+class nsUDPSocket final : public nsASocketHandler, public nsIUDPSocket {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKET
+
+ // nsASocketHandler methods:
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override;
+ virtual void OnSocketDetached(PRFileDesc* fd) override;
+ virtual void IsLocal(bool* aIsLocal) override;
+ virtual nsresult GetRemoteAddr(NetAddr* addr) override;
+
+ uint64_t ByteCountSent() override { return mByteWriteCount; }
+ uint64_t ByteCountReceived() override { return mByteReadCount; }
+
+ void AddOutputBytes(int32_t aBytes);
+
+ nsUDPSocket();
+
+ private:
+ virtual ~nsUDPSocket();
+
+ void OnMsgClose();
+ void OnMsgAttach();
+
+ // try attaching our socket (mFD) to the STS's poll list.
+ nsresult TryAttach();
+
+ friend class SetSocketOptionRunnable;
+ nsresult SetSocketOption(const PRSocketOptionData& aOpt);
+ nsresult JoinMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface);
+ nsresult LeaveMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface);
+ nsresult SetMulticastInterfaceInternal(const PRNetAddr& aIface);
+
+ void CloseSocket();
+
+ // lock protects access to mListener;
+ // so mListener is not cleared while being used/locked.
+ Mutex mLock MOZ_UNANNOTATED{"nsUDPSocket.mLock"};
+ PRFileDesc* mFD{nullptr};
+ NetAddr mAddr;
+ OriginAttributes mOriginAttributes;
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIUDPSocketSyncListener> mSyncListener;
+ nsCOMPtr<nsIEventTarget> mListenerTarget;
+ bool mAttached{false};
+ RefPtr<nsSocketTransportService> mSts;
+
+ uint64_t mByteReadCount{0};
+ uint64_t mByteWriteCount{0};
+};
+
+//-----------------------------------------------------------------------------
+
+class nsUDPMessage : public nsIUDPMessage {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsUDPMessage)
+ NS_DECL_NSIUDPMESSAGE
+
+ nsUDPMessage(NetAddr* aAddr, nsIOutputStream* aOutputStream,
+ FallibleTArray<uint8_t>&& aData);
+
+ private:
+ virtual ~nsUDPMessage();
+
+ NetAddr mAddr;
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ FallibleTArray<uint8_t> mData;
+ JS::Heap<JSObject*> mJsobj;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsUDPOutputStream : public nsIOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsUDPOutputStream(nsUDPSocket* aSocket, PRFileDesc* aFD,
+ PRNetAddr& aPrClientAddr);
+
+ private:
+ virtual ~nsUDPOutputStream() = default;
+
+ RefPtr<nsUDPSocket> mSocket;
+ PRFileDesc* mFD;
+ PRNetAddr mPrClientAddr;
+ bool mIsClosed;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsUDPSocket_h__
diff --git a/netwerk/base/nsURIHashKey.h b/netwerk/base/nsURIHashKey.h
new file mode 100644
index 0000000000..fb98ae0d21
--- /dev/null
+++ b/netwerk/base/nsURIHashKey.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsURIHashKey_h__
+#define nsURIHashKey_h__
+
+#include <utility>
+
+#include "PLDHashTable.h"
+#include "mozilla/Unused.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIURI.h"
+
+/**
+ * Hashtable key class to use with nsTHashtable/nsBaseHashtable
+ */
+class nsURIHashKey : public PLDHashEntryHdr {
+ public:
+ typedef nsIURI* KeyType;
+ typedef const nsIURI* KeyTypePointer;
+
+ nsURIHashKey() { MOZ_COUNT_CTOR(nsURIHashKey); }
+ explicit nsURIHashKey(const nsIURI* aKey) : mKey(const_cast<nsIURI*>(aKey)) {
+ MOZ_COUNT_CTOR(nsURIHashKey);
+ }
+ nsURIHashKey(nsURIHashKey&& toMove)
+ : PLDHashEntryHdr(std::move(toMove)), mKey(std::move(toMove.mKey)) {
+ MOZ_COUNT_CTOR(nsURIHashKey);
+ }
+ MOZ_COUNTED_DTOR(nsURIHashKey)
+
+ nsURIHashKey& operator=(const nsURIHashKey& aOther) {
+ mKey = aOther.mKey;
+ return *this;
+ }
+
+ nsIURI* GetKey() const { return mKey; }
+
+ bool KeyEquals(const nsIURI* aKey) const {
+ bool eq;
+ if (!mKey) {
+ return !aKey;
+ }
+ if (NS_SUCCEEDED(mKey->Equals(const_cast<nsIURI*>(aKey), &eq))) {
+ return eq;
+ }
+ return false;
+ }
+
+ static const nsIURI* KeyToPointer(nsIURI* aKey) { return aKey; }
+ static PLDHashNumber HashKey(const nsIURI* aKey) {
+ if (!aKey) {
+ // If the key is null, return hash for empty string.
+ return mozilla::HashString(""_ns);
+ }
+ nsAutoCString spec;
+ // If GetSpec() fails, ignoring the failure and proceeding with an
+ // empty |spec| seems like the best thing to do.
+ mozilla::Unused << const_cast<nsIURI*>(aKey)->GetSpec(spec);
+ return mozilla::HashString(spec);
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ nsCOMPtr<nsIURI> mKey;
+};
+
+#endif // nsURIHashKey_h__
diff --git a/netwerk/base/nsURLHelper.cpp b/netwerk/base/nsURLHelper.cpp
new file mode 100644
index 0000000000..3850c6865a
--- /dev/null
+++ b/netwerk/base/nsURLHelper.cpp
@@ -0,0 +1,1356 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsURLHelper.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/RangedPtr.h"
+#include "mozilla/TextUtils.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "nsASCIIMask.h"
+#include "nsIFile.h"
+#include "nsIURLParser.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "mozilla/Preferences.h"
+#include "prnetdb.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Tokenizer.h"
+#include "nsEscape.h"
+#include "nsDOMString.h"
+#include "mozilla/net/rust_helper.h"
+#include "mozilla/net/DNS.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------------
+// Init/Shutdown
+//----------------------------------------------------------------------------
+
+static bool gInitialized = false;
+static StaticRefPtr<nsIURLParser> gNoAuthURLParser;
+static StaticRefPtr<nsIURLParser> gAuthURLParser;
+static StaticRefPtr<nsIURLParser> gStdURLParser;
+
+static void InitGlobals() {
+ nsCOMPtr<nsIURLParser> parser;
+
+ parser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'noauth' url parser");
+ if (parser) {
+ gNoAuthURLParser = parser;
+ }
+
+ parser = do_GetService(NS_AUTHURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'auth' url parser");
+ if (parser) {
+ gAuthURLParser = parser;
+ }
+
+ parser = do_GetService(NS_STDURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'std' url parser");
+ if (parser) {
+ gStdURLParser = parser;
+ }
+
+ gInitialized = true;
+}
+
+void net_ShutdownURLHelper() {
+ if (gInitialized) {
+ gInitialized = false;
+ }
+ gNoAuthURLParser = nullptr;
+ gAuthURLParser = nullptr;
+ gStdURLParser = nullptr;
+}
+
+//----------------------------------------------------------------------------
+// nsIURLParser getters
+//----------------------------------------------------------------------------
+
+nsIURLParser* net_GetAuthURLParser() {
+ if (!gInitialized) InitGlobals();
+ return gAuthURLParser;
+}
+
+nsIURLParser* net_GetNoAuthURLParser() {
+ if (!gInitialized) InitGlobals();
+ return gNoAuthURLParser;
+}
+
+nsIURLParser* net_GetStdURLParser() {
+ if (!gInitialized) InitGlobals();
+ return gStdURLParser;
+}
+
+//---------------------------------------------------------------------------
+// GetFileFromURLSpec implementations
+//---------------------------------------------------------------------------
+nsresult net_GetURLSpecFromDir(nsIFile* aFile, nsACString& result) {
+ nsAutoCString escPath;
+ nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath);
+ if (NS_FAILED(rv)) return rv;
+
+ if (escPath.Last() != '/') {
+ escPath += '/';
+ }
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult net_GetURLSpecFromFile(nsIFile* aFile, nsACString& result) {
+ nsAutoCString escPath;
+ nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath);
+ if (NS_FAILED(rv)) return rv;
+
+ // if this file references a directory, then we need to ensure that the
+ // URL ends with a slash. this is important since it affects the rules
+ // for relative URL resolution when this URL is used as a base URL.
+ // if the file does not exist, then we make no assumption about its type,
+ // and simply leave the URL unmodified.
+ if (escPath.Last() != '/') {
+ bool dir;
+ rv = aFile->IsDirectory(&dir);
+ if (NS_SUCCEEDED(rv) && dir) escPath += '/';
+ }
+
+ result = escPath;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// file:// URL parsing
+//----------------------------------------------------------------------------
+
+nsresult net_ParseFileURL(const nsACString& inURL, nsACString& outDirectory,
+ nsACString& outFileBaseName,
+ nsACString& outFileExtension) {
+ nsresult rv;
+
+ if (inURL.Length() >
+ (uint32_t)StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ outDirectory.Truncate();
+ outFileBaseName.Truncate();
+ outFileExtension.Truncate();
+
+ const nsPromiseFlatCString& flatURL = PromiseFlatCString(inURL);
+ const char* url = flatURL.get();
+
+ nsAutoCString scheme;
+ rv = net_ExtractURLScheme(flatURL, scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!scheme.EqualsLiteral("file")) {
+ NS_ERROR("must be a file:// url");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsIURLParser* parser = net_GetNoAuthURLParser();
+ NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
+
+ uint32_t pathPos, filepathPos, directoryPos, basenamePos, extensionPos;
+ int32_t pathLen, filepathLen, directoryLen, basenameLen, extensionLen;
+
+ // invoke the parser to extract the URL path
+ rv = parser->ParseURL(url, flatURL.Length(), nullptr,
+ nullptr, // don't care about scheme
+ nullptr, nullptr, // don't care about authority
+ &pathPos, &pathLen);
+ if (NS_FAILED(rv)) return rv;
+
+ // invoke the parser to extract filepath from the path
+ rv = parser->ParsePath(url + pathPos, pathLen, &filepathPos, &filepathLen,
+ nullptr, nullptr, // don't care about query
+ nullptr, nullptr); // don't care about ref
+ if (NS_FAILED(rv)) return rv;
+
+ filepathPos += pathPos;
+
+ // invoke the parser to extract the directory and filename from filepath
+ rv = parser->ParseFilePath(url + filepathPos, filepathLen, &directoryPos,
+ &directoryLen, &basenamePos, &basenameLen,
+ &extensionPos, &extensionLen);
+ if (NS_FAILED(rv)) return rv;
+
+ if (directoryLen > 0) {
+ outDirectory = Substring(inURL, filepathPos + directoryPos, directoryLen);
+ }
+ if (basenameLen > 0) {
+ outFileBaseName = Substring(inURL, filepathPos + basenamePos, basenameLen);
+ }
+ if (extensionLen > 0) {
+ outFileExtension =
+ Substring(inURL, filepathPos + extensionPos, extensionLen);
+ }
+ // since we are using a no-auth url parser, there will never be a host
+ // XXX not strictly true... file://localhost/foo/bar.html is a valid URL
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// path manipulation functions
+//----------------------------------------------------------------------------
+
+// Replace all /./ with a / while resolving URLs
+// But only till #?
+void net_CoalesceDirs(netCoalesceFlags flags, char* path) {
+ /* Stolen from the old netlib's mkparse.c.
+ *
+ * modifies a url of the form /foo/../foo1 -> /foo1
+ * and /foo/./foo1 -> /foo/foo1
+ * and /foo/foo1/.. -> /foo/
+ */
+ char* fwdPtr = path;
+ char* urlPtr = path;
+ char* lastslash = path;
+ uint32_t traversal = 0;
+ uint32_t special_ftp_len = 0;
+
+ /* Remember if this url is a special ftp one: */
+ if (flags & NET_COALESCE_DOUBLE_SLASH_IS_ROOT) {
+ /* some schemes (for example ftp) have the speciality that
+ the path can begin // or /%2F to mark the root of the
+ servers filesystem, a simple / only marks the root relative
+ to the user loging in. We remember the length of the marker */
+ if (nsCRT::strncasecmp(path, "/%2F", 4) == 0) {
+ special_ftp_len = 4;
+ } else if (strncmp(path, "//", 2) == 0) {
+ special_ftp_len = 2;
+ }
+ }
+
+ /* find the last slash before # or ? */
+ for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#'); ++fwdPtr) {
+ }
+
+ /* found nothing, but go back one only */
+ /* if there is something to go back to */
+ if (fwdPtr != path && *fwdPtr == '\0') {
+ --fwdPtr;
+ }
+
+ /* search the slash */
+ for (; (fwdPtr != path) && (*fwdPtr != '/'); --fwdPtr) {
+ }
+ lastslash = fwdPtr;
+ fwdPtr = path;
+
+ /* replace all %2E or %2e with . in the path */
+ /* but stop at lastchar if non null */
+ for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#') &&
+ (*lastslash == '\0' || fwdPtr != lastslash);
+ ++fwdPtr) {
+ if (*fwdPtr == '%' && *(fwdPtr + 1) == '2' &&
+ (*(fwdPtr + 2) == 'E' || *(fwdPtr + 2) == 'e')) {
+ *urlPtr++ = '.';
+ ++fwdPtr;
+ ++fwdPtr;
+ } else {
+ *urlPtr++ = *fwdPtr;
+ }
+ }
+ // Copy remaining stuff past the #?;
+ for (; *fwdPtr != '\0'; ++fwdPtr) {
+ *urlPtr++ = *fwdPtr;
+ }
+ *urlPtr = '\0'; // terminate the url
+
+ // start again, this time for real
+ fwdPtr = path;
+ urlPtr = path;
+
+ for (; (*fwdPtr != '\0') && (*fwdPtr != '?') && (*fwdPtr != '#'); ++fwdPtr) {
+ if (*fwdPtr == '/' && *(fwdPtr + 1) == '.' && *(fwdPtr + 2) == '/') {
+ // remove . followed by slash
+ ++fwdPtr;
+ } else if (*fwdPtr == '/' && *(fwdPtr + 1) == '.' && *(fwdPtr + 2) == '.' &&
+ (*(fwdPtr + 3) == '/' ||
+ *(fwdPtr + 3) == '\0' || // This will take care of
+ *(fwdPtr + 3) == '?' || // something like foo/bar/..#sometag
+ *(fwdPtr + 3) == '#')) {
+ // remove foo/..
+ // reverse the urlPtr to the previous slash if possible
+ // if url does not allow relative root then drop .. above root
+ // otherwise retain them in the path
+ if (traversal > 0 || !(flags & NET_COALESCE_ALLOW_RELATIVE_ROOT)) {
+ if (urlPtr != path) urlPtr--; // we must be going back at least by one
+ for (; *urlPtr != '/' && urlPtr != path; urlPtr--) {
+ ; // null body
+ }
+ --traversal; // count back
+ // forward the fwdPtr past the ../
+ fwdPtr += 2;
+ // if we have reached the beginning of the path
+ // while searching for the previous / and we remember
+ // that it is an url that begins with /%2F then
+ // advance urlPtr again by 3 chars because /%2F already
+ // marks the root of the path
+ if (urlPtr == path && special_ftp_len > 3) {
+ ++urlPtr;
+ ++urlPtr;
+ ++urlPtr;
+ }
+ // special case if we have reached the end
+ // to preserve the last /
+ if (*fwdPtr == '.' && *(fwdPtr + 1) == '\0') ++urlPtr;
+ } else {
+ // there are to much /.. in this path, just copy them instead.
+ // forward the urlPtr past the /.. and copying it
+
+ // However if we remember it is an url that starts with
+ // /%2F and urlPtr just points at the "F" of "/%2F" then do
+ // not overwrite it with the /, just copy .. and move forward
+ // urlPtr.
+ if (special_ftp_len > 3 && urlPtr == path + special_ftp_len - 1) {
+ ++urlPtr;
+ } else {
+ *urlPtr++ = *fwdPtr;
+ }
+ ++fwdPtr;
+ *urlPtr++ = *fwdPtr;
+ ++fwdPtr;
+ *urlPtr++ = *fwdPtr;
+ }
+ } else {
+ // count the hierachie, but only if we do not have reached
+ // the root of some special urls with a special root marker
+ if (*fwdPtr == '/' && *(fwdPtr + 1) != '.' &&
+ (special_ftp_len != 2 || *(fwdPtr + 1) != '/')) {
+ traversal++;
+ }
+ // copy the url incrementaly
+ *urlPtr++ = *fwdPtr;
+ }
+ }
+
+ /*
+ * Now lets remove trailing . case
+ * /foo/foo1/. -> /foo/foo1/
+ */
+
+ if ((urlPtr > (path + 1)) && (*(urlPtr - 1) == '.') &&
+ (*(urlPtr - 2) == '/')) {
+ urlPtr--;
+ }
+
+ // Copy remaining stuff past the #?;
+ for (; *fwdPtr != '\0'; ++fwdPtr) {
+ *urlPtr++ = *fwdPtr;
+ }
+ *urlPtr = '\0'; // terminate the url
+}
+
+//----------------------------------------------------------------------------
+// scheme fu
+//----------------------------------------------------------------------------
+
+static bool net_IsValidSchemeChar(const char aChar) {
+ return mozilla::net::rust_net_is_valid_scheme_char(aChar);
+}
+
+/* Extract URI-Scheme if possible */
+nsresult net_ExtractURLScheme(const nsACString& inURI, nsACString& scheme) {
+ nsACString::const_iterator start, end;
+ inURI.BeginReading(start);
+ inURI.EndReading(end);
+
+ // Strip C0 and space from begining
+ while (start != end) {
+ if ((uint8_t)*start > 0x20) {
+ break;
+ }
+ start++;
+ }
+
+ Tokenizer p(Substring(start, end), "\r\n\t");
+ p.Record();
+ if (!p.CheckChar(IsAsciiAlpha)) {
+ // First char must be alpha
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) {
+ // Skip valid scheme characters or \r\n\t
+ }
+
+ if (!p.CheckChar(':')) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ p.Claim(scheme);
+ scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+ ToLowerCase(scheme);
+ return NS_OK;
+}
+
+bool net_IsValidScheme(const nsACString& scheme) {
+ return mozilla::net::rust_net_is_valid_scheme(&scheme);
+}
+
+bool net_IsAbsoluteURL(const nsACString& uri) {
+ nsACString::const_iterator start, end;
+ uri.BeginReading(start);
+ uri.EndReading(end);
+
+ // Strip C0 and space from begining
+ while (start != end) {
+ if ((uint8_t)*start > 0x20) {
+ break;
+ }
+ start++;
+ }
+
+ Tokenizer p(Substring(start, end), "\r\n\t");
+
+ // First char must be alpha
+ if (!p.CheckChar(IsAsciiAlpha)) {
+ return false;
+ }
+
+ while (p.CheckChar(net_IsValidSchemeChar) || p.CheckWhite()) {
+ // Skip valid scheme characters or \r\n\t
+ }
+ if (!p.CheckChar(':')) {
+ return false;
+ }
+ p.SkipWhites();
+
+ if (!p.CheckChar('/')) {
+ return false;
+ }
+ p.SkipWhites();
+
+ if (p.CheckChar('/')) {
+ // aSpec is really absolute. Ignore aBaseURI in this case
+ return true;
+ }
+ return false;
+}
+
+void net_FilterURIString(const nsACString& input, nsACString& result) {
+ result.Truncate();
+
+ const auto* start = input.BeginReading();
+ const auto* end = input.EndReading();
+
+ // Trim off leading and trailing invalid chars.
+ auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
+ const auto* newStart = std::find_if(start, end, charFilter);
+ const auto* newEnd =
+ std::find_if(std::reverse_iterator<decltype(end)>(end),
+ std::reverse_iterator<decltype(newStart)>(newStart),
+ charFilter)
+ .base();
+
+ // Check if chars need to be stripped.
+ bool needsStrip = false;
+ const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab();
+ for (const auto* itr = start; itr != end; ++itr) {
+ if (ASCIIMask::IsMasked(mask, *itr)) {
+ needsStrip = true;
+ break;
+ }
+ }
+
+ // Just use the passed in string rather than creating new copies if no
+ // changes are necessary.
+ if (newStart == start && newEnd == end && !needsStrip) {
+ result = input;
+ return;
+ }
+
+ result.Assign(Substring(newStart, newEnd));
+ if (needsStrip) {
+ result.StripTaggedASCII(mask);
+ }
+}
+
+nsresult net_FilterAndEscapeURI(const nsACString& aInput, uint32_t aFlags,
+ const ASCIIMaskArray& aFilterMask,
+ nsACString& aResult) {
+ aResult.Truncate();
+
+ const auto* start = aInput.BeginReading();
+ const auto* end = aInput.EndReading();
+
+ // Trim off leading and trailing invalid chars.
+ auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
+ const auto* newStart = std::find_if(start, end, charFilter);
+ const auto* newEnd =
+ std::find_if(std::reverse_iterator<decltype(end)>(end),
+ std::reverse_iterator<decltype(newStart)>(newStart),
+ charFilter)
+ .base();
+
+ return NS_EscapeAndFilterURL(Substring(newStart, newEnd), aFlags,
+ &aFilterMask, aResult, fallible);
+}
+
+#if defined(XP_WIN)
+bool net_NormalizeFileURL(const nsACString& aURL, nsCString& aResultBuf) {
+ bool writing = false;
+
+ nsACString::const_iterator beginIter, endIter;
+ aURL.BeginReading(beginIter);
+ aURL.EndReading(endIter);
+
+ const char *s, *begin = beginIter.get();
+
+ for (s = begin; s != endIter.get(); ++s) {
+ if (*s == '\\') {
+ writing = true;
+ if (s > begin) aResultBuf.Append(begin, s - begin);
+ aResultBuf += '/';
+ begin = s + 1;
+ }
+ if (*s == '#') {
+ // Don't normalize any backslashes following the hash.
+ s = endIter.get();
+ break;
+ }
+ }
+ if (writing && s > begin) aResultBuf.Append(begin, s - begin);
+
+ return writing;
+}
+#endif
+
+//----------------------------------------------------------------------------
+// miscellaneous (i.e., stuff that should really be elsewhere)
+//----------------------------------------------------------------------------
+
+static inline void ToLower(char& c) {
+ if ((unsigned)(c - 'A') <= (unsigned)('Z' - 'A')) c += 'a' - 'A';
+}
+
+void net_ToLowerCase(char* str, uint32_t length) {
+ for (char* end = str + length; str < end; ++str) ToLower(*str);
+}
+
+void net_ToLowerCase(char* str) {
+ for (; *str; ++str) ToLower(*str);
+}
+
+char* net_FindCharInSet(const char* iter, const char* stop, const char* set) {
+ for (; iter != stop && *iter; ++iter) {
+ for (const char* s = set; *s; ++s) {
+ if (*iter == *s) return (char*)iter;
+ }
+ }
+ return (char*)iter;
+}
+
+char* net_FindCharNotInSet(const char* iter, const char* stop,
+ const char* set) {
+repeat:
+ for (const char* s = set; *s; ++s) {
+ if (*iter == *s) {
+ if (++iter == stop) break;
+ goto repeat;
+ }
+ }
+ return (char*)iter;
+}
+
+char* net_RFindCharNotInSet(const char* stop, const char* iter,
+ const char* set) {
+ --iter;
+ --stop;
+
+ if (iter == stop) return (char*)iter;
+
+repeat:
+ for (const char* s = set; *s; ++s) {
+ if (*iter == *s) {
+ if (--iter == stop) break;
+ goto repeat;
+ }
+ }
+ return (char*)iter;
+}
+
+#define HTTP_LWS " \t"
+
+// Return the index of the closing quote of the string, if any
+static uint32_t net_FindStringEnd(const nsCString& flatStr,
+ uint32_t stringStart, char stringDelim) {
+ NS_ASSERTION(stringStart < flatStr.Length() &&
+ flatStr.CharAt(stringStart) == stringDelim &&
+ (stringDelim == '"' || stringDelim == '\''),
+ "Invalid stringStart");
+
+ const char set[] = {stringDelim, '\\', '\0'};
+ do {
+ // stringStart points to either the start quote or the last
+ // escaped char (the char following a '\\')
+
+ // Write to searchStart here, so that when we get back to the
+ // top of the loop right outside this one we search from the
+ // right place.
+ uint32_t stringEnd = flatStr.FindCharInSet(set, stringStart + 1);
+ if (stringEnd == uint32_t(kNotFound)) return flatStr.Length();
+
+ if (flatStr.CharAt(stringEnd) == '\\') {
+ // Hit a backslash-escaped char. Need to skip over it.
+ stringStart = stringEnd + 1;
+ if (stringStart == flatStr.Length()) return stringStart;
+
+ // Go back to looking for the next escape or the string end
+ continue;
+ }
+
+ return stringEnd;
+
+ } while (true);
+
+ MOZ_ASSERT_UNREACHABLE("How did we get here?");
+ return flatStr.Length();
+}
+
+static uint32_t net_FindMediaDelimiter(const nsCString& flatStr,
+ uint32_t searchStart, char delimiter) {
+ do {
+ // searchStart points to the spot from which we should start looking
+ // for the delimiter.
+ const char delimStr[] = {delimiter, '"', '\0'};
+ uint32_t curDelimPos = flatStr.FindCharInSet(delimStr, searchStart);
+ if (curDelimPos == uint32_t(kNotFound)) return flatStr.Length();
+
+ char ch = flatStr.CharAt(curDelimPos);
+ if (ch == delimiter) {
+ // Found delimiter
+ return curDelimPos;
+ }
+
+ // We hit the start of a quoted string. Look for its end.
+ searchStart = net_FindStringEnd(flatStr, curDelimPos, ch);
+ if (searchStart == flatStr.Length()) return searchStart;
+
+ ++searchStart;
+
+ // searchStart now points to the first char after the end of the
+ // string, so just go back to the top of the loop and look for
+ // |delimiter| again.
+ } while (true);
+
+ MOZ_ASSERT_UNREACHABLE("How did we get here?");
+ return flatStr.Length();
+}
+
+// aOffset should be added to aCharsetStart and aCharsetEnd if this
+// function sets them.
+static void net_ParseMediaType(const nsACString& aMediaTypeStr,
+ nsACString& aContentType,
+ nsACString& aContentCharset, int32_t aOffset,
+ bool* aHadCharset, int32_t* aCharsetStart,
+ int32_t* aCharsetEnd, bool aStrict) {
+ const nsCString& flatStr = PromiseFlatCString(aMediaTypeStr);
+ const char* start = flatStr.get();
+ const char* end = start + flatStr.Length();
+
+ // Trim LWS leading and trailing whitespace from type.
+ const char* type = net_FindCharNotInSet(start, end, HTTP_LWS);
+ const char* typeEnd = net_FindCharInSet(type, end, HTTP_LWS ";");
+
+ const char* charset = "";
+ const char* charsetEnd = charset;
+ int32_t charsetParamStart = 0;
+ int32_t charsetParamEnd = 0;
+
+ uint32_t consumed = typeEnd - type;
+
+ // Iterate over parameters
+ bool typeHasCharset = false;
+ uint32_t paramStart = flatStr.FindChar(';', typeEnd - start);
+ if (paramStart != uint32_t(kNotFound)) {
+ // We have parameters. Iterate over them.
+ uint32_t curParamStart = paramStart + 1;
+ do {
+ uint32_t curParamEnd =
+ net_FindMediaDelimiter(flatStr, curParamStart, ';');
+
+ const char* paramName = net_FindCharNotInSet(
+ start + curParamStart, start + curParamEnd, HTTP_LWS);
+ static const char charsetStr[] = "charset=";
+ if (nsCRT::strncasecmp(paramName, charsetStr, sizeof(charsetStr) - 1) ==
+ 0) {
+ charset = paramName + sizeof(charsetStr) - 1;
+ charsetEnd = start + curParamEnd;
+ typeHasCharset = true;
+ charsetParamStart = curParamStart - 1;
+ charsetParamEnd = curParamEnd;
+ }
+
+ consumed = curParamEnd;
+ curParamStart = curParamEnd + 1;
+ } while (curParamStart < flatStr.Length());
+ }
+
+ bool charsetNeedsQuotedStringUnescaping = false;
+ if (typeHasCharset) {
+ // Trim LWS leading and trailing whitespace from charset.
+ charset = net_FindCharNotInSet(charset, charsetEnd, HTTP_LWS);
+ if (*charset == '"') {
+ charsetNeedsQuotedStringUnescaping = true;
+ charsetEnd =
+ start + net_FindStringEnd(flatStr, charset - start, *charset);
+ charset++;
+ NS_ASSERTION(charsetEnd >= charset, "Bad charset parsing");
+ } else {
+ charsetEnd = net_FindCharInSet(charset, charsetEnd, HTTP_LWS ";");
+ }
+ }
+
+ // if the server sent "*/*", it is meaningless, so do not store it.
+ // also, if type is the same as aContentType, then just update the
+ // charset. however, if charset is empty and aContentType hasn't
+ // changed, then don't wipe-out an existing aContentCharset. We
+ // also want to reject a mime-type if it does not include a slash.
+ // some servers give junk after the charset parameter, which may
+ // include a comma, so this check makes us a bit more tolerant.
+
+ if (type != typeEnd && memchr(type, '/', typeEnd - type) != nullptr &&
+ (aStrict ? (net_FindCharNotInSet(start + consumed, end, HTTP_LWS) == end)
+ : (strncmp(type, "*/*", typeEnd - type) != 0))) {
+ // Common case here is that aContentType is empty
+ bool eq = !aContentType.IsEmpty() &&
+ aContentType.Equals(Substring(type, typeEnd),
+ nsCaseInsensitiveCStringComparator);
+ if (!eq) {
+ aContentType.Assign(type, typeEnd - type);
+ ToLowerCase(aContentType);
+ }
+
+ if ((!eq && *aHadCharset) || typeHasCharset) {
+ *aHadCharset = true;
+ if (charsetNeedsQuotedStringUnescaping) {
+ // parameters using the "quoted-string" syntax need
+ // backslash-escapes to be unescaped (see RFC 2616 Section 2.2)
+ aContentCharset.Truncate();
+ for (const char* c = charset; c != charsetEnd; c++) {
+ if (*c == '\\' && c + 1 != charsetEnd) {
+ // eat escape
+ c++;
+ }
+ aContentCharset.Append(*c);
+ }
+ } else {
+ aContentCharset.Assign(charset, charsetEnd - charset);
+ }
+ if (typeHasCharset) {
+ *aCharsetStart = charsetParamStart + aOffset;
+ *aCharsetEnd = charsetParamEnd + aOffset;
+ }
+ }
+ // Only set a new charset position if this is a different type
+ // from the last one we had and it doesn't already have a
+ // charset param. If this is the same type, we probably want
+ // to leave the charset position on its first occurrence.
+ if (!eq && !typeHasCharset) {
+ int32_t charsetStart = int32_t(paramStart);
+ if (charsetStart == kNotFound) charsetStart = flatStr.Length();
+
+ *aCharsetEnd = *aCharsetStart = charsetStart + aOffset;
+ }
+ }
+}
+
+#undef HTTP_LWS
+
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset) {
+ int32_t dummy1, dummy2;
+ net_ParseContentType(aHeaderStr, aContentType, aContentCharset, aHadCharset,
+ &dummy1, &dummy2);
+}
+
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset, int32_t* aCharsetStart,
+ int32_t* aCharsetEnd) {
+ //
+ // Augmented BNF (from RFC 2616 section 3.7):
+ //
+ // header-value = media-type *( LWS "," LWS media-type )
+ // media-type = type "/" subtype *( LWS ";" LWS parameter )
+ // type = token
+ // subtype = token
+ // parameter = attribute "=" value
+ // attribute = token
+ // value = token | quoted-string
+ //
+ //
+ // Examples:
+ //
+ // text/html
+ // text/html, text/html
+ // text/html,text/html; charset=ISO-8859-1
+ // text/html,text/html; charset="ISO-8859-1"
+ // text/html;charset=ISO-8859-1, text/html
+ // text/html;charset='ISO-8859-1', text/html
+ // application/octet-stream
+ //
+
+ *aHadCharset = false;
+ const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+ // iterate over media-types. Note that ',' characters can happen
+ // inside quoted strings, so we need to watch out for that.
+ uint32_t curTypeStart = 0;
+ do {
+ // curTypeStart points to the start of the current media-type. We want
+ // to look for its end.
+ uint32_t curTypeEnd = net_FindMediaDelimiter(flatStr, curTypeStart, ',');
+
+ // At this point curTypeEnd points to the spot where the media-type
+ // starting at curTypeEnd ends. Time to parse that!
+ net_ParseMediaType(
+ Substring(flatStr, curTypeStart, curTypeEnd - curTypeStart),
+ aContentType, aContentCharset, curTypeStart, aHadCharset, aCharsetStart,
+ aCharsetEnd, false);
+
+ // And let's move on to the next media-type
+ curTypeStart = curTypeEnd + 1;
+ } while (curTypeStart < flatStr.Length());
+}
+
+void net_ParseRequestContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType,
+ nsACString& aContentCharset,
+ bool* aHadCharset) {
+ //
+ // Augmented BNF (from RFC 7231 section 3.1.1.1):
+ //
+ // media-type = type "/" subtype *( OWS ";" OWS parameter )
+ // type = token
+ // subtype = token
+ // parameter = token "=" ( token / quoted-string )
+ //
+ // Examples:
+ //
+ // text/html
+ // text/html; charset=ISO-8859-1
+ // text/html; charset="ISO-8859-1"
+ // application/octet-stream
+ //
+
+ aContentType.Truncate();
+ aContentCharset.Truncate();
+ *aHadCharset = false;
+ const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+ // At this point curTypeEnd points to the spot where the media-type
+ // starting at curTypeEnd ends. Time to parse that!
+ nsAutoCString contentType, contentCharset;
+ bool hadCharset = false;
+ int32_t dummy1, dummy2;
+ uint32_t typeEnd = net_FindMediaDelimiter(flatStr, 0, ',');
+ if (typeEnd != flatStr.Length()) {
+ // We have some stuff left at the end, so this is not a valid
+ // request Content-Type header.
+ return;
+ }
+ net_ParseMediaType(flatStr, contentType, contentCharset, 0, &hadCharset,
+ &dummy1, &dummy2, true);
+
+ aContentType = contentType;
+ aContentCharset = contentCharset;
+ *aHadCharset = hadCharset;
+}
+
+bool net_IsValidHostName(const nsACString& host) {
+ // The host name is limited to 253 ascii characters.
+ if (host.Length() > 253) {
+ return false;
+ }
+
+ const char* end = host.EndReading();
+ // Use explicit whitelists to select which characters we are
+ // willing to send to lower-level DNS logic. This is more
+ // self-documenting, and can also be slightly faster than the
+ // blacklist approach, since DNS names are the common case, and
+ // the commonest characters will tend to be near the start of
+ // the list.
+
+ // Whitelist for DNS names (RFC 1035) with extra characters added
+ // for pragmatic reasons "$+_"
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=355181#c2
+ if (net_FindCharNotInSet(host.BeginReading(), end,
+ "abcdefghijklmnopqrstuvwxyz"
+ ".-0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ$+_") == end) {
+ return true;
+ }
+
+ // Might be a valid IPv6 link-local address containing a percent sign
+ return mozilla::net::HostIsIPLiteral(host);
+}
+
+bool net_IsValidIPv4Addr(const nsACString& aAddr) {
+ return mozilla::net::rust_net_is_valid_ipv4_addr(&aAddr);
+}
+
+bool net_IsValidIPv6Addr(const nsACString& aAddr) {
+ return mozilla::net::rust_net_is_valid_ipv6_addr(&aAddr);
+}
+
+bool net_GetDefaultStatusTextForCode(uint16_t aCode, nsACString& aOutText) {
+ switch (aCode) {
+ // start with the most common
+ case 200:
+ aOutText.AssignLiteral("OK");
+ break;
+ case 404:
+ aOutText.AssignLiteral("Not Found");
+ break;
+ case 301:
+ aOutText.AssignLiteral("Moved Permanently");
+ break;
+ case 304:
+ aOutText.AssignLiteral("Not Modified");
+ break;
+ case 307:
+ aOutText.AssignLiteral("Temporary Redirect");
+ break;
+ case 500:
+ aOutText.AssignLiteral("Internal Server Error");
+ break;
+
+ // also well known
+ case 100:
+ aOutText.AssignLiteral("Continue");
+ break;
+ case 101:
+ aOutText.AssignLiteral("Switching Protocols");
+ break;
+ case 201:
+ aOutText.AssignLiteral("Created");
+ break;
+ case 202:
+ aOutText.AssignLiteral("Accepted");
+ break;
+ case 203:
+ aOutText.AssignLiteral("Non Authoritative");
+ break;
+ case 204:
+ aOutText.AssignLiteral("No Content");
+ break;
+ case 205:
+ aOutText.AssignLiteral("Reset Content");
+ break;
+ case 206:
+ aOutText.AssignLiteral("Partial Content");
+ break;
+ case 207:
+ aOutText.AssignLiteral("Multi-Status");
+ break;
+ case 208:
+ aOutText.AssignLiteral("Already Reported");
+ break;
+ case 300:
+ aOutText.AssignLiteral("Multiple Choices");
+ break;
+ case 302:
+ aOutText.AssignLiteral("Found");
+ break;
+ case 303:
+ aOutText.AssignLiteral("See Other");
+ break;
+ case 305:
+ aOutText.AssignLiteral("Use Proxy");
+ break;
+ case 308:
+ aOutText.AssignLiteral("Permanent Redirect");
+ break;
+ case 400:
+ aOutText.AssignLiteral("Bad Request");
+ break;
+ case 401:
+ aOutText.AssignLiteral("Unauthorized");
+ break;
+ case 402:
+ aOutText.AssignLiteral("Payment Required");
+ break;
+ case 403:
+ aOutText.AssignLiteral("Forbidden");
+ break;
+ case 405:
+ aOutText.AssignLiteral("Method Not Allowed");
+ break;
+ case 406:
+ aOutText.AssignLiteral("Not Acceptable");
+ break;
+ case 407:
+ aOutText.AssignLiteral("Proxy Authentication Required");
+ break;
+ case 408:
+ aOutText.AssignLiteral("Request Timeout");
+ break;
+ case 409:
+ aOutText.AssignLiteral("Conflict");
+ break;
+ case 410:
+ aOutText.AssignLiteral("Gone");
+ break;
+ case 411:
+ aOutText.AssignLiteral("Length Required");
+ break;
+ case 412:
+ aOutText.AssignLiteral("Precondition Failed");
+ break;
+ case 413:
+ aOutText.AssignLiteral("Request Entity Too Large");
+ break;
+ case 414:
+ aOutText.AssignLiteral("Request URI Too Long");
+ break;
+ case 415:
+ aOutText.AssignLiteral("Unsupported Media Type");
+ break;
+ case 416:
+ aOutText.AssignLiteral("Requested Range Not Satisfiable");
+ break;
+ case 417:
+ aOutText.AssignLiteral("Expectation Failed");
+ break;
+ case 418:
+ aOutText.AssignLiteral("I'm a teapot");
+ break;
+ case 421:
+ aOutText.AssignLiteral("Misdirected Request");
+ break;
+ case 422:
+ aOutText.AssignLiteral("Unprocessable Entity");
+ break;
+ case 423:
+ aOutText.AssignLiteral("Locked");
+ break;
+ case 424:
+ aOutText.AssignLiteral("Failed Dependency");
+ break;
+ case 425:
+ aOutText.AssignLiteral("Too Early");
+ break;
+ case 426:
+ aOutText.AssignLiteral("Upgrade Required");
+ break;
+ case 428:
+ aOutText.AssignLiteral("Precondition Required");
+ break;
+ case 429:
+ aOutText.AssignLiteral("Too Many Requests");
+ break;
+ case 431:
+ aOutText.AssignLiteral("Request Header Fields Too Large");
+ break;
+ case 451:
+ aOutText.AssignLiteral("Unavailable For Legal Reasons");
+ break;
+ case 501:
+ aOutText.AssignLiteral("Not Implemented");
+ break;
+ case 502:
+ aOutText.AssignLiteral("Bad Gateway");
+ break;
+ case 503:
+ aOutText.AssignLiteral("Service Unavailable");
+ break;
+ case 504:
+ aOutText.AssignLiteral("Gateway Timeout");
+ break;
+ case 505:
+ aOutText.AssignLiteral("HTTP Version Unsupported");
+ break;
+ case 506:
+ aOutText.AssignLiteral("Variant Also Negotiates");
+ break;
+ case 507:
+ aOutText.AssignLiteral("Insufficient Storage ");
+ break;
+ case 508:
+ aOutText.AssignLiteral("Loop Detected");
+ break;
+ case 510:
+ aOutText.AssignLiteral("Not Extended");
+ break;
+ case 511:
+ aOutText.AssignLiteral("Network Authentication Required");
+ break;
+ default:
+ aOutText.AssignLiteral("No Reason Phrase");
+ return false;
+ }
+ return true;
+}
+
+namespace mozilla {
+static auto MakeNameMatcher(const nsAString& aName) {
+ return [&aName](const auto& param) { return param.mKey.Equals(aName); };
+}
+
+bool URLParams::Has(const nsAString& aName) {
+ return std::any_of(mParams.cbegin(), mParams.cend(), MakeNameMatcher(aName));
+}
+
+bool URLParams::Has(const nsAString& aName, const nsAString& aValue) {
+ return std::any_of(
+ mParams.cbegin(), mParams.cend(), [&aName, &aValue](const auto& param) {
+ return param.mKey.Equals(aName) && param.mValue.Equals(aValue);
+ });
+}
+
+void URLParams::Get(const nsAString& aName, nsString& aRetval) {
+ SetDOMStringToNull(aRetval);
+
+ const auto end = mParams.cend();
+ const auto it = std::find_if(mParams.cbegin(), end, MakeNameMatcher(aName));
+ if (it != end) {
+ aRetval.Assign(it->mValue);
+ }
+}
+
+void URLParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval) {
+ aRetval.Clear();
+
+ for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
+ if (mParams[i].mKey.Equals(aName)) {
+ aRetval.AppendElement(mParams[i].mValue);
+ }
+ }
+}
+
+void URLParams::Append(const nsAString& aName, const nsAString& aValue) {
+ Param* param = mParams.AppendElement();
+ param->mKey = aName;
+ param->mValue = aValue;
+}
+
+void URLParams::Set(const nsAString& aName, const nsAString& aValue) {
+ Param* param = nullptr;
+ for (uint32_t i = 0, len = mParams.Length(); i < len;) {
+ if (!mParams[i].mKey.Equals(aName)) {
+ ++i;
+ continue;
+ }
+ if (!param) {
+ param = &mParams[i];
+ ++i;
+ continue;
+ }
+ // Remove duplicates.
+ mParams.RemoveElementAt(i);
+ --len;
+ }
+
+ if (!param) {
+ param = mParams.AppendElement();
+ param->mKey = aName;
+ }
+
+ param->mValue = aValue;
+}
+
+void URLParams::Delete(const nsAString& aName) {
+ mParams.RemoveElementsBy(
+ [&aName](const auto& param) { return param.mKey.Equals(aName); });
+}
+
+void URLParams::Delete(const nsAString& aName, const nsAString& aValue) {
+ mParams.RemoveElementsBy([&aName, &aValue](const auto& param) {
+ return param.mKey.Equals(aName) && param.mValue.Equals(aValue);
+ });
+}
+
+/* static */
+void URLParams::ConvertString(const nsACString& aInput, nsAString& aOutput) {
+ if (NS_FAILED(UTF_8_ENCODING->DecodeWithoutBOMHandling(aInput, aOutput))) {
+ MOZ_CRASH("Out of memory when converting URL params.");
+ }
+}
+
+/* static */
+void URLParams::DecodeString(const nsACString& aInput, nsAString& aOutput) {
+ const char* const end = aInput.EndReading();
+
+ nsAutoCString unescaped;
+
+ for (const char* iter = aInput.BeginReading(); iter != end;) {
+ // replace '+' with U+0020
+ if (*iter == '+') {
+ unescaped.Append(' ');
+ ++iter;
+ continue;
+ }
+
+ // Percent decode algorithm
+ if (*iter == '%') {
+ const char* const first = iter + 1;
+ const char* const second = first + 1;
+
+ const auto asciiHexDigit = [](char x) {
+ return (x >= 0x41 && x <= 0x46) || (x >= 0x61 && x <= 0x66) ||
+ (x >= 0x30 && x <= 0x39);
+ };
+
+ const auto hexDigit = [](char x) {
+ return x >= 0x30 && x <= 0x39
+ ? x - 0x30
+ : (x >= 0x41 && x <= 0x46 ? x - 0x37 : x - 0x57);
+ };
+
+ if (first != end && second != end && asciiHexDigit(*first) &&
+ asciiHexDigit(*second)) {
+ unescaped.Append(hexDigit(*first) * 16 + hexDigit(*second));
+ iter = second + 1;
+ } else {
+ unescaped.Append('%');
+ ++iter;
+ }
+
+ continue;
+ }
+
+ unescaped.Append(*iter);
+ ++iter;
+ }
+
+ // XXX It seems rather wasteful to first decode into a UTF-8 nsCString and
+ // then convert the whole string to UTF-16, at least if we exceed the inline
+ // storage size.
+ ConvertString(unescaped, aOutput);
+}
+
+/* static */
+bool URLParams::ParseNextInternal(const char*& aStart, const char* const aEnd,
+ nsAString* aOutDecodedName,
+ nsAString* aOutDecodedValue) {
+ nsDependentCSubstring string;
+
+ const char* const iter = std::find(aStart, aEnd, '&');
+ if (iter != aEnd) {
+ string.Rebind(aStart, iter);
+ aStart = iter + 1;
+ } else {
+ string.Rebind(aStart, aEnd);
+ aStart = aEnd;
+ }
+
+ if (string.IsEmpty()) {
+ return false;
+ }
+
+ const auto* const eqStart = string.BeginReading();
+ const auto* const eqEnd = string.EndReading();
+ const auto* const eqIter = std::find(eqStart, eqEnd, '=');
+
+ nsDependentCSubstring name;
+ nsDependentCSubstring value;
+
+ if (eqIter != eqEnd) {
+ name.Rebind(eqStart, eqIter);
+ value.Rebind(eqIter + 1, eqEnd);
+ } else {
+ name.Rebind(string, 0);
+ }
+
+ DecodeString(name, *aOutDecodedName);
+ DecodeString(value, *aOutDecodedValue);
+
+ return true;
+}
+
+/* static */
+bool URLParams::Extract(const nsACString& aInput, const nsAString& aName,
+ nsAString& aValue) {
+ aValue.SetIsVoid(true);
+ return !URLParams::Parse(
+ aInput, [&aName, &aValue](const nsAString& name, nsString&& value) {
+ if (aName == name) {
+ aValue = std::move(value);
+ return false;
+ }
+ return true;
+ });
+}
+
+void URLParams::ParseInput(const nsACString& aInput) {
+ // Remove all the existing data before parsing a new input.
+ DeleteAll();
+
+ URLParams::Parse(aInput, [this](nsString&& name, nsString&& value) {
+ mParams.AppendElement(Param{std::move(name), std::move(value)});
+ return true;
+ });
+}
+
+namespace {
+
+void SerializeString(const nsCString& aInput, nsAString& aValue) {
+ const unsigned char* p = (const unsigned char*)aInput.get();
+ const unsigned char* end = p + aInput.Length();
+
+ while (p != end) {
+ // ' ' to '+'
+ if (*p == 0x20) {
+ aValue.Append(0x2B);
+ // Percent Encode algorithm
+ } else if (*p == 0x2A || *p == 0x2D || *p == 0x2E ||
+ (*p >= 0x30 && *p <= 0x39) || (*p >= 0x41 && *p <= 0x5A) ||
+ *p == 0x5F || (*p >= 0x61 && *p <= 0x7A)) {
+ aValue.Append(*p);
+ } else {
+ aValue.AppendPrintf("%%%.2X", *p);
+ }
+
+ ++p;
+ }
+}
+
+} // namespace
+
+void URLParams::Serialize(nsAString& aValue, bool aEncode) const {
+ aValue.Truncate();
+ bool first = true;
+
+ for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
+ if (first) {
+ first = false;
+ } else {
+ aValue.Append('&');
+ }
+
+ // XXX Actually, it's not necessary to build a new string object. Generally,
+ // such cases could just convert each codepoint one-by-one.
+ if (aEncode) {
+ SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mKey), aValue);
+ aValue.Append('=');
+ SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mValue), aValue);
+ } else {
+ aValue.Append(mParams[i].mKey);
+ aValue.Append('=');
+ aValue.Append(mParams[i].mValue);
+ }
+ }
+}
+
+void URLParams::Sort() {
+ mParams.StableSort([](const Param& lhs, const Param& rhs) {
+ return Compare(lhs.mKey, rhs.mKey);
+ });
+}
+
+} // namespace mozilla
diff --git a/netwerk/base/nsURLHelper.h b/netwerk/base/nsURLHelper.h
new file mode 100644
index 0000000000..f5ccc8bac6
--- /dev/null
+++ b/netwerk/base/nsURLHelper.h
@@ -0,0 +1,372 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsURLHelper_h__
+#define nsURLHelper_h__
+
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsASCIIMask.h"
+
+class nsIFile;
+class nsIURLParser;
+
+enum netCoalesceFlags {
+ NET_COALESCE_NORMAL = 0,
+
+ /**
+ * retains /../ that reach above dir root (useful for FTP
+ * servers in which the root of the FTP URL is not necessarily
+ * the root of the FTP filesystem).
+ */
+ NET_COALESCE_ALLOW_RELATIVE_ROOT = 1 << 0,
+
+ /**
+ * recognizes /%2F and // as markers for the root directory
+ * and handles them properly.
+ */
+ NET_COALESCE_DOUBLE_SLASH_IS_ROOT = 1 << 1
+};
+
+//----------------------------------------------------------------------------
+// This module contains some private helper functions related to URL parsing.
+//----------------------------------------------------------------------------
+
+/* shutdown frees URL parser */
+void net_ShutdownURLHelper();
+#ifdef XP_MACOSX
+void net_ShutdownURLHelperOSX();
+#endif
+
+/* access URL parsers */
+nsIURLParser* net_GetAuthURLParser();
+nsIURLParser* net_GetNoAuthURLParser();
+nsIURLParser* net_GetStdURLParser();
+
+/* convert between nsIFile and file:// URL spec
+ * net_GetURLSpecFromFile does an extra stat, so callers should
+ * avoid it if possible in favor of net_GetURLSpecFromActualFile
+ * and net_GetURLSpecFromDir */
+nsresult net_GetURLSpecFromFile(nsIFile*, nsACString&);
+nsresult net_GetURLSpecFromDir(nsIFile*, nsACString&);
+nsresult net_GetURLSpecFromActualFile(nsIFile*, nsACString&);
+nsresult net_GetFileFromURLSpec(const nsACString&, nsIFile**);
+
+/* extract file path components from file:// URL */
+nsresult net_ParseFileURL(const nsACString& inURL, nsACString& outDirectory,
+ nsACString& outFileBaseName,
+ nsACString& outFileExtension);
+
+/* handle .. in dirs while resolving URLs (path is UTF-8) */
+void net_CoalesceDirs(netCoalesceFlags flags, char* path);
+
+/**
+ * Check if a URL is absolute
+ *
+ * @param inURL URL spec
+ * @return true if the given spec represents an absolute URL
+ */
+bool net_IsAbsoluteURL(const nsACString& uri);
+
+/**
+ * Extract URI-Scheme if possible
+ *
+ * @param inURI URI spec
+ * @param scheme scheme copied to this buffer on return. Is lowercase.
+ */
+nsresult net_ExtractURLScheme(const nsACString& inURI, nsACString& scheme);
+
+/* check that the given scheme conforms to RFC 2396 */
+bool net_IsValidScheme(const nsACString& scheme);
+
+/**
+ * This function strips out all C0 controls and space at the beginning and end
+ * of the URL and filters out \r, \n, \t from the middle of the URL. This makes
+ * it safe to call on things like javascript: urls or data: urls, where we may
+ * in fact run into whitespace that is not properly encoded.
+ *
+ * @param input the URL spec we want to filter
+ * @param result the out param to write to if filtering happens
+ */
+void net_FilterURIString(const nsACString& input, nsACString& result);
+
+/**
+ * This function performs character stripping just like net_FilterURIString,
+ * with the added benefit of also performing percent escaping of dissallowed
+ * characters, all in one pass. Saving one pass is very important when operating
+ * on really large strings.
+ *
+ * @param aInput the URL spec we want to filter
+ * @param aFlags the flags which control which characters we escape
+ * @param aFilterMask a mask of characters that should excluded from the result
+ * @param aResult the out param to write to if filtering happens
+ */
+nsresult net_FilterAndEscapeURI(const nsACString& aInput, uint32_t aFlags,
+ const ASCIIMaskArray& aFilterMask,
+ nsACString& aResult);
+
+#if defined(XP_WIN)
+/**
+ * On Win32 and OS/2 system's a back-slash in a file:// URL is equivalent to a
+ * forward-slash. This function maps any back-slashes to forward-slashes.
+ *
+ * @param aURL
+ * The URL string to normalize (UTF-8 encoded). This can be a
+ * relative URL segment.
+ * @param aResultBuf
+ * The resulting string is appended to this string. If the input URL
+ * is already normalized, then aResultBuf is unchanged.
+ *
+ * @returns false if aURL is already normalized. Otherwise, returns true.
+ */
+bool net_NormalizeFileURL(const nsACString& aURL, nsCString& aResultBuf);
+#endif
+
+/*****************************************************************************
+ * generic string routines follow (XXX move to someplace more generic).
+ */
+
+/* convert to lower case */
+void net_ToLowerCase(char* str, uint32_t length);
+void net_ToLowerCase(char* str);
+
+/**
+ * returns pointer to first character of |str| in the given set. if not found,
+ * then |end| is returned. stops prematurely if a null byte is encountered,
+ * and returns the address of the null byte.
+ */
+char* net_FindCharInSet(const char* iter, const char* stop, const char* set);
+
+/**
+ * returns pointer to first character of |str| NOT in the given set. if all
+ * characters are in the given set, then |end| is returned. if '\0' is not
+ * included in |set|, then stops prematurely if a null byte is encountered,
+ * and returns the address of the null byte.
+ */
+char* net_FindCharNotInSet(const char* iter, const char* stop, const char* set);
+
+/**
+ * returns pointer to last character of |str| NOT in the given set. if all
+ * characters are in the given set, then |str - 1| is returned.
+ */
+char* net_RFindCharNotInSet(const char* stop, const char* iter,
+ const char* set);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any). aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr. In that case (no charset
+ * specified), aHadCharset is set to false. Otherwise, it's set to
+ * true. Note that aContentCharset can be empty even if aHadCharset
+ * is true.
+ *
+ * This parsing is suitable for HTTP request. Use net_ParseContentType
+ * for parsing this header in HTTP responses.
+ */
+void net_ParseRequestContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType,
+ nsACString& aContentCharset,
+ bool* aHadCharset);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any). aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr. In that case (no charset
+ * specified), aHadCharset is set to false. Otherwise, it's set to
+ * true. Note that aContentCharset can be empty even if aHadCharset
+ * is true.
+ */
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset);
+/**
+ * As above, but also returns the start and end indexes for the charset
+ * parameter in aHeaderStr. These are indices for the entire parameter, NOT
+ * just the value. If there is "effectively" no charset parameter (e.g. if an
+ * earlier type with one is overridden by a later type without one),
+ * *aHadCharset will be true but *aCharsetStart will be set to -1. Note that
+ * it's possible to have aContentCharset empty and *aHadCharset true when
+ * *aCharsetStart is nonnegative; this corresponds to charset="".
+ */
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset, int32_t* aCharsetStart,
+ int32_t* aCharsetEnd);
+
+/* inline versions */
+
+/* remember the 64-bit platforms ;-) */
+#define NET_MAX_ADDRESS ((char*)UINTPTR_MAX)
+
+inline char* net_FindCharInSet(const char* str, const char* set) {
+ return net_FindCharInSet(str, NET_MAX_ADDRESS, set);
+}
+inline char* net_FindCharNotInSet(const char* str, const char* set) {
+ return net_FindCharNotInSet(str, NET_MAX_ADDRESS, set);
+}
+inline char* net_RFindCharNotInSet(const char* str, const char* set) {
+ return net_RFindCharNotInSet(str, str + strlen(str), set);
+}
+
+/**
+ * This function returns true if the given hostname does not include any
+ * restricted characters. Otherwise, false is returned.
+ */
+bool net_IsValidHostName(const nsACString& host);
+
+/**
+ * Checks whether the IPv4 address is valid according to RFC 3986 section 3.2.2.
+ */
+bool net_IsValidIPv4Addr(const nsACString& aAddr);
+
+/**
+ * Checks whether the IPv6 address is valid according to RFC 3986 section 3.2.2.
+ */
+bool net_IsValidIPv6Addr(const nsACString& aAddr);
+
+/**
+ * Returns the default status text for a given HTTP status code (useful if HTTP2
+ * does not provide one, for instance).
+ */
+bool net_GetDefaultStatusTextForCode(uint16_t aCode, nsACString& aOutText);
+
+namespace mozilla {
+/**
+ * A class for handling form-urlencoded query strings.
+ *
+ * Manages an ordered list of name-value pairs, and allows conversion from and
+ * to the string representation.
+ *
+ * In addition, there are static functions for handling one-shot use cases.
+ */
+class URLParams final {
+ public:
+ /**
+ * \brief Parses a query string and calls a parameter handler for each
+ * name/value pair. The parameter handler can stop processing early by
+ * returning false.
+ *
+ * \param aInput the query string to parse
+ * \param aParamHandler the parameter handler as desribed above
+ * \tparam ParamHandler a function type compatible with signature
+ * bool(nsString, nsString)
+ *
+ * \return false if the parameter handler returned false for any parameter,
+ * true otherwise
+ */
+ template <typename ParamHandler>
+ static bool Parse(const nsACString& aInput, ParamHandler aParamHandler) {
+ const char* start = aInput.BeginReading();
+ const char* const end = aInput.EndReading();
+
+ while (start != end) {
+ nsAutoString decodedName;
+ nsAutoString decodedValue;
+
+ if (!ParseNextInternal(start, end, &decodedName, &decodedValue)) {
+ continue;
+ }
+
+ if (!aParamHandler(std::move(decodedName), std::move(decodedValue))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * \brief Parses a query string and returns the value of a single parameter
+ * specified by name.
+ *
+ * If there are multiple parameters with the same name, the value of the first
+ * is returned.
+ *
+ * \param aInput the query string to parse
+ * \param aName the name of the parameter to extract
+ * \param[out] aValue will be assigned the parameter value, set to void if
+ * there is no match \return true iff there was a parameter with with name
+ * \paramref aName
+ */
+ static bool Extract(const nsACString& aInput, const nsAString& aName,
+ nsAString& aValue);
+
+ /**
+ * \brief Resets the state of this instance and parses a new query string.
+ *
+ * \param aInput the query string to parse
+ */
+ void ParseInput(const nsACString& aInput);
+
+ /**
+ * Serializes the current state to a query string.
+ *
+ * \param[out] aValue will be assigned the result of the serialization
+ * \param aEncode If this is true, the serialization will encode the string.
+ */
+ void Serialize(nsAString& aValue, bool aEncode) const;
+
+ void Get(const nsAString& aName, nsString& aRetval);
+
+ void GetAll(const nsAString& aName, nsTArray<nsString>& aRetval);
+
+ /**
+ * \brief Sets the value of a given parameter.
+ *
+ * If one or more parameters of the name exist, the value of the first is
+ * replaced, and all further parameters of the name are deleted. Otherwise,
+ * the behaviour is the same as \ref Append.
+ */
+ void Set(const nsAString& aName, const nsAString& aValue);
+
+ void Append(const nsAString& aName, const nsAString& aValue);
+
+ bool Has(const nsAString& aName);
+
+ bool Has(const nsAString& aName, const nsAString& aValue);
+
+ /**
+ * \brief Deletes all parameters with the given name.
+ */
+ void Delete(const nsAString& aName);
+
+ void Delete(const nsAString& aName, const nsAString& aValue);
+
+ void DeleteAll() { mParams.Clear(); }
+
+ uint32_t Length() const { return mParams.Length(); }
+
+ const nsAString& GetKeyAtIndex(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mParams.Length());
+ return mParams[aIndex].mKey;
+ }
+
+ const nsAString& GetValueAtIndex(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < mParams.Length());
+ return mParams[aIndex].mValue;
+ }
+
+ /**
+ * \brief Performs a stable sort of the parameters, maintaining the order of
+ * multiple parameters with the same name.
+ */
+ void Sort();
+
+ private:
+ static void DecodeString(const nsACString& aInput, nsAString& aOutput);
+ static void ConvertString(const nsACString& aInput, nsAString& aOutput);
+ static bool ParseNextInternal(const char*& aStart, const char* aEnd,
+ nsAString* aOutDecodedName,
+ nsAString* aOutDecodedValue);
+
+ struct Param {
+ nsString mKey;
+ nsString mValue;
+ };
+
+ nsTArray<Param> mParams;
+};
+} // namespace mozilla
+
+#endif // !nsURLHelper_h__
diff --git a/netwerk/base/nsURLHelperOSX.cpp b/netwerk/base/nsURLHelperOSX.cpp
new file mode 100644
index 0000000000..9a8fbad9a2
--- /dev/null
+++ b/netwerk/base/nsURLHelperOSX.cpp
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Mac OS X-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsReadableUtils.h"
+#include <Carbon/Carbon.h>
+
+static nsTArray<nsCString>* gVolumeList = nullptr;
+
+static bool pathBeginsWithVolName(const nsACString& path,
+ nsACString& firstPathComponent) {
+ // Return whether the 1st path component in path (escaped) is equal to the
+ // name of a mounted volume. Return the 1st path component (unescaped) in any
+ // case. This needs to be done as quickly as possible, so we cache a list of
+ // volume names.
+ // XXX Register an event handler to detect drives being mounted/unmounted?
+
+ if (!gVolumeList) {
+ gVolumeList = new nsTArray<nsCString>;
+ if (!gVolumeList) {
+ return false; // out of memory
+ }
+ }
+
+ // Cache a list of volume names
+ if (!gVolumeList->Length()) {
+ OSErr err;
+ ItemCount volumeIndex = 1;
+
+ do {
+ HFSUniStr255 volName;
+ FSRef rootDirectory;
+ err = ::FSGetVolumeInfo(0, volumeIndex, nullptr, kFSVolInfoNone, nullptr,
+ &volName, &rootDirectory);
+ if (err == noErr) {
+ NS_ConvertUTF16toUTF8 volNameStr(
+ Substring((char16_t*)volName.unicode,
+ (char16_t*)volName.unicode + volName.length));
+ gVolumeList->AppendElement(volNameStr);
+ volumeIndex++;
+ }
+ } while (err == noErr);
+ }
+
+ // Extract the first component of the path
+ nsACString::const_iterator start;
+ path.BeginReading(start);
+ start.advance(1); // path begins with '/'
+ nsACString::const_iterator directory_end;
+ path.EndReading(directory_end);
+ nsACString::const_iterator component_end(start);
+ FindCharInReadable('/', component_end, directory_end);
+
+ nsAutoCString flatComponent((Substring(start, component_end)));
+ NS_UnescapeURL(flatComponent);
+ int32_t foundIndex = gVolumeList->IndexOf(flatComponent);
+ firstPathComponent = flatComponent;
+ return (foundIndex != -1);
+}
+
+void net_ShutdownURLHelperOSX() {
+ delete gVolumeList;
+ gVolumeList = nullptr;
+}
+
+static nsresult convertHFSPathtoPOSIX(const nsACString& hfsPath,
+ nsACString& posixPath) {
+ // Use CFURL to do the conversion. We don't want to do this by simply
+ // using SwapSlashColon - we need the charset mapped from MacRoman
+ // to UTF-8, and we need "/Volumes" (or whatever - Apple says this is subject
+ // to change) prepended if the path is not on the boot drive.
+
+ CFStringRef pathStrRef = CFStringCreateWithCString(
+ nullptr, PromiseFlatCString(hfsPath).get(), kCFStringEncodingMacRoman);
+ if (!pathStrRef) return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ CFURLRef urlRef = CFURLCreateWithFileSystemPath(nullptr, pathStrRef,
+ kCFURLHFSPathStyle, true);
+ if (urlRef) {
+ UInt8 pathBuf[PATH_MAX];
+ if (CFURLGetFileSystemRepresentation(urlRef, true, pathBuf,
+ sizeof(pathBuf))) {
+ posixPath = (char*)pathBuf;
+ rv = NS_OK;
+ }
+ }
+ CFRelease(pathStrRef);
+ if (urlRef) CFRelease(urlRef);
+ return rv;
+}
+
+static void SwapSlashColon(char* s) {
+ while (*s) {
+ if (*s == '/')
+ *s = ':';
+ else if (*s == ':')
+ *s = '/';
+ s++;
+ }
+}
+
+nsresult net_GetURLSpecFromActualFile(nsIFile* aFile, nsACString& result) {
+ // NOTE: This is identical to the implementation in nsURLHelperUnix.cpp
+
+ nsresult rv;
+ nsAutoCString ePath;
+
+ // construct URL spec from native file path
+ rv = aFile->GetNativePath(ePath);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString escPath;
+ constexpr auto prefix = "file://"_ns;
+
+ // Escape the path with the directory mask
+ if (NS_EscapeURL(ePath.get(), ePath.Length(), esc_Directory + esc_Forced,
+ escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult net_GetFileFromURLSpec(const nsACString& aURL, nsIFile** result) {
+ // NOTE: See also the implementation in nsURLHelperUnix.cpp
+ // This matches it except for the HFS path handling.
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString directory, fileBaseName, fileExtension, path;
+ bool bHFSPath = false;
+
+ rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!directory.IsEmpty()) {
+ NS_EscapeURL(directory, esc_Directory | esc_AlwaysCopy, path);
+
+ // The canonical form of file URLs on OSX use POSIX paths:
+ // file:///path-name.
+ // But, we still encounter file URLs that use HFS paths:
+ // file:///volume-name/path-name
+ // Determine that here and normalize HFS paths to POSIX.
+ nsAutoCString possibleVolName;
+ if (pathBeginsWithVolName(directory, possibleVolName)) {
+ // Though we know it begins with a volume name, it could still
+ // be a valid POSIX path if the boot drive is named "Mac HD"
+ // and there is a directory "Mac HD" at its root. If such a
+ // directory doesn't exist, we'll assume this is an HFS path.
+ FSRef testRef;
+ possibleVolName.InsertLiteral("/", 0);
+ if (::FSPathMakeRef((UInt8*)possibleVolName.get(), &testRef, nullptr) !=
+ noErr)
+ bHFSPath = true;
+ }
+
+ if (bHFSPath) {
+ // "%2F"s need to become slashes, while all other slashes need to
+ // become colons. If we start out by changing "%2F"s to colons, we
+ // can reply on SwapSlashColon() to do what we need
+ path.ReplaceSubstring("%2F", ":");
+ path.Cut(0, 1); // directory begins with '/'
+ SwapSlashColon((char*)path.get());
+ // At this point, path is an HFS path made using the same
+ // algorithm as nsURLHelperMac. We'll convert to POSIX below.
+ }
+ }
+ if (!fileBaseName.IsEmpty())
+ NS_EscapeURL(fileBaseName, esc_FileBaseName | esc_AlwaysCopy, path);
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ NS_EscapeURL(fileExtension, esc_FileExtension | esc_AlwaysCopy, path);
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get())) return NS_ERROR_FILE_INVALID_PATH;
+
+ if (bHFSPath) convertHFSPathtoPOSIX(path, path);
+
+ // assuming path is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLHelperUnix.cpp b/netwerk/base/nsURLHelperUnix.cpp
new file mode 100644
index 0000000000..abb8b0cfeb
--- /dev/null
+++ b/netwerk/base/nsURLHelperUnix.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Unix-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsNativeCharsetUtils.h"
+#include "mozilla/Utf8.h"
+
+using mozilla::IsUtf8;
+
+nsresult net_GetURLSpecFromActualFile(nsIFile* aFile, nsACString& result) {
+ nsresult rv;
+ nsAutoCString nativePath, ePath;
+ nsAutoString path;
+
+ rv = aFile->GetNativePath(nativePath);
+ if (NS_FAILED(rv)) return rv;
+
+ // Convert to unicode and back to check correct conversion to native charset
+ NS_CopyNativeToUnicode(nativePath, path);
+ NS_CopyUnicodeToNative(path, ePath);
+
+ // Use UTF8 version if conversion was successful
+ if (nativePath == ePath) {
+ CopyUTF16toUTF8(path, ePath);
+ } else {
+ ePath = nativePath;
+ }
+
+ nsAutoCString escPath;
+ constexpr auto prefix = "file://"_ns;
+
+ // Escape the path with the directory mask
+ if (NS_EscapeURL(ePath.get(), -1, esc_Directory + esc_Forced, escPath)) {
+ escPath.Insert(prefix, 0);
+ } else {
+ escPath.Assign(prefix + ePath);
+ }
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult net_GetFileFromURLSpec(const nsACString& aURL, nsIFile** result) {
+ // NOTE: See also the implementation in nsURLHelperOSX.cpp,
+ // which is based on this.
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString directory, fileBaseName, fileExtension, path;
+
+ rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!directory.IsEmpty()) {
+ rv = NS_EscapeURL(directory, esc_Directory | esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (!fileBaseName.IsEmpty()) {
+ rv = NS_EscapeURL(fileBaseName, esc_FileBaseName | esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ rv = NS_EscapeURL(fileExtension, esc_FileExtension | esc_AlwaysCopy, path,
+ mozilla::fallible);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get())) return NS_ERROR_FILE_INVALID_PATH;
+
+ if (IsUtf8(path)) {
+ // speed up the start-up where UTF-8 is the native charset
+ // (e.g. on recent Linux distributions)
+ if (NS_IsNativeUTF8()) {
+ rv = localFile->InitWithNativePath(path);
+ } else {
+ rv = localFile->InitWithPath(NS_ConvertUTF8toUTF16(path));
+ }
+ // XXX In rare cases, a valid UTF-8 string can be valid as a native
+ // encoding (e.g. 0xC5 0x83 is valid both as UTF-8 and Windows-125x).
+ // However, the chance is very low that a meaningful word in a legacy
+ // encoding is valid as UTF-8.
+ } else {
+ // if path is not in UTF-8, assume it is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLHelperWin.cpp b/netwerk/base/nsURLHelperWin.cpp
new file mode 100644
index 0000000000..7a4f53978e
--- /dev/null
+++ b/netwerk/base/nsURLHelperWin.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Windows-specific local file uri parsing */
+#include "nsComponentManagerUtils.h"
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include <windows.h>
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Utf8.h"
+
+using namespace mozilla;
+
+nsresult net_GetURLSpecFromActualFile(nsIFile* aFile, nsACString& result) {
+ nsresult rv;
+ nsAutoString path;
+
+ // construct URL spec from file path
+ rv = aFile->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ // Replace \ with / to convert to an url
+ path.ReplaceChar(char16_t(0x5Cu), char16_t(0x2Fu));
+
+ nsAutoCString escPath;
+
+ // Windows Desktop paths begin with a drive letter, so need an 'extra'
+ // slash at the begining
+ // C:\Windows => file:///C:/Windows
+ constexpr auto prefix = "file:///"_ns;
+
+ // Escape the path with the directory mask
+ NS_ConvertUTF16toUTF8 ePath(path);
+ if (NS_EscapeURL(ePath.get(), -1, esc_Directory + esc_Forced, escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult net_GetFileFromURLSpec(const nsACString& aURL, nsIFile** result) {
+ nsresult rv;
+
+ if (aURL.Length() > StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsCOMPtr<nsIFile> localFile(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Only nsIFile supported right now");
+ return rv;
+ }
+
+ const nsACString* specPtr;
+
+ nsAutoCString buf;
+ if (net_NormalizeFileURL(aURL, buf))
+ specPtr = &buf;
+ else
+ specPtr = &aURL;
+
+ nsAutoCString directory, fileBaseName, fileExtension;
+
+ rv = net_ParseFileURL(*specPtr, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString path;
+
+ if (!directory.IsEmpty()) {
+ NS_EscapeURL(directory, esc_Directory | esc_AlwaysCopy, path);
+ if (path.Length() > 2 && path.CharAt(2) == '|') path.SetCharAt(':', 2);
+ path.ReplaceChar('/', '\\');
+ }
+ if (!fileBaseName.IsEmpty())
+ NS_EscapeURL(fileBaseName, esc_FileBaseName | esc_AlwaysCopy, path);
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ NS_EscapeURL(fileExtension, esc_FileExtension | esc_AlwaysCopy, path);
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get())) return NS_ERROR_FILE_INVALID_PATH;
+
+ // remove leading '\'
+ if (path.CharAt(0) == '\\') path.Cut(0, 1);
+
+ if (IsUtf8(path)) rv = localFile->InitWithPath(NS_ConvertUTF8toUTF16(path));
+ // XXX In rare cases, a valid UTF-8 string can be valid as a native
+ // encoding (e.g. 0xC5 0x83 is valid both as UTF-8 and Windows-125x).
+ // However, the chance is very low that a meaningful word in a legacy
+ // encoding is valid as UTF-8.
+ else
+ // if path is not in UTF-8, assume it is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+
+ if (NS_FAILED(rv)) return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/base/nsURLParsers.cpp b/netwerk/base/nsURLParsers.cpp
new file mode 100644
index 0000000000..7dc6567d84
--- /dev/null
+++ b/netwerk/base/nsURLParsers.cpp
@@ -0,0 +1,644 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+
+#include "mozilla/RangedPtr.h"
+#include "mozilla/TextUtils.h"
+
+#include "nsCRTGlue.h"
+#include "nsURLParsers.h"
+#include "nsURLHelper.h"
+#include "nsString.h"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------------
+
+static uint32_t CountConsecutiveSlashes(const char* str, int32_t len) {
+ RangedPtr<const char> p(str, len);
+ uint32_t count = 0;
+ while (len-- && *p++ == '/') ++count;
+ return count;
+}
+
+//----------------------------------------------------------------------------
+// nsBaseURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsAuthURLParser, nsIURLParser)
+NS_IMPL_ISUPPORTS(nsNoAuthURLParser, nsIURLParser)
+
+#define SET_RESULT(component, pos, len) \
+ PR_BEGIN_MACRO \
+ if (component##Pos) *component##Pos = uint32_t(pos); \
+ if (component##Len) *component##Len = int32_t(len); \
+ PR_END_MACRO
+
+#define OFFSET_RESULT(component, offset) \
+ PR_BEGIN_MACRO \
+ if (component##Pos) *component##Pos += (offset); \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseURL(const char* spec, int32_t specLen,
+ uint32_t* schemePos, int32_t* schemeLen,
+ uint32_t* authorityPos, int32_t* authorityLen,
+ uint32_t* pathPos, int32_t* pathLen) {
+ if (NS_WARN_IF(!spec)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (specLen < 0) specLen = strlen(spec);
+
+ const char* stop = nullptr;
+ const char* colon = nullptr;
+ const char* slash = nullptr;
+ const char* p = spec;
+ uint32_t offset = 0;
+ int32_t len = specLen;
+
+ // skip leading whitespace
+ while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') {
+ spec++;
+ specLen--;
+ offset++;
+
+ p++;
+ len--;
+ }
+
+ for (; len && *p && !colon && !slash; ++p, --len) {
+ switch (*p) {
+ case ':':
+ if (!colon) colon = p;
+ break;
+ case '/': // start of filepath
+ case '?': // start of query
+ case '#': // start of ref
+ if (!slash) slash = p;
+ break;
+ case '@': // username@hostname
+ case '[': // start of IPv6 address literal
+ if (!stop) stop = p;
+ break;
+ }
+ }
+ // disregard the first colon if it follows an '@' or a '['
+ if (colon && stop && colon > stop) colon = nullptr;
+
+ // if the spec only contained whitespace ...
+ if (specLen == 0) {
+ SET_RESULT(scheme, 0, -1);
+ SET_RESULT(authority, 0, 0);
+ SET_RESULT(path, 0, 0);
+ return NS_OK;
+ }
+
+ // ignore trailing whitespace and control characters
+ for (p = spec + specLen - 1; ((unsigned char)*p <= ' ') && (p != spec); --p) {
+ ;
+ }
+
+ specLen = p - spec + 1;
+
+ if (colon && (colon < slash || !slash)) {
+ //
+ // spec = <scheme>:/<the-rest>
+ //
+ // or
+ //
+ // spec = <scheme>:<authority>
+ // spec = <scheme>:<path-no-slashes>
+ //
+ if (!net_IsValidScheme(nsDependentCSubstring(spec, colon - spec))) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ SET_RESULT(scheme, offset, colon - spec);
+ if (authorityLen || pathLen) {
+ uint32_t schemeLen = colon + 1 - spec;
+ offset += schemeLen;
+ ParseAfterScheme(colon + 1, specLen - schemeLen, authorityPos,
+ authorityLen, pathPos, pathLen);
+ OFFSET_RESULT(authority, offset);
+ OFFSET_RESULT(path, offset);
+ }
+ } else {
+ //
+ // spec = <authority-no-port-or-password>/<path>
+ // spec = <path>
+ //
+ // or
+ //
+ // spec = <authority-no-port-or-password>/<path-with-colon>
+ // spec = <path-with-colon>
+ //
+ // or
+ //
+ // spec = <authority-no-port-or-password>
+ // spec = <path-no-slashes-or-colon>
+ //
+ SET_RESULT(scheme, 0, -1);
+ if (authorityLen || pathLen) {
+ ParseAfterScheme(spec, specLen, authorityPos, authorityLen, pathPos,
+ pathLen);
+ OFFSET_RESULT(authority, offset);
+ OFFSET_RESULT(path, offset);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ if (NS_WARN_IF(!auth)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (authLen < 0) authLen = strlen(auth);
+
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ SET_RESULT(hostname, 0, authLen);
+ if (port) *port = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseUserInfo(const char* userinfo, int32_t userinfoLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseServerInfo(const char* serverinfo, int32_t serverinfoLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ SET_RESULT(hostname, 0, -1);
+ if (port) *port = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParsePath(const char* path, int32_t pathLen,
+ uint32_t* filepathPos, int32_t* filepathLen,
+ uint32_t* queryPos, int32_t* queryLen,
+ uint32_t* refPos, int32_t* refLen) {
+ if (NS_WARN_IF(!path)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (pathLen < 0) pathLen = strlen(path);
+
+ // path = [/]<segment1>/<segment2>/<...>/<segmentN>?<query>#<ref>
+
+ // XXX PL_strnpbrk would be nice, but it's buggy
+
+ // search for first occurrence of either ? or #
+ const char *query_beg = nullptr, *query_end = nullptr;
+ const char* ref_beg = nullptr;
+ const char* p = nullptr;
+ for (p = path; p < path + pathLen; ++p) {
+ // only match the query string if it precedes the reference fragment
+ if (!ref_beg && !query_beg && *p == '?') {
+ query_beg = p + 1;
+ } else if (*p == '#') {
+ ref_beg = p + 1;
+ if (query_beg) query_end = p;
+ break;
+ }
+ }
+
+ if (query_beg) {
+ if (query_end) {
+ SET_RESULT(query, query_beg - path, query_end - query_beg);
+ } else {
+ SET_RESULT(query, query_beg - path, pathLen - (query_beg - path));
+ }
+ } else {
+ SET_RESULT(query, 0, -1);
+ }
+
+ if (ref_beg) {
+ SET_RESULT(ref, ref_beg - path, pathLen - (ref_beg - path));
+ } else {
+ SET_RESULT(ref, 0, -1);
+ }
+
+ const char* end;
+ if (query_beg) {
+ end = query_beg - 1;
+ } else if (ref_beg) {
+ end = ref_beg - 1;
+ } else {
+ end = path + pathLen;
+ }
+
+ // an empty file path is no file path
+ if (end != path) {
+ SET_RESULT(filepath, 0, end - path);
+ } else {
+ SET_RESULT(filepath, 0, -1);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseFilePath(const char* filepath, int32_t filepathLen,
+ uint32_t* directoryPos, int32_t* directoryLen,
+ uint32_t* basenamePos, int32_t* basenameLen,
+ uint32_t* extensionPos, int32_t* extensionLen) {
+ if (NS_WARN_IF(!filepath)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filepathLen < 0) filepathLen = strlen(filepath);
+
+ if (filepathLen == 0) {
+ SET_RESULT(directory, 0, -1);
+ SET_RESULT(basename, 0, 0); // assume a zero length file basename
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+ }
+
+ const char* p;
+ const char* end = filepath + filepathLen;
+
+ // search backwards for filename
+ for (p = end - 1; *p != '/' && p > filepath; --p) {
+ ;
+ }
+ if (*p == '/') {
+ // catch /.. and /.
+ if ((p + 1 < end && *(p + 1) == '.') &&
+ (p + 2 == end || (*(p + 2) == '.' && p + 3 == end))) {
+ p = end - 1;
+ }
+ // filepath = <directory><filename>.<extension>
+ SET_RESULT(directory, 0, p - filepath + 1);
+ ParseFileName(p + 1, end - (p + 1), basenamePos, basenameLen, extensionPos,
+ extensionLen);
+ OFFSET_RESULT(basename, p + 1 - filepath);
+ OFFSET_RESULT(extension, p + 1 - filepath);
+ } else {
+ // filepath = <filename>.<extension>
+ SET_RESULT(directory, 0, -1);
+ ParseFileName(filepath, filepathLen, basenamePos, basenameLen, extensionPos,
+ extensionLen);
+ }
+ return NS_OK;
+}
+
+nsresult nsBaseURLParser::ParseFileName(
+ const char* filename, int32_t filenameLen, uint32_t* basenamePos,
+ int32_t* basenameLen, uint32_t* extensionPos, int32_t* extensionLen) {
+ if (NS_WARN_IF(!filename)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filenameLen < 0) filenameLen = strlen(filename);
+
+ // no extension if filename ends with a '.'
+ if (filename[filenameLen - 1] != '.') {
+ // ignore '.' at the beginning
+ for (const char* p = filename + filenameLen - 1; p > filename; --p) {
+ if (*p == '.') {
+ // filename = <basename.extension>
+ SET_RESULT(basename, 0, p - filename);
+ SET_RESULT(extension, p + 1 - filename,
+ filenameLen - (p - filename + 1));
+ return NS_OK;
+ }
+ }
+ }
+ // filename = <basename>
+ SET_RESULT(basename, 0, filenameLen);
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+// nsNoAuthURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsNoAuthURLParser::ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ MOZ_ASSERT_UNREACHABLE("Shouldn't parse auth in a NoAuthURL!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+void nsNoAuthURLParser::ParseAfterScheme(const char* spec, int32_t specLen,
+ uint32_t* authPos, int32_t* authLen,
+ uint32_t* pathPos, int32_t* pathLen) {
+ MOZ_ASSERT(specLen >= 0, "unexpected");
+
+ // everything is the path
+ uint32_t pos = 0;
+ switch (CountConsecutiveSlashes(spec, specLen)) {
+ case 0:
+ case 1:
+ break;
+ case 2: {
+ const char* p = nullptr;
+ if (specLen > 2) {
+ // looks like there is an authority section
+
+ // if the authority looks like a drive number then we
+ // really want to treat it as part of the path
+ // [a-zA-Z][:|]{/\}
+ // i.e one of: c: c:\foo c:/foo c| c|\foo c|/foo
+ if ((specLen > 3) && (spec[3] == ':' || spec[3] == '|') &&
+ IsAsciiAlpha(spec[2]) &&
+ ((specLen == 4) || (spec[4] == '/') || (spec[4] == '\\'))) {
+ pos = 1;
+ break;
+ }
+ // Ignore apparent authority; path is everything after it
+ for (p = spec + 2; p < spec + specLen; ++p) {
+ if (*p == '/' || *p == '?' || *p == '#') break;
+ }
+ }
+ SET_RESULT(auth, 0, -1);
+ if (p && p != spec + specLen) {
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ } else {
+ SET_RESULT(path, 0, -1);
+ }
+ return;
+ }
+ default:
+ pos = 2;
+ break;
+ }
+ SET_RESULT(auth, pos, 0);
+ SET_RESULT(path, pos, specLen - pos);
+}
+
+#if defined(XP_WIN)
+NS_IMETHODIMP
+nsNoAuthURLParser::ParseFilePath(const char* filepath, int32_t filepathLen,
+ uint32_t* directoryPos, int32_t* directoryLen,
+ uint32_t* basenamePos, int32_t* basenameLen,
+ uint32_t* extensionPos,
+ int32_t* extensionLen) {
+ if (NS_WARN_IF(!filepath)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (filepathLen < 0) filepathLen = strlen(filepath);
+
+ // look for a filepath consisting of only a drive number, which may or
+ // may not have a leading slash.
+ if (filepathLen > 1 && filepathLen < 4) {
+ const char* end = filepath + filepathLen;
+ const char* p = filepath;
+ if (*p == '/') p++;
+ if ((end - p == 2) && (p[1] == ':' || p[1] == '|') && IsAsciiAlpha(*p)) {
+ // filepath = <drive-number>:
+ SET_RESULT(directory, 0, filepathLen);
+ SET_RESULT(basename, 0, -1);
+ SET_RESULT(extension, 0, -1);
+ return NS_OK;
+ }
+ }
+
+ // otherwise fallback on common implementation
+ return nsBaseURLParser::ParseFilePath(filepath, filepathLen, directoryPos,
+ directoryLen, basenamePos, basenameLen,
+ extensionPos, extensionLen);
+}
+#endif
+
+//----------------------------------------------------------------------------
+// nsAuthURLParser implementation
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ nsresult rv;
+
+ if (NS_WARN_IF(!auth)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (authLen < 0) authLen = strlen(auth);
+
+ if (authLen == 0) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ SET_RESULT(hostname, 0, 0);
+ if (port) *port = -1;
+ return NS_OK;
+ }
+
+ // search backwards for @
+ const char* p = auth + authLen - 1;
+ for (; (*p != '@') && (p > auth); --p) {
+ }
+ if (*p == '@') {
+ // auth = <user-info@server-info>
+ rv = ParseUserInfo(auth, p - auth, usernamePos, usernameLen, passwordPos,
+ passwordLen);
+ if (NS_FAILED(rv)) return rv;
+ rv = ParseServerInfo(p + 1, authLen - (p - auth + 1), hostnamePos,
+ hostnameLen, port);
+ if (NS_FAILED(rv)) return rv;
+ OFFSET_RESULT(hostname, p + 1 - auth);
+
+ // malformed if has a username or password
+ // but no host info, such as: http://u:p@/
+ if ((usernamePos || passwordPos) && (!hostnamePos || !*hostnameLen)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ } else {
+ // auth = <server-info>
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ rv = ParseServerInfo(auth, authLen, hostnamePos, hostnameLen, port);
+ if (NS_FAILED(rv)) return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseUserInfo(const char* userinfo, int32_t userinfoLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen) {
+ if (NS_WARN_IF(!userinfo)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (userinfoLen < 0) userinfoLen = strlen(userinfo);
+
+ if (userinfoLen == 0) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ return NS_OK;
+ }
+
+ const char* p = (const char*)memchr(userinfo, ':', userinfoLen);
+ if (p) {
+ // userinfo = <username:password>
+ SET_RESULT(username, 0, p - userinfo);
+ SET_RESULT(password, p - userinfo + 1, userinfoLen - (p - userinfo + 1));
+ } else {
+ // userinfo = <username>
+ SET_RESULT(username, 0, userinfoLen);
+ SET_RESULT(password, 0, -1);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAuthURLParser::ParseServerInfo(const char* serverinfo, int32_t serverinfoLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ if (NS_WARN_IF(!serverinfo)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (serverinfoLen < 0) serverinfoLen = strlen(serverinfo);
+
+ if (serverinfoLen == 0) {
+ SET_RESULT(hostname, 0, 0);
+ if (port) *port = -1;
+ return NS_OK;
+ }
+
+ // search backwards for a ':' but stop on ']' (IPv6 address literal
+ // delimiter). check for illegal characters in the hostname.
+ const char* p = serverinfo + serverinfoLen - 1;
+ const char *colon = nullptr, *bracket = nullptr;
+ for (; p > serverinfo; --p) {
+ switch (*p) {
+ case ']':
+ bracket = p;
+ break;
+ case ':':
+ if (bracket == nullptr) colon = p;
+ break;
+ case ' ':
+ // hostname must not contain a space
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+
+ if (colon) {
+ // serverinfo = <hostname:port>
+ SET_RESULT(hostname, 0, colon - serverinfo);
+ if (port) {
+ // XXX unfortunately ToInteger is not defined for substrings
+ nsAutoCString buf(colon + 1, serverinfoLen - (colon + 1 - serverinfo));
+ if (buf.Length() == 0) {
+ *port = -1;
+ } else {
+ const char* nondigit = NS_strspnp("0123456789", buf.get());
+ if (nondigit && *nondigit) return NS_ERROR_MALFORMED_URI;
+
+ nsresult err;
+ *port = buf.ToInteger(&err);
+ if (NS_FAILED(err) || *port < 0 ||
+ *port > std::numeric_limits<uint16_t>::max()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ }
+ }
+ } else {
+ // serverinfo = <hostname>
+ SET_RESULT(hostname, 0, serverinfoLen);
+ if (port) *port = -1;
+ }
+
+ // In case of IPv6 address check its validity
+ if (*hostnameLen > 1 && *(serverinfo + *hostnamePos) == '[' &&
+ *(serverinfo + *hostnamePos + *hostnameLen - 1) == ']' &&
+ !net_IsValidIPv6Addr(
+ Substring(serverinfo + *hostnamePos + 1, *hostnameLen - 2))) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return NS_OK;
+}
+
+void nsAuthURLParser::ParseAfterScheme(const char* spec, int32_t specLen,
+ uint32_t* authPos, int32_t* authLen,
+ uint32_t* pathPos, int32_t* pathLen) {
+ MOZ_ASSERT(specLen >= 0, "unexpected");
+
+ uint32_t nslash = CountConsecutiveSlashes(spec, specLen);
+
+ // search for the end of the authority section
+ const char* end = spec + specLen;
+ const char* p;
+ for (p = spec + nslash; p < end; ++p) {
+ if (*p == '/' || *p == '?' || *p == '#') break;
+ }
+ if (p < end) {
+ // spec = [/]<auth><path>
+ SET_RESULT(auth, nslash, p - (spec + nslash));
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ } else {
+ // spec = [/]<auth>
+ SET_RESULT(auth, nslash, specLen - nslash);
+ SET_RESULT(path, 0, -1);
+ }
+}
+
+//----------------------------------------------------------------------------
+// nsStdURLParser implementation
+//----------------------------------------------------------------------------
+
+void nsStdURLParser::ParseAfterScheme(const char* spec, int32_t specLen,
+ uint32_t* authPos, int32_t* authLen,
+ uint32_t* pathPos, int32_t* pathLen) {
+ MOZ_ASSERT(specLen >= 0, "unexpected");
+
+ uint32_t nslash = CountConsecutiveSlashes(spec, specLen);
+
+ // search for the end of the authority section
+ const char* end = spec + specLen;
+ const char* p;
+ for (p = spec + nslash; p < end; ++p) {
+ if (strchr("/?#;", *p)) break;
+ }
+ switch (nslash) {
+ case 0:
+ case 2:
+ if (p < end) {
+ // spec = (//)<auth><path>
+ SET_RESULT(auth, nslash, p - (spec + nslash));
+ SET_RESULT(path, p - spec, specLen - (p - spec));
+ } else {
+ // spec = (//)<auth>
+ SET_RESULT(auth, nslash, specLen - nslash);
+ SET_RESULT(path, 0, -1);
+ }
+ break;
+ case 1:
+ // spec = /<path>
+ SET_RESULT(auth, 0, -1);
+ SET_RESULT(path, 0, specLen);
+ break;
+ default:
+ // spec = ///[/]<path>
+ SET_RESULT(auth, 2, 0);
+ SET_RESULT(path, 2, specLen - 2);
+ }
+}
diff --git a/netwerk/base/nsURLParsers.h b/netwerk/base/nsURLParsers.h
new file mode 100644
index 0000000000..3143022b90
--- /dev/null
+++ b/netwerk/base/nsURLParsers.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsURLParsers_h__
+#define nsURLParsers_h__
+
+#include "nsIURLParser.h"
+#include "mozilla/Attributes.h"
+
+//----------------------------------------------------------------------------
+// base class for url parsers
+//----------------------------------------------------------------------------
+
+class nsBaseURLParser : public nsIURLParser {
+ public:
+ NS_DECL_NSIURLPARSER
+
+ nsBaseURLParser() = default;
+
+ protected:
+ // implemented by subclasses
+ virtual void ParseAfterScheme(const char* spec, int32_t specLen,
+ uint32_t* authPos, int32_t* authLen,
+ uint32_t* pathPos, int32_t* pathLen) = 0;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that do not have an authority section
+//
+// eg. file:foo/bar.txt
+// file:/foo/bar.txt (treated equivalently)
+// file:///foo/bar.txt
+//
+// eg. file:////foo/bar.txt (UNC-filepath = \\foo\bar.txt)
+//
+// XXX except in this case:
+// file://foo/bar.txt (the authority "foo" is ignored)
+//----------------------------------------------------------------------------
+
+class nsNoAuthURLParser final : public nsBaseURLParser {
+ ~nsNoAuthURLParser() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+#if defined(XP_WIN)
+ NS_IMETHOD ParseFilePath(const char*, int32_t, uint32_t*, int32_t*, uint32_t*,
+ int32_t*, uint32_t*, int32_t*) override;
+#endif
+
+ NS_IMETHOD ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) override;
+
+ void ParseAfterScheme(const char* spec, int32_t specLen, uint32_t* authPos,
+ int32_t* authLen, uint32_t* pathPos,
+ int32_t* pathLen) override;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that must have an authority section
+//
+// eg. http:www.foo.com/bar.html
+// http:/www.foo.com/bar.html
+// http://www.foo.com/bar.html (treated equivalently)
+// http:///www.foo.com/bar.html
+//----------------------------------------------------------------------------
+
+class nsAuthURLParser : public nsBaseURLParser {
+ protected:
+ virtual ~nsAuthURLParser() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) override;
+
+ NS_IMETHOD ParseUserInfo(const char* userinfo, int32_t userinfoLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos,
+ int32_t* passwordLen) override;
+
+ NS_IMETHOD ParseServerInfo(const char* serverinfo, int32_t serverinfoLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) override;
+
+ void ParseAfterScheme(const char* spec, int32_t specLen, uint32_t* authPos,
+ int32_t* authLen, uint32_t* pathPos,
+ int32_t* pathLen) override;
+};
+
+//----------------------------------------------------------------------------
+// an url parser for urls that may or may not have an authority section
+//
+// eg. http:www.foo.com (www.foo.com is authority)
+// http:www.foo.com/bar.html (www.foo.com is authority)
+// http:/www.foo.com/bar.html (www.foo.com is part of file path)
+// http://www.foo.com/bar.html (www.foo.com is authority)
+// http:///www.foo.com/bar.html (www.foo.com is part of file path)
+//----------------------------------------------------------------------------
+
+class nsStdURLParser : public nsAuthURLParser {
+ virtual ~nsStdURLParser() = default;
+
+ public:
+ void ParseAfterScheme(const char* spec, int32_t specLen, uint32_t* authPos,
+ int32_t* authLen, uint32_t* pathPos,
+ int32_t* pathLen) override;
+};
+
+#endif // nsURLParsers_h__
diff --git a/netwerk/base/rust-helper/Cargo.toml b/netwerk/base/rust-helper/Cargo.toml
new file mode 100644
index 0000000000..370d420182
--- /dev/null
+++ b/netwerk/base/rust-helper/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "netwerk_helper"
+version = "0.0.1"
+authors = ["Jeff Hemphill <jthemphill@mozilla.com>"]
+license = "MPL-2.0"
+
+[dependencies]
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
diff --git a/netwerk/base/rust-helper/cbindgen.toml b/netwerk/base/rust-helper/cbindgen.toml
new file mode 100644
index 0000000000..1e5e235576
--- /dev/null
+++ b/netwerk/base/rust-helper/cbindgen.toml
@@ -0,0 +1,18 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */"""
+include_guard = "mozilla_net_rustHelper_h"
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla", "net"]
+includes = ["nsError.h", "nsString.h"]
+
+[export]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+[export.rename]
+"ThinVec" = "nsTArray"
diff --git a/netwerk/base/rust-helper/moz.build b/netwerk/base/rust-helper/moz.build
new file mode 100644
index 0000000000..1f7512ecf9
--- /dev/null
+++ b/netwerk/base/rust-helper/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ CbindgenHeader("rust_helper.h", inputs=["/netwerk/base/rust-helper"])
+
+ EXPORTS.mozilla.net += [
+ "!rust_helper.h",
+ ]
diff --git a/netwerk/base/rust-helper/src/lib.rs b/netwerk/base/rust-helper/src/lib.rs
new file mode 100644
index 0000000000..7a97736e50
--- /dev/null
+++ b/netwerk/base/rust-helper/src/lib.rs
@@ -0,0 +1,360 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate nserror;
+use self::nserror::*;
+
+extern crate nsstring;
+use self::nsstring::{nsACString, nsCString};
+
+extern crate thin_vec;
+use self::thin_vec::ThinVec;
+
+use std::fs::File;
+use std::io::{self, BufRead};
+use std::net::Ipv4Addr;
+
+/// HTTP leading whitespace, defined in netwerk/protocol/http/nsHttp.h
+static HTTP_LWS: &'static [u8] = &[' ' as u8, '\t' as u8];
+
+/// Trim leading whitespace, trailing whitespace, and quality-value
+/// from a token.
+fn trim_token(token: &[u8]) -> &[u8] {
+ // Trim left whitespace
+ let ltrim = token
+ .iter()
+ .take_while(|c| HTTP_LWS.iter().any(|ws| &ws == c))
+ .count();
+
+ // Trim right whitespace
+ // remove "; q=..." if present
+ let rtrim = token[ltrim..]
+ .iter()
+ .take_while(|c| **c != (';' as u8) && HTTP_LWS.iter().all(|ws| ws != *c))
+ .count();
+
+ &token[ltrim..ltrim + rtrim]
+}
+
+#[no_mangle]
+/// Allocates an nsACString that contains a ISO 639 language list
+/// notated with HTTP "q" values for output with an HTTP Accept-Language
+/// header. Previous q values will be stripped because the order of
+/// the langs implies the q value. The q values are calculated by dividing
+/// 1.0 amongst the number of languages present.
+///
+/// Ex: passing: "en, ja"
+/// returns: "en,ja;q=0.5"
+///
+/// passing: "en, ja, fr_CA"
+/// returns: "en,ja;q=0.7,fr_CA;q=0.3"
+pub extern "C" fn rust_prepare_accept_languages<'a, 'b>(
+ i_accept_languages: &'a nsACString,
+ o_accept_languages: &'b mut nsACString,
+) -> nsresult {
+ if i_accept_languages.is_empty() {
+ return NS_OK;
+ }
+
+ let make_tokens = || {
+ i_accept_languages
+ .split(|c| *c == (',' as u8))
+ .map(|token| trim_token(token))
+ .filter(|token| token.len() != 0)
+ };
+
+ let n = make_tokens().count();
+
+ for (count_n, i_token) in make_tokens().enumerate() {
+ // delimiter if not first item
+ if count_n != 0 {
+ o_accept_languages.append(",");
+ }
+
+ let token_pos = o_accept_languages.len();
+ o_accept_languages.append(&i_token as &[u8]);
+
+ {
+ let o_token = o_accept_languages.to_mut();
+ canonicalize_language_tag(&mut o_token[token_pos..]);
+ }
+
+ // Divide the quality-values evenly among the languages.
+ let q = 1.0 - count_n as f32 / n as f32;
+
+ let u: u32 = ((q + 0.005) * 100.0) as u32;
+ // Only display q-value if less than 1.00.
+ if u < 100 {
+ // With a small number of languages, one decimal place is
+ // enough to prevent duplicate q-values.
+ // Also, trailing zeroes do not add any information, so
+ // they can be removed.
+ if n < 10 || u % 10 == 0 {
+ let u = (u + 5) / 10;
+ o_accept_languages.append(&format!(";q=0.{}", u));
+ } else {
+ // Values below 10 require zero padding.
+ o_accept_languages.append(&format!(";q=0.{:02}", u));
+ }
+ }
+ }
+
+ NS_OK
+}
+
+/// Defines a consistent capitalization for a given language string.
+///
+/// # Arguments
+/// * `token` - a narrow char slice describing a language.
+///
+/// Valid language tags are of the form
+/// "*", "fr", "en-US", "es-419", "az-Arab", "x-pig-latin", "man-Nkoo-GN"
+///
+/// Language tags are defined in the
+/// [rfc5646](https://tools.ietf.org/html/rfc5646) spec. According to
+/// the spec:
+///
+/// > At all times, language tags and their subtags, including private
+/// > use and extensions, are to be treated as case insensitive: there
+/// > exist conventions for the capitalization of some of the subtags,
+/// > but these MUST NOT be taken to carry meaning.
+///
+/// So why is this code even here? See bug 1108183, I guess.
+fn canonicalize_language_tag(token: &mut [u8]) {
+ for c in token.iter_mut() {
+ *c = c.to_ascii_lowercase();
+ }
+
+ let sub_tags = token.split_mut(|c| *c == ('-' as u8));
+ for (i, sub_tag) in sub_tags.enumerate() {
+ if i == 0 {
+ // ISO 639-1 language code, like the "en" in "en-US"
+ continue;
+ }
+
+ match sub_tag.len() {
+ // Singleton tag, like "x" or "i". These signify a
+ // non-standard language, so we stop capitalizing after
+ // these.
+ 1 => break,
+ // ISO 3166-1 Country code, like "US"
+ 2 => {
+ sub_tag[0] = sub_tag[0].to_ascii_uppercase();
+ sub_tag[1] = sub_tag[1].to_ascii_uppercase();
+ }
+ // ISO 15924 script code, like "Nkoo"
+ 4 => {
+ sub_tag[0] = sub_tag[0].to_ascii_uppercase();
+ }
+ _ => {}
+ };
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rust_net_is_valid_ipv4_addr<'a>(addr: &'a nsACString) -> bool {
+ is_valid_ipv4_addr(addr)
+}
+
+#[inline]
+fn try_apply_digit(current_octet: u8, digit_to_apply: u8) -> Option<u8> {
+ current_octet.checked_mul(10)?.checked_add(digit_to_apply)
+}
+
+pub fn is_valid_ipv4_addr<'a>(addr: &'a [u8]) -> bool {
+ let mut current_octet: Option<u8> = None;
+ let mut dots: u8 = 0;
+ for c in addr {
+ let c = *c as char;
+ match c {
+ '.' => {
+ match current_octet {
+ None => {
+ // starting an octet with a . is not allowed
+ return false;
+ }
+ Some(_) => {
+ dots = dots + 1;
+ current_octet = None;
+ }
+ }
+ }
+ // The character is not a digit
+ no_digit if no_digit.to_digit(10).is_none() => {
+ return false;
+ }
+ digit => {
+ match current_octet {
+ None => {
+ // Unwrap is sound because it has been checked in the previous arm
+ current_octet = Some(digit.to_digit(10).unwrap() as u8);
+ }
+ Some(octet) => {
+ if let Some(0) = current_octet {
+ // Leading 0 is not allowed
+ return false;
+ }
+ if let Some(applied) =
+ try_apply_digit(octet, digit.to_digit(10).unwrap() as u8)
+ {
+ current_octet = Some(applied);
+ } else {
+ // Multiplication or Addition overflowed
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+ dots == 3 && current_octet.is_some()
+}
+
+#[no_mangle]
+pub extern "C" fn rust_net_is_valid_ipv6_addr<'a>(addr: &'a nsACString) -> bool {
+ is_valid_ipv6_addr(addr)
+}
+
+#[inline(always)]
+fn fast_is_hex_digit(c: u8) -> bool {
+ match c {
+ b'0'..=b'9' => true,
+ b'a'..=b'f' => true,
+ b'A'..=b'F' => true,
+ _ => false,
+ }
+}
+
+pub fn is_valid_ipv6_addr<'a>(addr: &'a [u8]) -> bool {
+ let mut double_colon = false;
+ let mut colon_before = false;
+ let mut digits: u8 = 0;
+ let mut blocks: u8 = 0;
+
+ // The smallest ipv6 is unspecified (::)
+ // The IP starts with a single colon
+ if addr.len() < 2 || addr[0] == b':' && addr[1] != b':' {
+ return false;
+ }
+ //Enumerate with an u8 for cache locality
+ for (i, c) in (0u8..).zip(addr) {
+ match c {
+ maybe_digit if fast_is_hex_digit(*maybe_digit) => {
+ // Too many digits in the block
+ if digits == 4 {
+ return false;
+ }
+ colon_before = false;
+ digits += 1;
+ }
+ b':' => {
+ // Too many columns
+ if double_colon && colon_before || blocks == 8 {
+ return false;
+ }
+ if !colon_before {
+ if digits != 0 {
+ blocks += 1;
+ }
+ digits = 0;
+ colon_before = true;
+ } else if !double_colon {
+ double_colon = true;
+ }
+ }
+ b'.' => {
+ // IPv4 from the last block
+ if is_valid_ipv4_addr(&addr[(i - digits) as usize..]) {
+ return double_colon && blocks < 6 || !double_colon && blocks == 6;
+ }
+ return false;
+ }
+ _ => {
+ // Invalid character
+ return false;
+ }
+ }
+ }
+ if colon_before && !double_colon {
+ // The IP ends with a single colon
+ return false;
+ }
+ if digits != 0 {
+ blocks += 1;
+ }
+
+ double_colon && blocks < 8 || !double_colon && blocks == 8
+}
+
+#[no_mangle]
+pub extern "C" fn rust_net_is_valid_scheme_char(a_char: u8) -> bool {
+ is_valid_scheme_char(a_char)
+}
+
+#[no_mangle]
+pub extern "C" fn rust_net_is_valid_scheme<'a>(scheme: &'a nsACString) -> bool {
+ if scheme.is_empty() {
+ return false;
+ }
+
+ // first char must be alpha
+ if !scheme[0].is_ascii_alphabetic() {
+ return false;
+ }
+
+ scheme[1..]
+ .iter()
+ .all(|a_char| is_valid_scheme_char(*a_char))
+}
+
+fn is_valid_scheme_char(a_char: u8) -> bool {
+ a_char.is_ascii_alphanumeric() || a_char == b'+' || a_char == b'.' || a_char == b'-'
+}
+
+pub type ParsingCallback = extern "C" fn(&ThinVec<nsCString>) -> bool;
+
+#[no_mangle]
+pub extern "C" fn rust_parse_etc_hosts<'a>(path: &'a nsACString, callback: ParsingCallback) {
+ let file = match File::open(&*path.to_utf8()) {
+ Ok(file) => io::BufReader::new(file),
+ Err(..) => return,
+ };
+
+ let mut array = ThinVec::new();
+ for line in file.lines() {
+ let line = match line {
+ Ok(l) => l,
+ Err(..) => continue,
+ };
+
+ let mut iter = line.split('#').next().unwrap().split_whitespace();
+ iter.next(); // skip the IP
+
+ array.extend(
+ iter.filter(|host| {
+ // Make sure it's a valid domain
+ let invalid = [
+ '\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']',
+ ];
+ host.parse::<Ipv4Addr>().is_err() && !host.contains(&invalid[..])
+ })
+ .map(nsCString::from),
+ );
+
+ // /etc/hosts files can be huge. To make sure we don't block shutdown
+ // for every 100 domains that we parse we call the callback passing the
+ // domains and see if we should keep parsing.
+ if array.len() > 100 {
+ let keep_going = callback(&array);
+ array.clear();
+ if !keep_going {
+ break;
+ }
+ }
+ }
+
+ if !array.is_empty() {
+ callback(&array);
+ }
+}